| #include <limits> |
| #include <memory> |
| #include <utility> |
| |
| #include "caffe2/core/logging.h" |
| #include "caffe2/opt/converter.h" |
| |
| #include "nomnigraph/Graph/Algorithms.h" |
| |
| #include "nomnigraph/Support/Casting.h" |
| |
| using namespace nom; |
| |
| namespace { |
| |
| std::vector<int> getStrides(std::map<std::string, caffe2::Argument> argMap) { |
| std::vector<int> strides; |
| // TODO: include all the other ways of adding these args. |
| // e.g. strides, stride_h, etc. |
| if (argMap.count("stride")) { |
| CAFFE_ENFORCE(argMap["stride"].has_i(), "Invalid stride argument"); |
| int stride = static_cast<int>(argMap["stride"].i()); |
| strides = {stride, stride}; |
| } |
| return strides; |
| } |
| |
| std::vector<int> getPads(std::map<std::string, caffe2::Argument> argMap) { |
| std::vector<int> pads; |
| if (argMap.count("pad")) { |
| CAFFE_ENFORCE(argMap["pad"].has_i(), "Invalid pad argument"); |
| int pad = static_cast<int>(argMap["pad"].i()); |
| pads = {pad, pad, pad, pad}; |
| } |
| return pads; |
| } |
| |
| std::vector<int> getDilations(std::map<std::string, caffe2::Argument> argMap) { |
| std::vector<int> dilations; |
| if (argMap.count("dilation")) { |
| CAFFE_ENFORCE(argMap["dilation"].has_i(), "Invalid dilation argument"); |
| int dilation = static_cast<int>(argMap["dilation"].i()); |
| dilations = {dilation, dilation}; |
| } |
| return dilations; |
| } |
| |
| int getGroup(std::map<std::string, caffe2::Argument>& argMap) { |
| if (argMap.count("group")) { |
| CAFFE_ENFORCE(argMap["group"].has_i() && "Invalid group argument"); |
| return static_cast<int>(argMap["group"].i()); |
| } |
| return 1; |
| } |
| |
| } // namespace |
| |
| namespace caffe2 { |
| |
| C10_DEFINE_REGISTRY(ConverterRegistry, Converter); |
| |
| std::map<std::string, caffe2::Argument> Converter::getArgumentsFromOperator( |
| caffe2::OperatorDef op) { |
| std::map<std::string, caffe2::Argument> argMap; |
| // NOLINTNEXTLINE(performance-for-range-copy) |
| for (auto arg : op.arg()) { |
| argMap[arg.name()] = arg; |
| } |
| return argMap; |
| } |
| |
| repr::NeuralNetOperator::NNLayout getLayout( |
| std::map<std::string, caffe2::Argument> argMap) { |
| auto arg = argMap.find("order"); |
| if (arg != argMap.end()) { |
| auto order = argMap["order"].s(); |
| if (order == "NCHW" || order == "nchw") { |
| return repr::NeuralNetOperator::NNLayout::NCHW; |
| } else if (order == "NHWC" || order == "nhwc") { |
| return repr::NeuralNetOperator::NNLayout::NHWC; |
| } |
| } |
| return repr::NeuralNetOperator::NNLayout::Undefined; |
| } |
| |
| OperatorDef Converter::convertToOperatorDef( |
| const nom::repr::NeuralNetOperator* nnOp) { |
| auto* annotation = nnOp->getAnnotation(); |
| // Default to using the stored operator. |
| if (annotation && isa<Caffe2Annotation>(annotation)) { |
| return dyn_cast<Caffe2Annotation>(annotation)->getOperatorDef(); |
| } |
| LOG(WARNING) |
| << "Cannot instantiate this OperatorDef from nomnigraph, falling back"; |
| caffe2::OperatorDef op; |
| op.set_type(nnOp->getName()); |
| return op; |
| } |
| |
| DeviceOption Converter::getDeviceOption( |
| const nom::repr::NeuralNetOperator* nnOp) const { |
| auto* annotation = nnOp->getAnnotation(); |
| // Default to using the stored operator. |
| if (annotation && isa<Caffe2Annotation>(annotation)) { |
| return dyn_cast<Caffe2Annotation>(annotation) |
| ->getOperatorDef() |
| .device_option(); |
| } |
| caffe2::DeviceOption opt; |
| return opt; |
| } |
| |
| std::vector<int> getKernelShape( |
| std::map<std::string, caffe2::Argument> argMap) { |
| // There are literally three ways to define shapes in Conv in Caffe2 |
| std::vector<int> kernelShape; |
| if (argMap.count("kernel")) { |
| CAFFE_ENFORCE(argMap["kernel"].has_i(), "Invalid kernel argument"); |
| int kernel = static_cast<int>(argMap["kernel"].i()); |
| kernelShape = {kernel, kernel}; |
| } else if (argMap.count("kernels")) { |
| for (auto i : argMap["kernels"].ints()) { |
| kernelShape.push_back(static_cast<int>(i)); |
| } |
| } else if (argMap.count("kernel_h") && argMap.count("kernel_w")) { |
| CAFFE_ENFORCE(argMap["kernel_h"].has_i(), "Invalid kernel argument"); |
| CAFFE_ENFORCE(argMap["kernel_w"].has_i(), "Invalid kernel argument"); |
| int kernelH = static_cast<int>(argMap["kernel_h"].i()); |
| int kernelW = static_cast<int>(argMap["kernel_w"].i()); |
| kernelShape = {kernelH, kernelW}; |
| } |
| return kernelShape; |
| } |
| |
| namespace { |
| |
| class ConvConverter : public Converter { |
| std::unique_ptr<nom::repr::NeuralNetOperator> convertToNeuralNetOperator( |
| const OperatorDef& op) override { |
| std::unique_ptr<repr::NeuralNetOperator> nnOp; |
| auto argMap = getArgumentsFromOperator(op); |
| auto kernelShape = getKernelShape(argMap); |
| nnOp = std::make_unique<repr::Conv>(kernelShape); |
| auto c = dyn_cast<repr::Conv>(nnOp.get()); |
| |
| c->setStrides(getStrides(argMap)); |
| c->setPads(getPads(argMap)); |
| c->setDilations(getDilations(argMap)); |
| c->setGroup(getGroup(argMap)); |
| |
| return nnOp; |
| } |
| // Does not override default converter to OperatorDef |
| |
| // NOLINTNEXTLINE(modernize-use-equals-default) |
| ~ConvConverter() override {} |
| }; |
| |
| class ConvTransposeConverter : public Converter { |
| std::unique_ptr<nom::repr::NeuralNetOperator> convertToNeuralNetOperator( |
| const OperatorDef& op) override { |
| std::unique_ptr<repr::NeuralNetOperator> nnOp; |
| auto argMap = getArgumentsFromOperator(op); |
| auto kernelShape = getKernelShape(argMap); |
| nnOp = std::make_unique<repr::ConvTranspose>(kernelShape); |
| auto c = dyn_cast<repr::ConvTranspose>(nnOp.get()); |
| |
| c->setStrides(getStrides(argMap)); |
| c->setPads(getPads(argMap)); |
| c->setGroup(getGroup(argMap)); |
| |
| return nnOp; |
| } |
| // Does not override default converter to OperatorDef |
| |
| // NOLINTNEXTLINE(modernize-use-override,modernize-use-equals-default) |
| virtual ~ConvTransposeConverter() {} |
| }; |
| |
| REGISTER_CONVERTER(Conv, ConvConverter); |
| |
| REGISTER_CONVERTER(ConvTranspose, ConvTransposeConverter); |
| |
| TRIVIAL_CONVERTER(Relu); |
| REGISTER_CONVERTER(Relu, ReluConverter); |
| |
| TRIVIAL_CONVERTER(Sum); |
| REGISTER_CONVERTER(Sum, SumConverter); |
| |
| TRIVIAL_CONVERTER(BatchNormalization); |
| REGISTER_CONVERTER(SpatialBN, BatchNormalizationConverter); |
| |
| TRIVIAL_CONVERTER(Flatten); |
| REGISTER_CONVERTER(Flatten, FlattenConverter); |
| |
| class ClipConverter : public Converter { |
| std::unique_ptr<nom::repr::NeuralNetOperator> convertToNeuralNetOperator( |
| const OperatorDef& op) override { |
| auto argMap = getArgumentsFromOperator(op); |
| float min = std::numeric_limits<float>::lowest(); |
| float max = std::numeric_limits<float>::max(); |
| |
| if (argMap.count("min")) { |
| CAFFE_ENFORCE(argMap["min"].has_f(), "Invalid 'min' argument"); |
| min = static_cast<float>(argMap["min"].f()); |
| } |
| |
| if (argMap.count("max")) { |
| CAFFE_ENFORCE(argMap["max"].has_f(), "Invalid 'max' argument"); |
| max = static_cast<float>(argMap["max"].f()); |
| } |
| |
| return std::make_unique<repr::Clip>(min, max); |
| } |
| // Does not override default converter to OperatorDef |
| |
| // NOLINTNEXTLINE(modernize-use-equals-default) |
| ~ClipConverter() override {} |
| }; |
| REGISTER_CONVERTER(Clip, ClipConverter); |
| |
| class AveragePoolConverter : public Converter { |
| std::unique_ptr<nom::repr::NeuralNetOperator> convertToNeuralNetOperator( |
| const OperatorDef& op) override { |
| std::unique_ptr<repr::NeuralNetOperator> nnOp; |
| auto argMap = getArgumentsFromOperator(op); |
| auto kernelShape = getKernelShape(argMap); |
| nnOp = std::make_unique<repr::AveragePool>(kernelShape); |
| return nnOp; |
| } |
| // Does not override default converter to OperatorDef |
| |
| // NOLINTNEXTLINE(modernize-use-equals-default) |
| ~AveragePoolConverter() override {} |
| }; |
| REGISTER_CONVERTER(AveragePool, AveragePoolConverter); |
| |
| class MaxPoolConverter : public Converter { |
| std::unique_ptr<nom::repr::NeuralNetOperator> convertToNeuralNetOperator( |
| const OperatorDef& op) override { |
| std::unique_ptr<repr::NeuralNetOperator> nnOp; |
| auto argMap = getArgumentsFromOperator(op); |
| auto kernelShape = getKernelShape(argMap); |
| nnOp = std::make_unique<repr::MaxPool>(kernelShape); |
| return nnOp; |
| } |
| // Does not override default converter to OperatorDef |
| |
| // NOLINTNEXTLINE(modernize-use-equals-default) |
| ~MaxPoolConverter() override {} |
| }; |
| REGISTER_CONVERTER(MaxPool, MaxPoolConverter); |
| |
| class ConcatConverter : public Converter { |
| std::unique_ptr<nom::repr::NeuralNetOperator> convertToNeuralNetOperator( |
| const OperatorDef& op) override { |
| std::unique_ptr<repr::NeuralNetOperator> nnOp = |
| std::make_unique<repr::Concat>(); |
| auto argMap = getArgumentsFromOperator(op); |
| |
| auto c = dyn_cast<repr::Concat>(nnOp.get()); |
| if (argMap.count("axis")) { |
| CAFFE_ENFORCE(argMap["axis"].has_i(), "Invalid axis argument"); |
| int axis = static_cast<int>(argMap["axis"].i()); |
| c->setAxis(axis); |
| } |
| if (argMap.count("add_axis")) { |
| CAFFE_ENFORCE(argMap["add_axis"].has_i(), "Invalid add_axis argument"); |
| int add_axis = static_cast<int>(argMap["add_axis"].i()); |
| c->setAddAxis(!!add_axis); |
| } |
| return nnOp; |
| } |
| // Does not override default converter to OperatorDef |
| |
| // NOLINTNEXTLINE(modernize-use-equals-default) |
| ~ConcatConverter() override {} |
| }; |
| REGISTER_CONVERTER(Concat, ConcatConverter); |
| |
| class FCConverter : public Converter { |
| std::unique_ptr<nom::repr::NeuralNetOperator> convertToNeuralNetOperator( |
| const OperatorDef& op) override { |
| std::unique_ptr<repr::NeuralNetOperator> nnOp = |
| std::make_unique<repr::FC>(); |
| auto argMap = getArgumentsFromOperator(op); |
| |
| auto c = dyn_cast<repr::FC>(nnOp.get()); |
| if (argMap.count("axis")) { |
| CAFFE_ENFORCE(argMap["axis"].has_i(), "Invalid axis argument"); |
| int axis = static_cast<int>(argMap["axis"].i()); |
| c->setAxis(axis); |
| } |
| if (argMap.count("axis_w")) { |
| CAFFE_ENFORCE(argMap["axis_w"].has_i(), "Invalid axis_w argument"); |
| int axis_w = static_cast<int>(argMap["axis_w"].i()); |
| c->setAxisW(axis_w); |
| } |
| |
| return nnOp; |
| } |
| // Does not override default converter to OperatorDef |
| |
| // NOLINTNEXTLINE(modernize-use-equals-default) |
| ~FCConverter() override {} |
| }; |
| REGISTER_CONVERTER(FC, FCConverter); |
| |
| } // namespace |
| |
| std::unique_ptr<repr::NeuralNetOperator> convertToNeuralNetOperator( |
| const caffe2::OperatorDef& op) { |
| auto argMap = Converter::getArgumentsFromOperator(op); |
| |
| std::unique_ptr<repr::NeuralNetOperator> nnOp; |
| |
| if (ConverterRegistry()->Has(op.type())) { |
| nnOp = |
| ConverterRegistry()->Create(op.type())->convertToNeuralNetOperator(op); |
| } |
| |
| if (!nnOp) { |
| nnOp = std::make_unique<repr::GenericOperator>(op.type()); |
| } |
| |
| // Generic attributes associated with Ops here |
| nnOp->setLayout(getLayout(argMap)); |
| |
| auto annotation = std::make_unique<Caffe2Annotation>(); |
| annotation->setOperatorDef(op); |
| |
| auto device_name = op.device_option().node_name(); |
| if (device_name != "") { |
| annotation->setDevice(device_name); |
| } |
| annotation->setDeviceType(op.device_option().device_type()); |
| |
| nnOp->setAnnotation(std::move(annotation)); |
| |
| return nnOp; |
| } |
| |
| /// \brief Ingest a caffe2 protobuf model and output an NNModule. |
| /// \param net The caffe2 protobuf NetDef |
| repr::NNModule convertToNNModule( |
| const caffe2::NetDef& net, |
| bool strict, |
| std::vector<repr::NNGraph::NodeRef>* opNodeVec) { |
| repr::NNModule module; |
| repr::NNGraph& dfg = module.dataFlow; |
| repr::NNCFGraph& cfg = module.controlFlow; |
| /// \brief We keep track of the producer of the blob. |
| /// Because Caffe2 Nets are really just ordered operations |
| /// we can just keep track of the most recent producer of |
| /// a blob and draw and edge from that to any consumer we |
| /// come by. If a new operator produces the blob we simply |
| /// replace it in this map. |
| std::unordered_map<std::string, repr::NNGraph::NodeRef> blobMap; |
| |
| std::unordered_set<std::string> externalInputNames; |
| for (const auto& inputName : net.external_input()) { |
| externalInputNames.insert(inputName); |
| } |
| |
| /// \brief For the construction of the control flow graph we keep track |
| /// of a current basic block, which we split up as we come across control |
| /// flow operations such as if and while. |
| auto bbNode = cfg.createNamedFunction("main"); |
| |
| for (const auto& op : net.op()) { |
| auto opNode = dfg.createNode(); // Create an empty node for the operator. |
| // First calculate in-edges (data dependencies). |
| for (const auto& input : op.input()) { |
| // If we've never seen this tensor, make one. |
| if (!blobMap.count(input)) { |
| auto tensor = std::make_unique<repr::Tensor>(input); |
| blobMap[input] = |
| dfg.createNode(unique_dyn_cast<repr::NeuralNetData>(tensor)); |
| if (externalInputNames.count(input)) { |
| module.inputs.insert(blobMap[input]); |
| externalInputNames.erase(input); |
| } |
| } |
| |
| auto tensorNode = blobMap[input]; |
| dfg.createEdge(tensorNode, opNode); |
| } |
| |
| // Then save outputs into the blobMap for later consumption. |
| for (const auto& output : op.output()) { |
| auto tensor = std::make_unique<repr::Tensor>(output); |
| auto tensorNode = |
| dfg.createNode(unique_dyn_cast<repr::NeuralNetData>(tensor)); |
| dfg.createEdge(opNode, tensorNode); |
| blobMap[output] = tensorNode; |
| } |
| |
| opNode->resetData(convertToNeuralNetOperator(op)); |
| if (opNodeVec) { |
| opNodeVec->emplace_back(opNode); |
| } |
| auto currentBasicBlock = bbNode->mutableData(); |
| currentBasicBlock->pushInstructionNode(opNode); |
| } |
| |
| if (externalInputNames.size()) { |
| // In strict mode we ensure the input names are valid |
| if (strict) { |
| std::ostringstream os; |
| for (const auto& inputName : externalInputNames) { |
| os << "\"" << inputName << "\" "; |
| } |
| |
| CAFFE_ENFORCE( |
| externalInputNames.size() == 0, |
| "Attempting to convert an ill-formed network: ", |
| "external_input contains ", |
| externalInputNames.size(), |
| " unused blobs: ", |
| os.str()); |
| // Otherwise, we add the blobs to the graph as no-ops |
| } else { |
| for (const auto& input : externalInputNames) { |
| blobMap[input] = dfg.createNode(std::make_unique<repr::Tensor>(input)); |
| } |
| } |
| } |
| |
| for (const auto& outputName : net.external_output()) { |
| CAFFE_ENFORCE( |
| !strict || blobMap.count(outputName), |
| "NetDef has ill-formed external_output:", |
| outputName); |
| if (!blobMap.count(outputName)) { |
| LOG(ERROR) << "NetDef has ill-formed external_output: " << outputName; |
| continue; |
| } |
| module.outputs.insert(blobMap[outputName]); |
| } |
| |
| return module; |
| } |
| |
| caffe2::OperatorDef convertToOperatorDef( |
| const repr::NNGraph::NodeRef& instrNode) { |
| auto* nnOp = repr::nn::get<repr::NeuralNetOperator>(instrNode); |
| auto op_type = nnOp->getName(); |
| auto* annotation = nnOp->getAnnotation(); |
| caffe2::OperatorDef op; |
| |
| if (ConverterRegistry()->Has(op_type)) { |
| op = ConverterRegistry()->Create(op_type)->convertToOperatorDef(nnOp); |
| } else if (!annotation) { |
| op.set_type(op_type); |
| } else { |
| if (isa<Caffe2Annotation>(annotation)) { |
| auto c2_annotation = dyn_cast<Caffe2Annotation>(annotation); |
| op = c2_annotation->getOperatorDef(); |
| op.mutable_device_option()->set_device_type( |
| c2_annotation->getDeviceType()); |
| } else { |
| CAFFE_THROW( |
| "Couldn't convert operator annotation to Caffe2 operator def"); |
| } |
| } |
| |
| // We may have swapped out some of the edges. |
| op.clear_input(); |
| op.clear_output(); |
| return op; |
| } |
| |
| Caffe2Annotation* getOrAddCaffe2Annotation( |
| nom::repr::NNGraph::NodeRef& instrNode) { |
| auto* nnOp = repr::nn::get<repr::NeuralNetOperator>(instrNode); |
| auto* annotation = nnOp->getMutableAnnotation(); |
| if (!annotation) { |
| auto new_annot = std::make_unique<Caffe2Annotation>(); |
| new_annot->setOperatorDef(convertToOperatorDef(instrNode)); |
| nnOp->setAnnotation(std::move(new_annot)); |
| annotation = nnOp->getMutableAnnotation(); |
| } |
| CAFFE_ENFORCE(isa<Caffe2Annotation>(annotation)); |
| auto c2_annotation = dyn_cast<Caffe2Annotation>(annotation); |
| return c2_annotation; |
| } |
| |
| caffe2::NetDef convertToCaffe2Proto(repr::NNModule& m) { |
| auto predictNet = caffe2::NetDef(); |
| return convertToCaffe2Proto(m, predictNet); |
| } |
| |
| std::vector<std::string> mergeExternalTensors( |
| const std::unordered_set<repr::NNGraph::NodeRef>& currExternal, |
| const std::vector<std::string>& oldExternal) { |
| std::vector<std::string> out; |
| |
| // Maximally preserve the order of external inputs and outputs. |
| std::unordered_set<std::string> newExternal; |
| for (const auto& tensorNode : currExternal) { |
| CAFFE_ENFORCE( |
| repr::nn::is<repr::NeuralNetData>(tensorNode), |
| "A non-tensor node was added to external inputs/outputs of the NNModule"); |
| auto name = repr::nn::get<repr::NeuralNetData>(tensorNode)->getName(); |
| newExternal.insert(name); |
| } |
| |
| for (const auto& tensorName : oldExternal) { |
| if (newExternal.count(tensorName)) { |
| out.emplace_back(tensorName); |
| newExternal.erase(tensorName); |
| } |
| } |
| for (const auto& tensorName : newExternal) { |
| out.emplace_back(tensorName); |
| } |
| |
| return out; |
| } |
| |
| caffe2::NetDef convertToCaffe2Proto( |
| repr::NNModule& m, |
| const caffe2::NetDef& oldNet) { |
| auto predictNet = caffe2::NetDef(); |
| // We copy the old net rather than mutate it. |
| predictNet.CopyFrom(oldNet); |
| predictNet.mutable_op()->Clear(); |
| |
| repr::nn::coalesceInsertedDataDependencies(&m); |
| |
| // Simply iterate through the CFG and populate data dependencies |
| // with the DFG |
| for (const auto& bbNode : m.controlFlow.getMutableNodes()) { |
| if (bbNode->getOutEdges().size() > 1) { |
| CAFFE_THROW("Control flow not yet supported in Caffe2 converter."); |
| } |
| auto& bb = bbNode->data(); |
| for (const auto& instrNode : bb.getInstructions()) { |
| caffe2::OperatorDef op = convertToOperatorDef(instrNode); |
| |
| for (const auto& inEdge : instrNode->getInEdges()) { |
| auto* tensorNode = |
| dyn_cast<repr::NeuralNetData>(inEdge->tail()->data().get()); |
| *op.add_input() = tensorNode->getName(); |
| } |
| for (const auto& outEdge : instrNode->getOutEdges()) { |
| auto* tensorNode = |
| dyn_cast<repr::NeuralNetData>(outEdge->head()->data().get()); |
| *op.add_output() = tensorNode->getName(); |
| } |
| |
| auto* nnOp = repr::nn::get<repr::NeuralNetOperator>(instrNode); |
| if (nnOp->getLayout() != repr::NeuralNetOperator::NNLayout::Undefined) { |
| caffe2::Argument* arg = nullptr; |
| for (int i = 0; i < op.arg_size(); ++i) { |
| auto arg_ = op.mutable_arg(i); |
| if (arg_->name() == "order") { |
| arg = arg_; |
| break; |
| } |
| } |
| |
| if (!arg) { |
| arg = op.add_arg(); |
| arg->set_name("order"); |
| } |
| |
| auto layout = nnOp->getLayout(); |
| if (layout == repr::NeuralNetOperator::NNLayout::NCHW) { |
| arg->set_s("NCHW"); |
| } |
| if (layout == repr::NeuralNetOperator::NNLayout::NHWC) { |
| arg->set_s("NHWC"); |
| } |
| } |
| |
| // Save the operator to the net. |
| *predictNet.add_op() = op; |
| } |
| } |
| |
| // Maximally preserve the order of external inputs and outputs. |
| std::vector<std::string> oldExternalInputs; |
| std::vector<std::string> oldExternalOutputs; |
| |
| for (const auto& inputName : predictNet.external_input()) { |
| oldExternalInputs.emplace_back(inputName); |
| } |
| for (const auto& outputName : predictNet.external_output()) { |
| oldExternalOutputs.emplace_back(outputName); |
| } |
| |
| auto newExternalInputs = mergeExternalTensors(m.inputs, oldExternalInputs); |
| auto newExternalOutputs = mergeExternalTensors(m.outputs, oldExternalOutputs); |
| |
| predictNet.clear_external_input(); |
| predictNet.clear_external_output(); |
| |
| for (const auto& inputName : newExternalInputs) { |
| predictNet.add_external_input(inputName); |
| } |
| |
| for (const auto& outputName : newExternalOutputs) { |
| predictNet.add_external_output(outputName); |
| } |
| |
| return predictNet; |
| } |
| |
| void pushOpToFront(caffe2::OperatorDef& op, caffe2::NetDef* net) { |
| *net->add_op() = op; |
| google::protobuf::RepeatedPtrField<caffe2::OperatorDef>* op_list( |
| net->mutable_op()); |
| // Reverse iterate, swapping new element in front each time |
| for (int i(net->op_size() - 1); i > 0; --i) { |
| op_list->SwapElements(i, i - 1); |
| } |
| } |
| |
| void injectDataEdgeIndicators(caffe2::NetDef* net) { |
| for (const auto& input : net->external_input()) { |
| caffe2::OperatorDef op; |
| op.set_type("Declare"); |
| op.add_output(input); |
| pushOpToFront(op, net); |
| } |
| for (const auto& output : net->external_output()) { |
| caffe2::OperatorDef op; |
| op.set_type("Export"); |
| op.add_input(output); |
| *net->add_op() = std::move(op); |
| } |
| net->clear_external_input(); |
| net->clear_external_output(); |
| } |
| |
| void removeDataEdgeIndicators(caffe2::NetDef* net) { |
| google::protobuf::RepeatedPtrField<caffe2::OperatorDef>* op_list( |
| net->mutable_op()); |
| for (auto i = 0; i < net->op_size(); ++i) { |
| auto op = net->op(i); |
| if (op.type() == "Declare") { |
| net->add_external_input(op.output(0)); |
| } else if (op.type() == "Export") { |
| net->add_external_output(op.input(0)); |
| } else { |
| continue; |
| } |
| // Note that this compensates for modifying the list inplace |
| op_list->DeleteSubrange(i--, 1); |
| } |
| } |
| |
| } // namespace caffe2 |