diff --git a/modules/dnn/src/onnx/onnx_graph_simplifier.hpp b/modules/dnn/src/onnx/onnx_graph_simplifier.hpp index a338e13796..ac2bb295a9 100644 --- a/modules/dnn/src/onnx/onnx_graph_simplifier.hpp +++ b/modules/dnn/src/onnx/onnx_graph_simplifier.hpp @@ -42,6 +42,15 @@ Mat getMatFromTensor(const opencv_onnx::TensorProto& tensor_proto, bool uint8ToI // or returns -1 when the data_type has no OpenCV equivalent. int dataType2cv(int dt); +/** @brief frees a tensor's raw payload once it has been copied into a Mat. + * clear_raw_data() only empties the string and keeps its capacity, so it must be released. + */ +inline void releaseONNXTensor(opencv_onnx::TensorProto& tensor_proto) +{ + if (!tensor_proto.raw_data().empty()) + delete tensor_proto.release_raw_data(); +} + CV__DNN_INLINE_NS_END }} // namespace dnn, namespace cv diff --git a/modules/dnn/src/onnx/onnx_importer.cpp b/modules/dnn/src/onnx/onnx_importer.cpp index e009a3b30b..3720230aa8 100644 --- a/modules/dnn/src/onnx/onnx_importer.cpp +++ b/modules/dnn/src/onnx/onnx_importer.cpp @@ -137,13 +137,6 @@ static int onnxDataTypeToCvDepth(int onnxType) } } -static void releaseONNXTensor(opencv_onnx::TensorProto& tensor_proto) -{ - if (!tensor_proto.raw_data().empty()) { - delete tensor_proto.release_raw_data(); - } -} - Mat readTensorFromONNX(const String& path) { std::fstream input(path.c_str(), std::ios::in | std::ios::binary); diff --git a/modules/dnn/src/onnx/onnx_importer2.cpp b/modules/dnn/src/onnx/onnx_importer2.cpp index 883e2275be..0d21b7390a 100644 --- a/modules/dnn/src/onnx/onnx_importer2.cpp +++ b/modules/dnn/src/onnx/onnx_importer2.cpp @@ -534,7 +534,7 @@ LayerParams ONNXImporter2::getLayerParams(const opencv_onnx::NodeProto& node_pro } else if (attribute_proto.has_t()) { - opencv_onnx::TensorProto tensor = attribute_proto.t(); + const opencv_onnx::TensorProto& tensor = attribute_proto.t(); Mat blob = parseTensor(tensor); lp.blobs.push_back(blob); lp.set("original_dims_of_mat", tensor.dims_size()); @@ -888,9 +888,10 @@ Ptr ONNXImporter2::parseGraph(opencv_onnx::GraphProto* graph_proto, bool // parse constant tensors int n_consts = graph_proto->initializer_size(); for (int i = 0; i < n_consts; i++) { - const opencv_onnx::TensorProto& const_i = graph_proto->initializer(i); - Mat t = parseTensor(const_i); - netimpl->newConstArg(remap(const_i.name()), t); + opencv_onnx::TensorProto* const_i = graph_proto->mutable_initializer(i); + Mat t = parseTensor(*const_i); + netimpl->newConstArg(remap(const_i->name()), t); + releaseONNXTensor(*const_i); } // parse graph inputs diff --git a/modules/dnn/test/test_backends.cpp b/modules/dnn/test/test_backends.cpp index 238374cad5..9f94206eea 100644 --- a/modules/dnn/test/test_backends.cpp +++ b/modules/dnn/test/test_backends.cpp @@ -37,17 +37,35 @@ public: if (!proto.empty()) proto = findDataFile(proto); - // Create two networks - with default backend and target and a tested one. - Net netDefault = readNet(weights, proto); - netDefault.setPreferableBackend(DNN_BACKEND_OPENCV); - netDefault.setInput(inp); + Mat inp2 = inp.clone(); + float* inpData = (float*)inp2.data; + for (int i = 0; i < inp2.size[0] * inp2.size[1]; ++i) + { + Mat slice(inp2.size[2], inp2.size[3], CV_32F, inpData); + cv::flip(slice, slice, 1); + inpData += slice.total(); + } - // BUG: https://github.com/opencv/opencv/issues/26349 - Mat outDefault; - if(netDefault.getMainGraph()) - outDefault = netDefault.forward().clone(); - else - outDefault = netDefault.forward(outputLayer).clone(); + // Both reference passes run before the tested net is loaded, so netDefault can be + // released first. Keeping both alive doubles peak memory on large models. + Mat outDefault1, outDefault2; + { + Net netDefault = readNet(weights, proto); + netDefault.setPreferableBackend(DNN_BACKEND_OPENCV); + + // BUG: https://github.com/opencv/opencv/issues/26349 + netDefault.setInput(inp); + if(netDefault.getMainGraph()) + outDefault1 = netDefault.forward().clone(); + else + outDefault1 = netDefault.forward(outputLayer).clone(); + + netDefault.setInput(inp2); + if(netDefault.getMainGraph()) + outDefault2 = netDefault.forward().clone(); + else + outDefault2 = netDefault.forward(outputLayer).clone(); + } net = readNet(weights, proto); net.setInput(inp); @@ -64,30 +82,15 @@ public: else out = net.forward(outputLayer).clone(); - check(outDefault, out, outputLayer, l1, lInf, detectionConfThresh, "First run"); - - // Test 2: change input. - float* inpData = (float*)inp.data; - for (int i = 0; i < inp.size[0] * inp.size[1]; ++i) - { - Mat slice(inp.size[2], inp.size[3], CV_32F, inpData); - cv::flip(slice, slice, 1); - inpData += slice.total(); - } - netDefault.setInput(inp); - net.setInput(inp); - - if(netDefault.getMainGraph()) - outDefault = netDefault.forward().clone(); - else - outDefault = netDefault.forward(outputLayer).clone(); + check(outDefault1, out, outputLayer, l1, lInf, detectionConfThresh, "First run"); + net.setInput(inp2); if(net.getMainGraph()) out = net.forward().clone(); else out = net.forward(outputLayer).clone(); - check(outDefault, out, outputLayer, l1, lInf, detectionConfThresh, "Second run"); + check(outDefault2, out, outputLayer, l1, lInf, detectionConfThresh, "Second run"); } void check(Mat& ref, Mat& out, const std::string& outputLayer, double l1, double lInf, @@ -124,12 +127,6 @@ TEST_P(DNNTestNetwork, YOLOv8n) { TEST_P(DNNTestNetwork, AlexNet) { applyTestTag(CV_TEST_TAG_MEMORY_1GB); - // fc6 needs one contiguous 9216*4096*4 = 144 MiB block, which does not fit - // reliably in the 2 GB user address space of a 32-bit process on Windows. -#ifdef _WIN32 - if (sizeof(void*) == 4) - throw SkipTestException("Skip memory-heavy network on 32-bit (x86) Windows platform"); -#endif processNet("dnn/onnx/models/alexnet.onnx", "", Size(227, 227)); expectNoFallbacksFromIE(net); expectNoFallbacksFromCUDA(net);