MobileNet-SSD and VGG-SSD topologies in Halide
authorDmitry Kurtaev <dmitry.kurtaev+github@gmail.com>
Wed, 6 Sep 2017 07:34:07 +0000 (10:34 +0300)
committerDmitry Kurtaev <dmitry.kurtaev+github@gmail.com>
Fri, 8 Sep 2017 06:55:53 +0000 (09:55 +0300)
modules/dnn/include/opencv2/dnn/dnn.hpp
modules/dnn/src/dnn.cpp
modules/dnn/src/op_halide.cpp
modules/dnn/src/op_halide.hpp
modules/dnn/test/test_halide_layers.cpp
modules/dnn/test/test_halide_nets.cpp

index 01e0021..6a4d4e7 100644 (file)
@@ -146,6 +146,11 @@ CV__DNN_EXPERIMENTAL_NS_BEGIN
          */
         virtual void copyToHost() = 0;
 
+        /**
+         * @brief Indicate that an actual data is on CPU.
+         */
+        virtual void setHostDirty() = 0;
+
         int backendId;  //!< Backend identifier.
         int targetId;   //!< Target identifier.
     };
index e4671ec..7dba3d2 100644 (file)
@@ -199,125 +199,6 @@ struct LayerPin
     }
 };
 
-// Objects of this class manages wrappers. For every CPU memory pointer and shape
-// one and only wrapper. Now it support wrapping for single backend and target.
-class BackendWrapManager
-{
-public:
-    Ptr<BackendWrapper> wrap(const Mat& m, int backendId, int targetId)
-    {
-        CV_TRACE_FUNCTION();
-
-        CV_Assert(backendId != DNN_BACKEND_DEFAULT);
-
-        std::map<void*, Ptr<BackendWrapper> >::iterator hostsIt;
-        // Check that the same CPU memory was previously wrapped.
-        hostsIt = hostWrappers.find(m.data);
-        if (hostsIt == hostWrappers.end())
-        {
-            // If not wrapped before.
-            return (hostWrappers[m.data] = wrapHost(m, backendId, targetId));
-        }
-        else
-        {
-            // Find if wrapper of this host and shape was created before.
-            std::map<std::pair<void*, MatSize>, Ptr<BackendWrapper> >::iterator it;
-            std::pair<void*, MatSize> key(m.data, m.size);
-            it = extraWrappers.find(key);
-            if (it == extraWrappers.end())
-            {
-                MatShape shape(m.dims);
-                for (int i = 0; i < m.dims; ++i)
-                    shape[i] = m.size.p[i];
-                return (extraWrappers[key] = wrapUser(hostsIt->second, shape));
-            }
-            else
-                return it->second;
-        }
-    }
-
-    std::vector<Ptr<BackendWrapper> > wrap(const std::vector<Mat*>& mats,
-                                           int backendId, int targetId)
-    {
-        const int num = mats.size();
-        std::vector<Ptr<BackendWrapper> > dst(num);
-        for (int i = 0; i < num; ++i)
-        {
-            dst[i] = wrap(*mats[i], backendId, targetId);
-        }
-        return dst;
-    }
-
-    std::vector<Ptr<BackendWrapper> > wrap(const std::vector<Mat>& mats,
-                                           int backendId, int targetId)
-    {
-        const int num = mats.size();
-        std::vector<Ptr<BackendWrapper> > dst(num);
-        for (int i = 0; i < num; ++i)
-        {
-            dst[i] = wrap(mats[i], backendId, targetId);
-        }
-        return dst;
-    }
-
-    void reset()
-    {
-        CV_TRACE_FUNCTION();
-
-        hostWrappers.clear();
-        extraWrappers.clear();
-    }
-
-private:
-    // Backend-specific wrapping function.
-    Ptr<BackendWrapper> wrapHost(const Mat& m, int backendId, int targetId)
-    {
-        if (backendId == DNN_BACKEND_DEFAULT)
-        {
-            return Ptr<BackendWrapper>();
-        }
-        else if (backendId == DNN_BACKEND_HALIDE)
-        {
-            CV_Assert(haveHalide());
-#ifdef HAVE_HALIDE
-            return Ptr<BackendWrapper>(new HalideBackendWrapper(targetId, m));
-#endif  // HAVE_HALIDE
-        }
-        else
-        {
-            CV_Error(Error::StsNotImplemented, "Unknown backend identifier");
-        }
-        return Ptr<BackendWrapper>();
-    }
-
-    // Backend-specific wrapping function.
-    Ptr<BackendWrapper> wrapUser(const Ptr<BackendWrapper>& host, const MatShape& shape)
-    {
-        int backendId = host->backendId;
-        if (backendId == DNN_BACKEND_DEFAULT)
-        {
-            return Ptr<BackendWrapper>();
-        }
-        else if (backendId == DNN_BACKEND_HALIDE)
-        {
-            CV_Assert(haveHalide());
-#ifdef HAVE_HALIDE
-            return Ptr<BackendWrapper>(new HalideBackendWrapper(host, shape));
-#endif  // HAVE_HALIDE
-        }
-        else
-        {
-            CV_Error(Error::StsNotImplemented, "Unknown backend identifier");
-        }
-        return Ptr<BackendWrapper>();
-    }
-
-    // Wrappers that initialized for memory hosts (first wrapping of CPU data).
-    std::map<void*, Ptr<BackendWrapper> > hostWrappers;
-    // The rest of wrappers. They initialized for non-host cv::Mat.
-    std::map<std::pair<void*, MatSize>, Ptr<BackendWrapper> > extraWrappers;
-};
-
 struct LayerData
 {
     LayerData() : id(-1), flag(0) {}
@@ -340,6 +221,8 @@ struct LayerData
     std::set<int> inputLayersId;
     std::set<int> requiredOutputs;
     std::vector<LayerPin> consumers;
+    std::vector<Ptr<BackendWrapper> > outputBlobsWrappers;
+    std::vector<Ptr<BackendWrapper> > inputBlobsWrappers;
 
     Ptr<Layer> layerInstance;
     std::vector<Mat> outputBlobs;
@@ -618,6 +501,24 @@ private:
     std::map<LayerPin, Mat> memHosts;
 };
 
+static Ptr<BackendWrapper> wrapMat(int backendId, int targetId, const cv::Mat& m)
+{
+    if (backendId == DNN_BACKEND_DEFAULT)
+    {
+        return Ptr<BackendWrapper>();
+    }
+    else if (backendId == DNN_BACKEND_HALIDE)
+    {
+        CV_Assert(haveHalide());
+#ifdef HAVE_HALIDE
+        return Ptr<BackendWrapper>(new HalideBackendWrapper(targetId, m));
+#endif  // HAVE_HALIDE
+    }
+    else
+        CV_Error(Error::StsNotImplemented, "Unknown backend identifier");
+    return Ptr<BackendWrapper>();
+}
+
 struct Net::Impl
 {
     typedef std::map<int, LayerShapes> LayersShapesMap;
@@ -650,8 +551,8 @@ struct Net::Impl
     int preferableBackend;
     int preferableTarget;
     String halideConfigFile;
-    // Backend-specific wrapping manager.
-    BackendWrapManager backendWrapper;
+    // Map host data to backend specific wrapper.
+    std::map<void*, Ptr<BackendWrapper> > backendWrappers;
 
     int lastLayerId;
 
@@ -659,6 +560,62 @@ struct Net::Impl
     bool fusion;
     std::vector<int64> layersTimings;
 
+    Ptr<BackendWrapper> wrap(const Mat& host)
+    {
+        if (preferableBackend == DNN_BACKEND_DEFAULT)
+            return Ptr<BackendWrapper>();
+
+        MatShape shape(host.dims);
+        for (int i = 0; i < host.dims; ++i)
+            shape[i] = host.size[i];
+
+        void* data = host.data;
+        if (backendWrappers.find(data) != backendWrappers.end())
+        {
+            Ptr<BackendWrapper> baseBuffer = backendWrappers[data];
+            if (preferableBackend == DNN_BACKEND_HALIDE)
+            {
+                CV_Assert(haveHalide());
+  #ifdef HAVE_HALIDE
+                return Ptr<BackendWrapper>(new HalideBackendWrapper(baseBuffer, shape));
+  #endif  // HAVE_HALIDE
+            }
+            else
+                CV_Error(Error::StsNotImplemented, "Unknown backend identifier");
+        }
+
+        Ptr<BackendWrapper> wrapper = wrapMat(preferableBackend, preferableTarget, host);
+        backendWrappers[data] = wrapper;
+        return wrapper;
+    }
+
+    class HalideCompiler : public ParallelLoopBody
+    {
+    public:
+        HalideCompiler(const MapIdToLayerData& layers_, int preferableTarget_)
+            : layers(&layers_), preferableTarget(preferableTarget_) {}
+
+        void operator()(const Range& r) const
+        {
+            MapIdToLayerData::const_iterator it = layers->begin();
+            for (int i = 0; i < r.start && it != layers->end(); ++i, ++it) {}
+            for (int i = r.start; i < r.end && it != layers->end(); ++i, ++it)
+            {
+                const LayerData &ld = it->second;
+                Ptr<Layer> layer = ld.layerInstance;
+                bool skip = ld.skipFlags.find(DNN_BACKEND_HALIDE)->second;
+                if (layer->supportBackend(DNN_BACKEND_HALIDE) && !skip)
+                {
+                    Ptr<BackendNode> node = ld.backendNodes.find(DNN_BACKEND_HALIDE)->second;
+                    dnn::compileHalide(ld.outputBlobs, node, preferableTarget);
+                }
+            }
+        }
+    private:
+        const MapIdToLayerData* layers;
+        int preferableTarget;
+    };
+
     void compileHalide()
     {
         CV_TRACE_FUNCTION();
@@ -682,10 +639,9 @@ struct Net::Impl
                                                 ld.inputBlobs, ld.outputBlobs,
                                                 preferableTarget);
                 }
-                dnn::compileHalide(ld.outputBlobs, ld.backendNodes[DNN_BACKEND_HALIDE],
-                                   preferableTarget);
             }
         }
+        parallel_for_(Range(0, layers.size()), HalideCompiler(layers, preferableTarget));
     }
 
     void clear()
@@ -917,7 +873,6 @@ struct Net::Impl
     {
         CV_TRACE_FUNCTION();
 
-        backendWrapper.reset();
         if (preferableBackend == DNN_BACKEND_DEFAULT)
         {
             CV_Assert(preferableTarget == DNN_TARGET_CPU);
@@ -967,12 +922,10 @@ struct Net::Impl
             }
             // No layers fusion.
             ldTop.skipFlags[preferableBackend] = false;
-            std::vector<Ptr<BackendWrapper> > inputs =
-                backendWrapper.wrap(ldTop.inputBlobs, preferableBackend,
-                                    preferableTarget);
             if (preferableBackend == DNN_BACKEND_HALIDE)
             {
-                ldTop.backendNodes[DNN_BACKEND_HALIDE] = layerTop->initHalide(inputs);
+                ldTop.backendNodes[DNN_BACKEND_HALIDE] =
+                    layerTop->initHalide(ldTop.inputBlobsWrappers);
                 baseIt = it;
             }
             else
@@ -1021,12 +974,14 @@ struct Net::Impl
 
         //bind inputs
         ld.inputBlobs.resize(ninputs);
+        ld.inputBlobsWrappers.resize(ninputs);
         for (size_t i = 0; i < ninputs; i++)
         {
             LayerPin from = ld.inputBlobsId[i];
             CV_Assert(from.valid());
             CV_DbgAssert(layers.count(from.lid) && (int)layers[from.lid].outputBlobs.size() > from.oid);
             ld.inputBlobs[i] = &layers[from.lid].outputBlobs[from.oid];
+            ld.inputBlobsWrappers[i] = layers[from.lid].outputBlobsWrappers[from.oid];
         }
 
         LayersShapesMap::const_iterator layerShapesIt = layersShapes.find(lid);
@@ -1036,6 +991,11 @@ struct Net::Impl
         std::vector<LayerPin> pinsForInternalBlobs;
         bool maximizeReuse = preferableBackend == DNN_BACKEND_HALIDE;
         blobManager.allocateBlobsForLayer(ld, layerShapesIt->second, pinsForInternalBlobs, maximizeReuse);
+        ld.outputBlobsWrappers.resize(ld.outputBlobs.size());
+        for (int i = 0; i < ld.outputBlobs.size(); ++i)
+        {
+            ld.outputBlobsWrappers[i] = wrap(ld.outputBlobs[i]);
+        }
 
         Ptr<Layer> layerPtr = ld.getLayerInstance();
         {
@@ -1256,6 +1216,8 @@ struct Net::Impl
         getLayersShapes(inputShapes, layersShapes);
 
         blobManager.reset();
+        backendWrappers.clear();
+        blobManager.addReference(LayerPin(0, 0));
         for (it = layers.begin(); it != layers.end(); ++it)
         {
             const LayerData& ld = it->second;
@@ -1291,18 +1253,28 @@ struct Net::Impl
             !layer->supportBackend(preferableBackend))
         {
             if( !ld.skipFlags[DNN_BACKEND_DEFAULT] )
+            {
+                for (int i = 0, n = ld.inputBlobsWrappers.size(); i < n; ++i)
+                {
+                    if (!ld.inputBlobsWrappers[i].empty())
+                        ld.inputBlobsWrappers[i]->copyToHost();
+                }
                 layer->forward(ld.inputBlobs, ld.outputBlobs, ld.internals);
+                for (int i = 0, n = ld.outputBlobsWrappers.size(); i < n; ++i)
+                {
+                    if (!ld.outputBlobsWrappers[i].empty())
+                        ld.outputBlobsWrappers[i]->setHostDirty();
+                }
+            }
             else
                 tm.reset();
         }
         else if (!ld.skipFlags[preferableBackend])
         {
-            std::vector<Ptr<BackendWrapper> > outputs =
-                backendWrapper.wrap(ld.outputBlobs, preferableBackend, preferableTarget);
             Ptr<BackendNode> node = ld.backendNodes[preferableBackend];
             if (preferableBackend == DNN_BACKEND_HALIDE)
             {
-                forwardHalide(outputs, node);
+                forwardHalide(ld.outputBlobsWrappers, node);
             }
             else
             {
@@ -1421,11 +1393,10 @@ struct Net::Impl
             CV_Error(Error::StsOutOfRange, "Layer \"" + ld.name + "\" produce only " + toString(ld.outputBlobs.size()) +
                                            " outputs, the #" + toString(pin.oid) + " was requsted");
         }
-        if (preferableBackend != DNN_BACKEND_DEFAULT)
+        if (preferableBackend != DNN_TARGET_CPU)
         {
             // Transfer data to CPU if it's require.
-            backendWrapper.wrap(ld.outputBlobs[pin.oid], preferableBackend,
-                                preferableTarget)->copyToHost();
+            ld.outputBlobsWrappers[pin.oid]->copyToHost();
         }
         else
         {
@@ -1633,6 +1604,7 @@ void Net::setInput(const Mat &blob_, const String& name)
 
     LayerData &ld = impl->layers[pin.lid];
     ld.outputBlobs.resize( std::max(pin.oid+1, (int)ld.requiredOutputs.size()) );
+    ld.outputBlobsWrappers.resize(ld.outputBlobs.size());
     MatShape prevShape = shape(ld.outputBlobs[pin.oid]);
     bool oldShape = prevShape == shape(blob_);
     if (oldShape)
@@ -1640,6 +1612,10 @@ void Net::setInput(const Mat &blob_, const String& name)
     else
         ld.outputBlobs[pin.oid] = blob_.clone();
 
+    if (!ld.outputBlobsWrappers[pin.oid].empty())
+    {
+        ld.outputBlobsWrappers[pin.oid]->setHostDirty();
+    }
     impl->netWasAllocated = impl->netWasAllocated && oldShape;
 }
 
index 51c7a57..a2d4309 100644 (file)
@@ -18,11 +18,30 @@ namespace dnn
 {
 
 #ifdef HAVE_HALIDE
+static MatShape getBufferShape(const MatShape& shape)
+{
+    if (shape.size() == 2 || shape.size() == 4)
+    {
+        int w, h, c, n;
+        getCanonicalSize(shape, &w, &h, &c, &n);
+        return {w, h, c, n};
+    }
+    else
+    {
+        MatShape bufferShape(shape);
+        std::reverse(bufferShape.begin(), bufferShape.end());
+        return bufferShape;
+    }
+}
+
+static MatShape getBufferShape(const MatSize& size)
+{
+    return getBufferShape(MatShape(size.p, size.p + size[-1]));
+}
+
 Halide::Buffer<float> wrapToHalideBuffer(const Mat& mat)
 {
-    int n, c, w, h;
-    getCanonicalSize(mat.size, &w, &h, &c, &n);
-    return wrapToHalideBuffer(mat, {w, h, c, n});
+    return wrapToHalideBuffer(mat, getBufferShape(mat.size));
 }
 
 Halide::Buffer<float> wrapToHalideBuffer(const Mat& mat,
@@ -97,11 +116,9 @@ HalideBackendWrapper::HalideBackendWrapper(const Ptr<BackendWrapper>& base,
     : BackendWrapper(DNN_BACKEND_HALIDE, base->targetId)
 {
     managesDevMemory = false;
-    int w, h, c, n;
-    getCanonicalSize(shape, &w, &h, &c, &n);
     Halide::Buffer<float> baseBuffer = halideBuffer(base);
     buffer = Halide::Buffer<float>((float*)baseBuffer.raw_buffer()->host,
-                                   {w, h, c, n});
+                                   getBufferShape(shape));
     if (baseBuffer.has_device_allocation())
     {
         buffer.raw_buffer()->device = baseBuffer.raw_buffer()->device;
@@ -127,32 +144,23 @@ HalideBackendWrapper::~HalideBackendWrapper()
 
 void HalideBackendWrapper::copyToHost()
 {
-    CV_Assert(targetId == DNN_TARGET_CPU || buffer.device_dirty());
     if (buffer.device_dirty())
     {
         buffer.device_sync();
         buffer.copy_to_host();
     }
 }
+
+void HalideBackendWrapper::setHostDirty()
+{
+    buffer.set_device_dirty(false);
+    buffer.set_host_dirty();
+}
 #endif  // HAVE_HALIDE
 
-void getCanonicalSize(const MatSize& size, int* width, int* height,
-                      int* channels, int* batch)
+void getCanonicalSize(const MatSize& size, int* w, int* h, int* c, int* n)
 {
-    const int dims = size.p[-1];
-    CV_Assert(dims == 2 || dims == 4);
-    *batch = size[0];
-    *channels = size[1];
-    if (dims == 4)
-    {
-        *width = size[3];
-        *height = size[2];
-    }
-    else
-    {
-        *width = 1;
-        *height = 1;
-    }
+    getCanonicalSize(MatShape(size.p, size.p + size[-1]), w, h, c, n);
 }
 
 void getCanonicalSize(const MatShape& shape, int* width, int* height,
@@ -174,7 +182,7 @@ void getCanonicalSize(const MatShape& shape, int* width, int* height,
     }
 }
 
-void compileHalide(std::vector<Mat> &outputs, Ptr<BackendNode>& node, int targetId)
+void compileHalide(const std::vector<Mat> &outputs, Ptr<BackendNode>& node, int targetId)
 {
 #ifdef HAVE_HALIDE
     CV_Assert(!node.empty());
index 1e0358e..715293d 100644 (file)
@@ -61,6 +61,8 @@ namespace dnn
 
         virtual void copyToHost();
 
+        virtual void setHostDirty();
+
         Halide::Buffer<float> buffer;
 
     private:
@@ -80,7 +82,7 @@ namespace dnn
                        const Ptr<BackendNode>& node);
 
     // Compile Halide pipeline to specific target. Use outputs to set bounds of functions.
-    void compileHalide(std::vector<Mat> &outputs, Ptr<BackendNode>& node, int targetId);
+    void compileHalide(const std::vector<Mat> &outputs, Ptr<BackendNode>& node, int targetId);
 
     bool haveHalide();
 }  // namespace dnn
index f3dd2bf..79f767a 100644 (file)
@@ -646,6 +646,48 @@ INSTANTIATE_TEST_CASE_P(Layer_Test_Halide, Eltwise, Combine(
 /*num convs*/  Values(1, 2, 3),
 /*weighted(for sum only)*/ Bool()
 ));
+
+////////////////////////////////////////////////////////////////////////////
+// Mixed backends
+////////////////////////////////////////////////////////////////////////////
+TEST(MixedBackends_Halide_Default_Halide, Accuracy)
+{
+    // Just a layer that supports Halide backend.
+    LayerParams lrn;
+    lrn.type = "LRN";
+    lrn.name = "testLRN";
+
+    // Some of layers that doesn't supports Halide backend yet.
+    LayerParams mvn;
+    mvn.type = "MVN";
+    mvn.name = "testMVN";
+
+    // Halide layer again.
+    LayerParams lrn2;
+    lrn2.type = "LRN";
+    lrn2.name = "testLRN2";
+
+    Net net;
+    int lrnId = net.addLayer(lrn.name, lrn.type, lrn);
+    net.connect(0, 0, lrnId, 0);
+    net.addLayerToPrev(mvn.name, mvn.type, mvn);
+    net.addLayerToPrev(lrn2.name, lrn2.type, lrn2);
+
+    Mat input({4, 3, 5, 6}, CV_32F);
+    randu(input, -1.0f, 1.0f);
+    net.setInput(input);
+    Mat outputDefault = net.forward().clone();
+
+    net.setPreferableBackend(DNN_BACKEND_HALIDE);
+    net.setInput(input);
+    Mat outputHalide = net.forward().clone();
+    normAssert(outputDefault, outputHalide);
+
+    net.setPreferableTarget(DNN_TARGET_OPENCL);
+    net.setInput(input);
+    outputHalide = net.forward().clone();
+    normAssert(outputDefault, outputHalide);
+}
 #endif  // HAVE_HALIDE
 
 }  // namespace cvtest
index c1ac2ff..ada0986 100644 (file)
@@ -62,6 +62,7 @@ static void test(const std::string& weights, const std::string& proto,
     netHalide.setInput(blobFromImage(input.clone(), 1.0, Size(), Scalar(), false));
 
     normAssert(outputDefault, outputHalide, "Second run", l1, lInf);
+    std::cout << "." << std::endl;
 
     // Swap backends.
     netHalide.setPreferableBackend(DNN_BACKEND_DEFAULT);
@@ -79,6 +80,20 @@ static void test(const std::string& weights, const std::string& proto,
 ////////////////////////////////////////////////////////////////////////////////
 // CPU target
 ////////////////////////////////////////////////////////////////////////////////
+TEST(Reproducibility_MobileNetSSD_Halide, Accuracy)
+{
+    test(findDataFile("dnn/MobileNetSSD_deploy.caffemodel", false),
+         findDataFile("dnn/MobileNetSSD_deploy.prototxt", false),
+         "", 300, 300, "detection_out", "caffe", DNN_TARGET_CPU);
+};
+
+TEST(Reproducibility_SSD_Halide, Accuracy)
+{
+    test(findDataFile("dnn/VGG_ILSVRC2016_SSD_300x300_iter_440000.caffemodel", false),
+         findDataFile("dnn/ssd_vgg16.prototxt", false),
+         "", 300, 300, "detection_out", "caffe", DNN_TARGET_CPU);
+};
+
 TEST(Reproducibility_GoogLeNet_Halide, Accuracy)
 {
     test(findDataFile("dnn/bvlc_googlenet.caffemodel", false),
@@ -126,6 +141,20 @@ TEST(Reproducibility_ENet_Halide, Accuracy)
 ////////////////////////////////////////////////////////////////////////////////
 // OpenCL target
 ////////////////////////////////////////////////////////////////////////////////
+TEST(Reproducibility_MobileNetSSD_Halide_opencl, Accuracy)
+{
+    test(findDataFile("dnn/MobileNetSSD_deploy.caffemodel", false),
+         findDataFile("dnn/MobileNetSSD_deploy.prototxt", false),
+         "", 300, 300, "detection_out", "caffe", DNN_TARGET_OPENCL);
+};
+
+TEST(Reproducibility_SSD_Halide_opencl, Accuracy)
+{
+    test(findDataFile("dnn/VGG_ILSVRC2016_SSD_300x300_iter_440000.caffemodel", false),
+         findDataFile("dnn/ssd_vgg16.prototxt", false),
+         "", 300, 300, "detection_out", "caffe", DNN_TARGET_OPENCL);
+};
+
 TEST(Reproducibility_GoogLeNet_Halide_opencl, Accuracy)
 {
     test(findDataFile("dnn/bvlc_googlenet.caffemodel", false),