// 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 "op_webnn.hpp" #include #include #include "opencv2/core/utils/filesystem.hpp" #include "opencv2/core/utils/filesystem.private.hpp" #include #include "net_impl.hpp" namespace cv { namespace dnn { #ifdef HAVE_WEBNN CV__DNN_INLINE_NS_BEGIN void Net::Impl::addWebnnOutputs(LayerData &ld) { CV_TRACE_FUNCTION(); Ptr layerNet; auto it = ld.backendNodes.find(preferableBackend); if (it != ld.backendNodes.end()) { Ptr node = it->second; if (!node.empty()) { Ptr webnnNode = node.dynamicCast(); CV_Assert(!webnnNode.empty()); CV_Assert(!webnnNode->net.empty()); layerNet = webnnNode->net; } } for (int i = 0; i < ld.inputBlobsId.size(); ++i) { LayerData &inpLd = layers[ld.inputBlobsId[i].lid]; Ptr inpNode = inpLd.backendNodes[preferableBackend]; if (!inpNode.empty()) { Ptr webnnInpNode = inpNode.dynamicCast(); CV_Assert(!webnnInpNode.empty()); CV_Assert(!webnnInpNode->net.empty()); if (layerNet != webnnInpNode->net) { webnnInpNode->net->addOutput(webnnInpNode->name); webnnInpNode->net->setUnconnectedNodes(webnnInpNode); } } } } void Net::Impl::initWebnnBackend(const std::vector& blobsToKeep_) { CV_TRACE_FUNCTION(); CV_Assert_N(preferableBackend == DNN_BACKEND_WEBNN, haveWebnn()); Ptr net; for (MapIdToLayerData::iterator it = layers.begin(); it != layers.end(); ++it) { LayerData &ld = it->second; if (ld.id == 0) { CV_Assert((netInputLayer->outNames.empty() && ld.outputBlobsWrappers.size() == 1) || (netInputLayer->outNames.size() == ld.outputBlobsWrappers.size())); for (int i = 0; i < ld.outputBlobsWrappers.size(); ++i) { Ptr wrapper = ld.outputBlobsWrappers[i].dynamicCast(); std::string outputName = netInputLayer->outNames.empty() ? ld.name : netInputLayer->outNames[i]; outputName = ld.outputBlobsWrappers.size() > 1 ? (outputName + "." + std::to_string(i)) : outputName; wrapper->name = outputName; } } else { for (int i = 0; i < ld.outputBlobsWrappers.size(); ++i) { Ptr wrapper = ld.outputBlobsWrappers[i].dynamicCast(); std::string outputName = ld.outputBlobsWrappers.size() > 1 ? (ld.name + "." + std::to_string(i)) : ld.name; wrapper->name = outputName; } } } // Build WebNN networks from sets of layers that support this // backend. Split a whole model on several WebNN networks if // some of layers are not implemented. for (MapIdToLayerData::iterator it = layers.begin(); it != layers.end(); ++it) { LayerData &ld = it->second; if (ld.id == 0 && ld.skip) continue; bool fused = ld.skip; Ptr layer = ld.layerInstance; if (!fused && !layer->supportBackend(preferableBackend)) { // For test use. when not using WebNN, the test case will fail // with the following code. CV_LOG_WARNING(NULL, "Layer " + ld.type + " name " + ld.name + " is unsupported by WebNN backend."); addWebnnOutputs(ld); net = Ptr(); layer->preferableTarget = DNN_TARGET_CPU; for (int i = 0; i < ld.inputBlobsId.size(); ++i) { LayerData &inpLd = layers[ld.inputBlobsId[i].lid]; Ptr inpNode = inpLd.backendNodes[preferableBackend]; if (!inpNode.empty()) { Ptr webnnNode = inpNode.dynamicCast(); CV_Assert(!webnnNode.empty()); webnnNode->net->setUnconnectedNodes(webnnNode); } } continue; } ld.skip = true; // Initially skip all WebNN supported layers. // Create a new network if one of inputs from different WebNN graph. std::vector> inputNodes; for (int i = 0; i < ld.inputBlobsId.size(); ++i) { // Layer_Test_ROIPooling.Accuracy has 2 inputs inpLD = 0, 0 -> has 4 inputNodes (input, rois, input, rois) if (inputNodes.size() == ld.inputBlobsId.size()) { break; } LayerData &inpLd = layers[ld.inputBlobsId[i].lid]; Ptr inpNode = inpLd.backendNodes[preferableBackend]; if (!inpNode.empty()) { Ptr webnnInpNode = inpNode.dynamicCast(); CV_Assert(!webnnInpNode.empty()); CV_Assert(!webnnInpNode->net.empty()); if (webnnInpNode->net == net && !fused) { inputNodes.push_back(inpNode); continue; } } if (net.empty()) { net = Ptr(new WebnnNet()); } if (!fused) { std::vector inputNames; std::vector inputs; auto curr_pos = inpLd.consumers.begin(); auto compare = [&ld] (const LayerPin& lp) { return lp.lid == ld.id; }; auto cons = curr_pos; while ((cons = std::find_if(curr_pos, inpLd.consumers.end(), compare)) != inpLd.consumers.end()) { int cons_inp = cons->oid; Ptr inpWrapper = inpLd.outputBlobsWrappers[cons_inp]. dynamicCast(); CV_Assert(!inpWrapper.empty()); auto iter = std::find(inputNames.begin(), inputNames.end(), inpWrapper->name); if (iter == inputNames.end()) { inputNames.push_back(inpWrapper->name); inputs.push_back(inpLd.outputBlobs[cons_inp]); } curr_pos = cons + 1; } auto inps = net->setInputs(inputs, inputNames); for (auto& inp : inps) { WebnnBackendNode* node = new WebnnBackendNode(inp); node->net = net; inputNodes.emplace_back(Ptr(node)); } } } Ptr node; if (!net.empty()) { if (fused) { bool inPlace = ld.inputBlobsId.size() == 1 && ld.outputBlobs.size() == 1 && ld.inputBlobs[0]->data == ld.outputBlobs[0].data; CV_Assert(inPlace); node = layers[ld.inputBlobsId[0].lid].backendNodes[preferableBackend]; ld.inputBlobsWrappers = layers[ld.inputBlobsId[0].lid].inputBlobsWrappers; } } else { net = Ptr(new WebnnNet()); } if (!fused) { CV_Assert(ld.inputBlobsId.size() == inputNodes.size()); for (int i = 0; i < ld.inputBlobsId.size(); ++i) { int lid = ld.inputBlobsId[i].lid; int oid = ld.inputBlobsId[i].oid; if (oid == 0 || lid == 0) continue; auto webnnInpNode = inputNodes[i].dynamicCast(); inputNodes[i] = Ptr(new WebnnBackendNode(webnnInpNode->operand)); } if (layer->supportBackend(preferableBackend)) { if (ld.type == "Const") { ml::Operand fake_operand; Ptr fake_input_node = Ptr(new WebnnBackendNode(fake_operand)); fake_input_node->net = net; inputNodes.push_back(fake_input_node); } node = layer->initWebnn(ld.inputBlobsWrappers, inputNodes); for (int i = 0; i < ld.outputBlobsWrappers.size(); ++i) { Ptr wrapper = ld.outputBlobsWrappers[i].dynamicCast(); node.dynamicCast()->name = wrapper->name; } } else { continue; } } else if (node.empty()) continue; ld.backendNodes[preferableBackend] = node; Ptr webnnNode = node.dynamicCast(); CV_Assert(!webnnNode.empty()); webnnNode->net = net; if (ld.consumers.empty()) { // TF EAST_text_detection webnnNode->net->setUnconnectedNodes(webnnNode); } for (const auto& pin : blobsToKeep_) { if (pin.lid == ld.id) { webnnNode->net->addOutput(webnnNode->name); break; } } net->addBlobs(ld.inputBlobsWrappers); net->addBlobs(ld.outputBlobsWrappers); addWebnnOutputs(ld); } // Initialize all networks. for (MapIdToLayerData::reverse_iterator it = layers.rbegin(); it != layers.rend(); ++it) { LayerData &ld = it->second; auto iter = ld.backendNodes.find(preferableBackend); if (iter == ld.backendNodes.end()) continue; Ptr& node = iter->second; if (node.empty()) continue; Ptr webnnNode = node.dynamicCast(); if (webnnNode.empty()) continue; CV_Assert(!webnnNode->net.empty()); if (!webnnNode->net->isInitialized()) { webnnNode->net->setUnconnectedNodes(webnnNode); webnnNode->net->createNet((Target)preferableTarget); ld.skip = false; } } } CV__DNN_INLINE_NS_END namespace webnn { ml::Operand BuildConstant(const ml::GraphBuilder& builder, const std::vector& dimensions, const void* value, size_t size, ml::OperandType type) { ml::OperandDescriptor desc; desc.type = type; desc.dimensions = dimensions.data(); desc.dimensionsCount = (uint32_t)dimensions.size(); ml::ArrayBufferView resource; resource.buffer = const_cast(value); resource.byteLength = size; return builder.Constant(&desc, &resource); } } static std::string kDefaultInpLayerName = "opencv_webnn_empty_inp_layer_name"; static std::vector > webnnWrappers(const std::vector >& ptrs) { std::vector > wrappers(ptrs.size()); for (int i = 0; i < ptrs.size(); ++i) { CV_Assert(!ptrs[i].empty()); wrappers[i] = ptrs[i].dynamicCast(); CV_Assert(!wrappers[i].empty()); } return wrappers; } // WebnnNet WebnnNet::WebnnNet() { hasNetOwner = false; device_name = "CPU"; #ifdef __EMSCRIPTEN__ context = ml::Context(emscripten_webnn_create_context()); #else WebnnProcTable backendProcs = webnn_native::GetProcs(); webnnProcSetProcs(&backendProcs); context = ml::Context(webnn_native::CreateContext()); #endif builder = ::ml::CreateGraphBuilder(context); namedOperands = ::ml::CreateNamedOperands(); } void WebnnNet::addOutput(const std::string& name) { requestedOutputs.push_back(name); } void WebnnNet::createNet(Target targetId) { init(targetId); } void WebnnNet::init(Target targetId) { switch (targetId) { case DNN_TARGET_CPU: device_name = "CPU"; break; case DNN_TARGET_OPENCL: device_name = "GPU"; break; default: CV_Error(Error::StsNotImplemented, "Unknown target"); }; graph = builder.Build(namedOperands); CV_Assert(graph!=nullptr); isInit = true; } std::vector WebnnNet::setInputs(const std::vector& inputs, const std::vector& names) { CV_Assert_N(inputs.size() == names.size()); std::vector current_inp; for (size_t i = 0; i < inputs.size(); i++) { auto& m = inputs[i]; std::vector dimensions = webnn::getShape(m); ml::OperandDescriptor descriptor; descriptor.dimensions = dimensions.data(); descriptor.dimensionsCount = dimensions.size(); if (m.type() == CV_32F) { descriptor.type = ml::OperandType::Float32; } else { CV_Error(Error::StsNotImplemented, format("Unsupported data type %s", typeToString(m.type()).c_str())); } ml::Operand inputOperand = builder.Input(names[i].c_str(), &descriptor); current_inp.push_back(std::move(inputOperand)); } inputNames = names; return current_inp; } void WebnnNet::setUnconnectedNodes(Ptr& node) { outputNames.push_back(node->name); namedOperands.Set(outputNames.back().c_str(), node->operand); } bool WebnnNet::isInitialized() { return isInit; } void WebnnNet::reset() { allBlobs.clear(); isInit = false; } void WebnnNet::addBlobs(const std::vector >& ptrs) { auto wrappers = webnnWrappers(ptrs); for (const auto& wrapper : wrappers) { std::string name = wrapper->name; name = name.empty() ? kDefaultInpLayerName : name; allBlobs.insert({name, wrapper}); } } void WebnnNet::forward(const std::vector >& outBlobsWrappers, bool isAsync) { CV_LOG_DEBUG(NULL, "WebnnNet::forward(" << (isAsync ? "async" : "sync") << ")"); ml::NamedInputs named_inputs = ::ml::CreateNamedInputs(); std::vector inputs(inputNames.size()); for (int i = 0; i < inputNames.size(); ++i) { const std::string& name = inputNames[i]; ml::Input& input = inputs[i]; auto blobIt = allBlobs.find(name); CV_Assert(blobIt != allBlobs.end()); const Ptr wrapper = blobIt->second; input.resource.buffer = wrapper->host->data; input.resource.byteLength = wrapper->size; named_inputs.Set(name.c_str(), &input); } std::vector > outs = webnnWrappers(outBlobsWrappers); ml::NamedOutputs named_outputs = ::ml::CreateNamedOutputs(); std::vector outputs(outs.size()); for (int i = 0; i < outs.size(); ++i) { const std::string& name = outs[i]->name; ml::ArrayBufferView& output = outputs[i]; output.buffer = outs[i]->host->data; // std::cout<<"host data size: "<host->total()*outs[i]->host->elemSize()<size; // std::cout<<"outs[i]->size: "<< outs[i]->size << std::endl; named_outputs.Set(name.c_str(), &output); } ml::ComputeGraphStatus status = graph.Compute(named_inputs, named_outputs); if (status != ::ml::ComputeGraphStatus::Success) { CV_Error(Error::StsAssert, format("Failed to compute: %d", int(status))); } } // WebnnBackendNode WebnnBackendNode::WebnnBackendNode(ml::Operand&& _operand) : BackendNode(DNN_BACKEND_WEBNN), operand(std::move(_operand)) {} WebnnBackendNode::WebnnBackendNode(ml::Operand& _operand) : BackendNode(DNN_BACKEND_WEBNN), operand(_operand) {} // WebnnBackendWrapper WebnnBackendWrapper::WebnnBackendWrapper(int targetId, cv::Mat& m) : BackendWrapper(DNN_BACKEND_WEBNN, targetId) { size = m.total() * m.elemSize(); // buffer.reset(new char[size]); // std::memcpy(buffer.get(), m.data, size); // dimensions = getShape(m); // descriptor.dimensions = dimensions.data(); // descriptor.dimensionsCount = dimensions.size(); if (m.type() == CV_32F) { descriptor.type = ml::OperandType::Float32; } else { CV_Error(Error::StsNotImplemented, format("Unsupported data type %s", typeToString(m.type()).c_str())); } host = &m; } WebnnBackendWrapper::~WebnnBackendWrapper() { // nothing } void WebnnBackendWrapper::copyToHost() { CV_LOG_DEBUG(NULL, "WebnnBackendWrapper::copyToHost()"); //CV_Error(Error::StsNotImplemented, ""); } void WebnnBackendWrapper::setHostDirty() { CV_LOG_DEBUG(NULL, "WebnnBackendWrapper::setHostDirty()"); //CV_Error(Error::StsNotImplemented, ""); } void forwardWebnn(const std::vector >& outBlobsWrappers, Ptr& node, bool isAsync) { CV_Assert(!node.empty()); Ptr webnnNode = node.dynamicCast(); CV_Assert(!webnnNode.empty()); webnnNode->net->forward(outBlobsWrappers, isAsync); } #else void forwardWebnn(const std::vector >& outBlobsWrappers, Ptr& operand, bool isAsync) { CV_Assert(false && "WebNN is not enabled in this OpenCV build"); } #endif } }