blob: 46753ee1f93c6d5288f26c325948916ba312c74e [file] [log] [blame]
#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