// 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. #include "precomp.hpp" #include #include "net_impl.hpp" namespace cv { namespace dnn { CV__DNN_INLINE_NS_BEGIN #ifdef HAVE_CANN static std::shared_ptr compileCannGraph(std::shared_ptr graph); class NetImplCann CV_FINAL : public Net::Impl { public: typedef Net::Impl Base; bool newWasSupported, netWasConverted; explicit NetImplCann(const Ptr& basePtr) : Net::Impl() { CV_LOG_INFO(NULL, "Initializing NetImplCann"); basePtr_ = basePtr; newWasSupported = true; netWasConverted = false; init(); CV_LOG_INFO(NULL, "Finished initializing NetImplCann"); } void init() { CV_TRACE_FUNCTION(); CV_Assert(basePtr_); Net::Impl& base = *basePtr_; CV_Assert(!base.netWasAllocated); CV_Assert(!base.netWasQuantized); // does not support quantized net for now netInputLayer = base.netInputLayer; blobsToKeep = base.blobsToKeep; layers = base.layers; for (MapIdToLayerData::iterator it = layers.begin(); it != layers.end(); it++) { LayerData& ld = it->second; ld.resetAllocation(); } layerNameToId = base.layerNameToId; outputNameToId = base.outputNameToId; preferableBackend = DNN_BACKEND_CANN; preferableTarget = DNN_TARGET_NPU; // force using NPU hasDynamicShapes = base.hasDynamicShapes; CV_Assert(base.backendWrappers.empty()); //backendWrappers = base.backendWrappers; lastLayerId = base.lastLayerId; netWasAllocated = base.netWasAllocated; netWasQuantized = base.netWasQuantized; fusion = base.fusion; } bool empty() const override { return Base::empty(); } void setPreferableBackend(Net& net, int backendId) override { if (backendId == preferableBackend) return; // no-op else CV_Error(Error::StsError, "DNN: Can't switch backend from CANN to other"); Ptr& impl_ptr_ref = accessor::DnnNetAccessor::getImplPtrRef(net); impl_ptr_ref = basePtr_; basePtr_->setPreferableBackend(net, backendId); } void setPreferableTarget(int targetId) override { if (targetId != preferableTarget) { CV_Error(Error::StsError, "DNN: Can't switch target from NPU to other"); } } Ptr wrap(Mat& host) override { return Ptr(new CannBackendWrapper(host)); } // void fuseLayers(const std::vector& blobsToKeep_); // fusion is done in the CANN graph engine void initBackend(const std::vector& blobsToKeep_) override; void forwardLayer(LayerData& ld) override; }; void NetImplCann::initBackend(const std::vector& blobsToKeep_) { CV_TRACE_FUNCTION(); CV_CheckEQ(preferableBackend, DNN_BACKEND_CANN, ""); // netWasAllocated turns to false if requested output is changed or input shape changes if (netWasConverted && netWasAllocated) return; if (!netWasConverted) { newWasSupported = true; for (MapIdToLayerData::iterator it = layers.begin(); it != layers.end(); ++it) { auto& ld = it->second; auto layer = ld.layerInstance; if (ld.id != 0 && !layer->supportBackend(preferableBackend)) { newWasSupported = false; CV_LOG_ONCE_WARNING(NULL, "DNN/CANN: layer (name=" << ld.name << ", type=" << ld.type << ") is not supported by CANN backend. Going back to default backend on CPU target"); } } } if (!newWasSupported) return ; // initialize each blob wrappers' names for (MapIdToLayerData::const_iterator it = layers.begin(); it != layers.end(); ++it) { const LayerData& ld = it->second; if (ld.id == 0) { for (int i = 0; i < ld.outputBlobsWrappers.size(); ++i) { auto cannWrapper = ld.outputBlobsWrappers[i].dynamicCast(); // cannWrapper->name = netInputLayer->outNames.empty() ? cv::format("%s_%d", ld.name.c_str(), i) : netInputLayer->outNames[i]; cannWrapper->name = std::string("y"); } } else { for (int i = 0; i < ld.outputBlobsWrappers.size(); ++i) { auto cannWrapper = ld.outputBlobsWrappers[i].dynamicCast(); // cannWrapper->name = ld.outputBlobsWrappers.size() > 1 ? (ld.name + ":" + std::to_string(i)) : ld.name; cannWrapper->name = ld.outputBlobsWrappers.size() > 1 ? (std::string("y") + std::to_string(i)) : std::string("y"); } } } // convert layers to CANN operators, // collect graph input and output operators, // collect and input and output wrappers int firstOutputLayerId = -1; std::vector > netInputNodes; std::vector graphInputOps, graphOutputOps; std::vector> graphInputWrappers, graphOutputWrappers; CV_LOG_INFO(NULL, "DNN/CANN: converting layers to CANN operators"); for (MapIdToLayerData::iterator it = layers.begin(); it != layers.end(); ++it) { LayerData& ld = it->second; auto layer = ld.layerInstance; if (ld.id == 0) { for (int i = 0; i < ld.outputBlobsWrappers.size(); i++) { // retrieve tensor description auto wrapper = ld.outputBlobsWrappers[i]; graphInputWrappers.push_back(wrapper); auto cannWrapper = wrapper.dynamicCast(); CV_Assert(!cannWrapper.empty()); // create graph input op std::string inputOpName = netInputLayer->outNames.empty() ? cv::format("%s_%d", ld.name.c_str(), i) : netInputLayer->outNames[i]; auto inputOp = std::make_shared(inputOpName); inputOp->update_input_desc_x(*(cannWrapper->desc_)); inputOp->update_output_desc_y(*(cannWrapper->desc_)); graphInputOps.push_back(*inputOp); netInputNodes.push_back(Ptr(new CannBackendNode(inputOp))); } } else { ld.skip = true; // skip all cann operators std::vector > layerInputNodes; for (int i = 0; i < ld.inputBlobsId.size(); i++) { int layerInputLid = ld.inputBlobsId[i].lid; int layerInputOid = ld.inputBlobsId[i].oid; if (layerInputLid == 0) { layerInputNodes.push_back(netInputNodes[layerInputOid]); } else { layerInputNodes.push_back(layers[layerInputLid].backendNodes[preferableBackend]); } } CV_LOG_INFO(NULL, "DNN/CANN: converting layer " << ld.name << "@" << ld.type << "@" << ld.id << " to CANN operator"); auto backendNode = layer->initCann(ld.inputBlobsWrappers, ld.outputBlobsWrappers, layerInputNodes); // it's ok if ld.name is empty // collect outputs bool isOutputNode = ld.consumers.size() == 0 ? true : false; if (isOutputNode) { if (firstOutputLayerId < 0) firstOutputLayerId = ld.id; auto cannNode = backendNode.dynamicCast(); graphOutputOps.push_back(*(cannNode->getOp())); // assume cann graph outputs and dnn net outputs have the same order for (int i = 0; i < ld.outputBlobsWrappers.size(); ++i) { graphOutputWrappers.push_back(ld.outputBlobsWrappers[i]); } } ld.backendNodes[preferableBackend] = backendNode; } } CV_LOG_INFO(NULL, "DNN/CANN: done converting layers to CANN operators"); // build graph from collected graph inputs and outputs CV_LOG_INFO(NULL, "DNN/CANN: building ge::Graph"); std::string graphName = cv::format("graph_%d", networkId); std::shared_ptr graph = std::make_shared(graphName.c_str()); (void)graph->SetInputs(graphInputOps); (void)graph->SetOutputs(graphOutputOps); CV_LOG_INFO(NULL, "DNN/CANN: done building ge::Graph"); // convert ge::Graph to OM buffer CV_LOG_INFO(NULL, "DNN/CANN: converting ge::Graph to OM buffer"); std::shared_ptr modelBuffer = compileCannGraph(graph); CV_LOG_INFO(NULL, "DNN/CANN: OM buffer size = " << modelBuffer->length); CV_LOG_INFO(NULL, "DNN/CANN: done building ge::Graph to OM buffer"); // keep net in the first output node and mark the node runnable auto& ld = layers[firstOutputLayerId]; auto cannNode = ld.backendNodes[preferableBackend].dynamicCast(); std::shared_ptr net = std::shared_ptr(new CannNet()); net->loadModelBuffer(modelBuffer); net->bindInputWrappers(graphInputWrappers); net->bindOutputWrappers(graphOutputWrappers); cannNode->net = net; ld.skip = false; netWasConverted = true; } void NetImplCann::forwardLayer(LayerData& ld) { CV_TRACE_FUNCTION(); auto layer = ld.layerInstance; if (!ld.skip) { auto it = ld.backendNodes.find(preferableBackend); if (ld.id == 0 || it == ld.backendNodes.end()) // input layer { return Base::forwardLayer(ld); } CV_Assert(it != ld.backendNodes.end()); const Ptr& node = it->second; CV_Assert(!node.empty()); auto cannNode = node.dynamicCast(); CV_Assert(!cannNode.empty()); CV_Assert(cannNode->net); TickMeter tm; tm.start(); cannNode->net->forward(); tm.stop(); int64_t t = tm.getTimeTicks(); layersTimings[ld.id] = (t > 0) ? t : 1; } else { layersTimings[ld.id] = 0; } ld.flag = 1; } std::shared_ptr compileCannGraph(std::shared_ptr graph) { const size_t hdrsize = 32; std::shared_ptr out_buffer = std::make_shared(); size_t buf_size = (1 << 27), model_size; // default buf_size 128 MB for (int iter = 0; iter < 2; ++iter) { size_t* shared_buf = (size_t*)mmap(NULL, buf_size + hdrsize, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, -1, 0); uint8_t* model_data = (uint8_t*)(shared_buf + 1); pid_t child; int childstate = 0; bool ok; if ((child=fork()) == 0) { // initialize engine Ascend310/Ascend310P3/Ascend910B/Ascend310B std::map options = { {ge::AscendString(ge::ir_option::SOC_VERSION), ge::AscendString(aclrtGetSocName())}, }; ACL_CHECK_GRAPH_RET(ge::aclgrphBuildInitialize(options)); // build std::shared_ptr om_model = std::make_shared(); std::map build_options; ACL_CHECK_GRAPH_RET(aclgrphBuildModel(*graph, build_options, *om_model)); #if 0 // (optional). Dump model ge::AscendString graph_name; graph->GetName(graph_name); aclgrphDumpGraph(*graph, graph_name.GetString(), 7); // (optional). Save model aclgrphSaveModel(graph_name.GetString(), *om_model); #endif // finalize engine ge::aclgrphBuildFinalize(); // send model from child to parent size_t model_size = om_model->length; *shared_buf = model_size; if (model_size > buf_size) { exit(1); } else { memcpy(model_data, om_model->data.get(), model_size); exit(0); } } waitpid (child, &childstate, 0); model_size = *shared_buf; ok = WIFEXITED(childstate) && WEXITSTATUS(childstate) == 0; if (ok) { CV_LOG_INFO(NULL, "Compile success, model size = " << model_size); out_buffer->data = std::shared_ptr(new uint8_t[model_size]); memcpy(out_buffer->data.get(), model_data, model_size); out_buffer->length = model_size; } munmap(shared_buf, buf_size + hdrsize); if (ok) break; buf_size = model_size; } return out_buffer; } void switchToCannBackend(Net& net) { CV_TRACE_FUNCTION(); Ptr& impl_ptr_ref = accessor::DnnNetAccessor::getImplPtrRef(net); CV_Assert(impl_ptr_ref); CV_LOG_INFO(NULL, "DNN: switching to CANN backend... (networkID=" << impl_ptr_ref->networkId << ")"); Ptr impl_ptr_cann = makePtr(impl_ptr_ref); impl_ptr_ref = impl_ptr_cann; } #endif // HAVE_CANN CV__DNN_INLINE_NS_END }} // namespace cv::dnn