Skip to content

Commit

Permalink
Added Halide backend support for deep learning layers
Browse files Browse the repository at this point in the history
  • Loading branch information
dkurt committed Jun 7, 2017
1 parent 27f6d4e commit 1070e98
Show file tree
Hide file tree
Showing 26 changed files with 2,810 additions and 11 deletions.
77 changes: 77 additions & 0 deletions modules/dnn/include/opencv2/dnn/dnn.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,23 @@ namespace dnn //! This namespace is used for dnn module functionlaity.

typedef std::vector<int> MatShape;

/**
* @brief Enum of computation backends supported by layers.
*/
enum Backend
{
DEFAULT,
HALIDE
};

/**
* @brief Compilation targets of Halide code.
*/
enum HalideTarget
{
HALIDE_CPU
};

/** @brief Initialize dnn module and built-in layers.
*
* This function automatically called on most of OpenCV builds,
Expand Down Expand Up @@ -131,6 +148,47 @@ namespace dnn //! This namespace is used for dnn module functionlaity.
*/
virtual int outputNameToIndex(String outputName);

/**
* @brief Ask layer if it support specific backend for doing computations.
* @param[in] backendId computation backend identifier.
* @see Backend
*/
virtual bool supportBackend(int backendId);

/**
* @brief Returns pointers to Halide functions.
* @param[in] inputs Pointers to input Halide buffers.
*
* Input buffers should be exactly the same that will be used in forward invocations.
* Despite we can use Halide::ImageParam based on input shape only,
* it helps prevent some memory management issues (if something wrong,
* Halide tests will be failed).
*/
virtual std::vector<void*> initHalide(const std::vector<void*> &inputs);

/**
* @brief Automatic Halide scheduling based on layer hyper-parameters.
* @param[in] funcs Halide functions.
* @param[in] inputs Blobs that will be used in forward invocations.
* @param[in] outputs Blobs that will be used in forward invocations.
*
* Layer don't use own Halide::Func members because we can have applied
* layers fusing. In this way the fused function should be scheduled.
*/
virtual void applyHalideScheduler(std::vector<void*>& funcs,
const std::vector<Mat*> &inputs,
const std::vector<Mat> &outputs) const;

/**
* @brief Implement layers fusing.
* @param[in] node pointer to backend node of bottom layer.
* @param[in] backendId specific backend identifier.
*
* Actual for graph-based backends. If layer attached successfully,
* returns pointer to fused node.
*/
virtual void* tryAttach(void* node, int backendId);

virtual bool getMemoryShapes(const std::vector<MatShape> &inputs,
const int requiredOutputs,
std::vector<MatShape> &outputs,
Expand Down Expand Up @@ -251,6 +309,25 @@ namespace dnn //! This namespace is used for dnn module functionlaity.
/** @overload */
void forwardOpt(const std::vector<LayerId> &toLayers);

/**
* @brief Compile Halide layers.
* @param[in] targetId Compilation target of Halide code.
* @param[in] scheduler Path to YAML file with scheduling directives.
* @see HalideTarget
*
* Schedule layers that support Halide backend. Then compile them for
* specific target. For layers that not represented in scheduling file
* or if no manual scheduling used at all, automatic scheduling will be applied.
*/
void compileHalide(int targetId = HALIDE_CPU, const std::string& scheduler = "");

/**
* @brief Ask network to use specific computation backend where it supported.
* @param[in] backendId backend identifier.
* @see Backend
*/
void setPreferableBackend(int backendId);

/** @brief Sets the new value for the layer output blob
* @param outputName descriptor of the updating layer output blob.
* @param blob new blob.
Expand Down
139 changes: 139 additions & 0 deletions modules/dnn/perf/perf_halide_net.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html.
//
// Copyright (C) 2017, Intel Corporation, all rights reserved.
// Third party copyrights are property of their respective owners.

namespace cvtest
{

#ifdef HAVE_HALIDE
using namespace cv;
using namespace dnn;

static void loadNet(const std::string& weights, const std::string& proto,
const std::string& scheduler, int inWidth, int inHeight,
const std::string& outputLayer, const std::string& framework,
Net* net, int* outputLayerId)
{
Mat input(inHeight, inWidth, CV_32FC3);
randu(input, 0.0f, 1.0f);

if (framework == "caffe")
{
*net = cv::dnn::readNetFromCaffe(proto, weights);
}
else if (framework == "torch")
{
*net = cv::dnn::readNetFromTorch(weights);
}
else if (framework == "tensorflow")
{
*net = cv::dnn::readNetFromTensorflow(weights);
}
else
CV_Error(Error::StsNotImplemented, "Unknown framework " + framework);

net->setBlob("", cv::dnn::blobFromImage(input, 1.0, false));
net->setPreferableBackend(HALIDE);
net->compileHalide(HALIDE_CPU, scheduler);
*outputLayerId = net->getLayerId(outputLayer);
net->forward(*outputLayerId);
}

PERF_TEST(GoogLeNet, HalidePerfTest)
{
Net net;
int outputLayerId;
loadNet(findDataFile("dnn/bvlc_googlenet.caffemodel"),
findDataFile("dnn/bvlc_googlenet.prototxt"),
"", 227, 227, "prob", "caffe", &net, &outputLayerId);

TEST_CYCLE_N(10)
{
net.forward(outputLayerId);
}
SANITY_CHECK_NOTHING();
}

PERF_TEST(AlexNet, HalidePerfTest)
{
Net net;
int outputLayerId;
loadNet(findDataFile("dnn/bvlc_alexnet.caffemodel"),
findDataFile("dnn/bvlc_alexnet.prototxt"),
findDataFile("dnn/halide_scheduler_alexnet.yml"),
227, 227, "prob", "caffe", &net, &outputLayerId);

TEST_CYCLE_N(10)
{
net.forward(outputLayerId);
}
SANITY_CHECK_NOTHING();
}

// PERF_TEST(ResNet50, HalidePerfTest)
// {
// Net net;
// int outputLayerId;
// loadNet(findDataFile("dnn/ResNet-50-model.caffemodel"),
// findDataFile("dnn/ResNet-50-deploy.prototxt"),
// findDataFile("dnn/halide_scheduler_resnet_50.yml"),
// 224, 224, "prob", "caffe", &net, &outputLayerId);
//
// TEST_CYCLE_N(10)
// {
// net.forward(outputLayerId);
// }
// SANITY_CHECK_NOTHING();
// }

// PERF_TEST(SqueezeNet_v1_1, HalidePerfTest)
// {
// Net net;
// int outputLayerId;
// loadNet(findDataFile("dnn/squeezenet_v1_1.caffemodel"),
// findDataFile("dnn/squeezenet_v1_1.prototxt"),
// findDataFile("dnn/halide_scheduler_squeezenet_v1_1.yml"),
// 227, 227, "prob", "caffe", &net, &outputLayerId);
//
// TEST_CYCLE_N(10)
// {
// net.forward(outputLayerId);
// }
// SANITY_CHECK_NOTHING();
// }

PERF_TEST(Inception_5h, HalidePerfTest)
{
Net net;
int outputLayerId;
loadNet(findDataFile("dnn/tensorflow_inception_graph.pb"), "",
findDataFile("dnn/halide_scheduler_inception_5h.yml"),
224, 224, "softmax2", "tensorflow", &net, &outputLayerId);

TEST_CYCLE_N(10)
{
net.forward(outputLayerId);
}
SANITY_CHECK_NOTHING();
}

PERF_TEST(ENet, HalidePerfTest)
{
Net net;
int outputLayerId;
loadNet(findDataFile("dnn/Enet-model-best.net"), "",
findDataFile("dnn/halide_scheduler_enet.yml"),
512, 256, "l367_Deconvolution", "torch", &net, &outputLayerId);

TEST_CYCLE_N(10)
{
net.forward(outputLayerId);
}
SANITY_CHECK_NOTHING();
}
#endif // HAVE_HALIDE

} // namespace cvtest
11 changes: 10 additions & 1 deletion modules/dnn/perf/perf_main.cpp
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@
#include "perf_precomp.hpp"

CV_PERF_TEST_MAIN(dnn)
static const char* extraTestDataPath =
#ifdef WINRT
NULL;
#else
getenv("OPENCV_DNN_TEST_DATA_PATH");
#endif

CV_PERF_TEST_MAIN(dnn,
extraTestDataPath ? (void)cvtest::addDataSearchPath(extraTestDataPath) : (void)0
)
127 changes: 127 additions & 0 deletions modules/dnn/samples/squeezenet_halide.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html.
//
// Copyright (C) 2017, Intel Corporation, all rights reserved.
// Third party copyrights are property of their respective owners.

// Sample of using Halide backend in OpenCV deep learning module.
// Based on dnn/samples/caffe_googlenet.cpp.

#include <opencv2/dnn.hpp>
#include <opencv2/imgproc.hpp>
#include <opencv2/highgui.hpp>
using namespace cv;
using namespace cv::dnn;

#include <fstream>
#include <iostream>
#include <cstdlib>

/* Find best class for the blob (i. e. class with maximal probability) */
void getMaxClass(const Mat &probBlob, int *classId, double *classProb)
{
Mat probMat = probBlob.reshape(1, 1); //reshape the blob to 1x1000 matrix
Point classNumber;

minMaxLoc(probMat, NULL, classProb, NULL, &classNumber);
*classId = classNumber.x;
}

std::vector<std::string> readClassNames(const char *filename = "synset_words.txt")
{
std::vector<std::string> classNames;

std::ifstream fp(filename);
if (!fp.is_open())
{
std::cerr << "File with classes labels not found: " << filename << std::endl;
exit(-1);
}

std::string name;
while (!fp.eof())
{
std::getline(fp, name);
if (name.length())
classNames.push_back( name.substr(name.find(' ')+1) );
}

fp.close();
return classNames;
}

int main(int argc, char **argv)
{
initModule(); // Required if OpenCV is built as static libs.

std::string modelTxt = "train_val.prototxt";
std::string modelBin = "squeezenet_v1.1.caffemodel";
std::string scheduler = "halide_scheduler_squeezenet_v1_1.yml";
std::string imageFile = (argc > 1) ? argv[1] : "space_shuttle.jpg";

//! [Read and initialize network]
Net net = dnn::readNetFromCaffe(modelTxt, modelBin);
//! [Read and initialize network]

//! [Check that network was read successfully]
if (net.empty())
{
std::cerr << "Can't load network by using the following files: " << std::endl;
std::cerr << "prototxt: " << modelTxt << std::endl;
std::cerr << "caffemodel: " << modelBin << std::endl;
std::cerr << "SqueezeNet v1.1 can be downloaded from:" << std::endl;
std::cerr << "https://github.com/DeepScale/SqueezeNet/tree/master/SqueezeNet_v1.1" << std::endl;
exit(-1);
}
//! [Check that network was read successfully]

//! [Prepare blob]
Mat img = imread(imageFile);
if (img.empty())
{
std::cerr << "Can't read image from the file: " << imageFile << std::endl;
exit(-1);
}
if (img.channels() != 3)
{
std::cerr << "Image " << imageFile << " isn't 3-channel" << std::endl;
exit(-1);
}

resize(img, img, Size(227, 227)); // SqueezeNet v1.1 predict class by 3x227x227 input image.
Mat inputBlob = blobFromImage(img, 1.0, false); // Convert Mat to 4-dimensional batch.
//! [Prepare blob]

//! [Set input blob]
net.setBlob("", inputBlob); // Set the network input.
//! [Set input blob]

//! [Enable Halide backend]
net.setPreferableBackend(HALIDE); // Tell engine to use Halide where it possible.
//! [Enable Halide backend]

//! [Compile Halide pipeline]
net.compileHalide(); // Compile Halide pipeline.
//! [Compile Halide pipeline]

//! [Make forward pass]
net.forward(); // Compute output.
//! [Make forward pass]

//! [Gather output]
Mat prob = net.getBlob("prob"); // Gather output of "prob" layer.

int classId;
double classProb;
getMaxClass(prob, &classId, &classProb); // Find the best class.
//! [Gather output]

//! [Print results]
std::vector<std::string> classNames = readClassNames();
std::cout << "Best class: #" << classId << " '" << classNames.at(classId) << "'" << std::endl;
std::cout << "Probability: " << classProb * 100 << "%" << std::endl;
//! [Print results]

return 0;
} //main
Loading

0 comments on commit 1070e98

Please sign in to comment.