onnx_model.cpp
1 /*
2  * This file is part of CasADi.
3  *
4  * CasADi -- A symbolic framework for dynamic optimization.
5  * Copyright (C) 2010-2023 Joel Andersson, Joris Gillis, Moritz Diehl,
6  * KU Leuven. All rights reserved.
7  * Copyright (C) 2011-2014 Greg Horn
8  *
9  * CasADi is free software; you can redistribute it and/or
10  * modify it under the terms of the GNU Lesser General Public
11  * License as published by the Free Software Foundation; either
12  * version 3 of the License, or (at your option) any later version.
13  *
14  * CasADi is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17  * Lesser General Public License for more details.
18  *
19  * You should have received a copy of the GNU Lesser General Public
20  * License along with CasADi; if not, write to the Free Software
21  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
22  *
23  */
24 
25 #include "onnx_model.hpp"
26 #include <casadi/core/graph_builder_internal.hpp>
27 #include <map>
28 
29 namespace casadi {
30 
31  // Defined in casadi core (onnx.cpp): human-readable name of an ONNX element-type enum
32  std::string onnx_dtype_name(casadi_int elem_type);
33 
34  extern "C"
35  int CASADI_GRAPHMODEL_ONNX_EXPORT
36  casadi_register_graphmodel_onnx(GraphModelInternal::Plugin* plugin) {
37  plugin->creator = Onnx::creator;
38  plugin->name = "onnx";
39  plugin->doc = Onnx::meta_doc.c_str();
40  plugin->version = CASADI_VERSION;
41  plugin->options = &Onnx::options_;
42  return 0;
43  }
44 
45  extern "C"
46  void CASADI_GRAPHMODEL_ONNX_EXPORT casadi_load_graphmodel_onnx() {
48  }
49 
50  const std::string Onnx::meta_doc =
51  "ONNX backend for GraphModel: protobuf metadata, symbolic import and export.\n";
52 
53  const Options Onnx::options_
55  {{"casadi_real",
56  {OT_STRING, "Real type for exported tensors: 'double' (default) or 'float'"}}
57  }
58  };
59 
60  Onnx::Onnx(const std::vector<uint8_t>& model_data) : GraphModelInternal(model_data) {
61  if (!model_data.empty()) load_bytes(model_data);
62  }
63 
65  }
66 
67  void Onnx::init(const Dict& opts) {
69  for (auto&& op : opts) {
70  if (op.first == "casadi_real") set_casadi_real(op.second.to_string());
71  }
72  }
73 
74  void Onnx::load_bytes(const std::vector<uint8_t>& data) {
75  casadi_assert(model_.ParseFromArray(data.data(), static_cast<int>(data.size())),
76  "Failed to parse ONNX model from memory");
77  has_model_ = true;
78  }
79 
80  std::vector<uint8_t> Onnx::save_bytes() const {
81  casadi_assert(has_model_, "No ONNX model loaded.");
82  std::string s;
83  casadi_assert(model_.SerializeToString(&s), "Failed to serialize ONNX model");
84  return std::vector<uint8_t>(s.begin(), s.end());
85  }
86 
87  // Build a Node descriptor from a graph input/output ValueInfo
88  static Node io_node(const onnx::ValueInfoProto& vi, const std::string& io) {
89  Node n;
90  n.name = vi.name();
91  n.io = io;
92  const onnx::TypeProto::Tensor& tt = vi.type().tensor_type();
93  n.dtype = onnx_dtype_name(tt.elem_type());
94  const onnx::TensorShapeProto& sh = tt.shape();
95  for (int k = 0; k < sh.dim_size(); ++k) {
96  const auto& d = sh.dim(k);
97  if (d.has_dim_value()) {
98  n.dimension.push_back(static_cast<casadi_int>(d.dim_value()));
99  n.dim_params.push_back("");
100  } else { // symbolic or unspecified -> dynamic
101  n.dimension.push_back(-1);
102  n.dim_params.push_back(d.has_dim_param() ? d.dim_param() : "");
103  }
104  }
105  return n;
106  }
107 
109  gb.clear_nodes();
110  const onnx::GraphProto& g = model_.graph();
111 
112  // Initializers are constants, not graph inputs (mirror ORT, which excludes them)
113  std::set<std::string> init_names;
114  for (int i = 0; i < g.initializer_size(); ++i) init_names.insert(g.initializer(i).name());
115 
116  for (int i = 0; i < g.input_size(); ++i)
117  if (!init_names.count(g.input(i).name())) gb.add_node(io_node(g.input(i), "input"));
118  for (int i = 0; i < g.output_size(); ++i) gb.add_node(io_node(g.output(i), "output"));
119  }
120 
121  Function Onnx::import_symbolic(const GraphBuilderInternal& gb, const std::string& name) {
122  for (auto&& b : gb.dim_bindings()) set_dimension(b.first, b.second);
123  Function f = create(name);
124  // CasADi exports use reversed axes; external models expose their declared ONNX shapes.
125  if (model_.producer_name() == "CasADi") return f;
126  std::vector<MX> inputs, args, outputs;
127  // Rank of each declared graph input by name (graph inputs may include initializers, so the
128  // graph index does not line up with the Function's input index)
129  std::map<std::string, int> in_rank;
130  for (const auto& in : model_.graph().input())
131  in_rank[in.name()] = in.type().tensor_type().shape().dim_size();
132  for (casadi_int i = 0; i < f.n_in(); ++i) {
133  // Rank<2 inputs are stored as columns already; only rank-2 inputs are transposed back
134  if (in_rank[f.name_in(i)] < 2) {
135  inputs.push_back(MX::sym(f.name_in(i), f.sparsity_in(i)));
136  args.push_back(inputs.back());
137  } else {
138  inputs.push_back(MX::sym(f.name_in(i), f.sparsity_in(i).T()));
139  args.push_back(inputs.back().T());
140  }
141  }
142  f.call(args, outputs, true);
143  for (casadi_int i = 0; i < f.n_out(); ++i) {
144  const auto& shape = model_.graph().output(i).type().tensor_type().shape();
145  outputs[i] = shape.dim_size() == 1 ? vec(outputs[i]) : outputs[i].T();
146  }
147  return Function(name, inputs, outputs, f.name_in(), f.name_out());
148  }
149 
150  std::vector<uint8_t> Onnx::export_symbolic(const Function& f, const Dict& opts) {
151  auto it = opts.find("casadi_real");
152  if (it != opts.end()) set_casadi_real(it->second.to_string());
153  load(f);
154  return save_bytes();
155  }
156 
157 } // namespace casadi
Function object.
Definition: function.hpp:126
const std::vector< std::string > & name_in() const
Get the names of all inputs, in order.
Definition: function.cpp:1113
const Sparsity & sparsity_in(casadi_int ind) const
Get the sparsity pattern of an input, by index or name.
Definition: function.cpp:1167
casadi_int n_out() const
Get the number of function outputs.
Definition: function.cpp:975
casadi_int n_in() const
Get the number of function inputs.
Definition: function.cpp:971
void call(const std::vector< DM > &arg, std::vector< DM > &res, bool always_inline=false, bool never_inline=false) const
Evaluate with a list or a dict of arguments; the result has the same form.
Definition: function.cpp:509
const std::vector< std::string > & name_out() const
Get the names of all outputs, in order.
Definition: function.cpp:1117
static MX sym(const std::string &name, casadi_int nrow=1, casadi_int ncol=1)
Create an nrow-by-ncol symbolic primitive.
Internal class for GraphBuilder.
void clear_nodes()
Drop all tensor descriptors (called by a backend before re-filling)
void add_node(const Node &n)
Append a tensor descriptor (called by a backend during fill_metadata)
const std::map< std::string, casadi_int > & dim_bindings() const
Base interface for format-specific graph-model backends.
static const Options options_
Options.
const std::vector< uint8_t > & model_data() const
Raw model bytes.
virtual void init(const Dict &opts)
Initialize.
std::vector< uint8_t > export_symbolic(const Function &f, const Dict &opts) override
Serialize a CasADi Function as model bytes (symbolic export; mutates the backend's engine)
Definition: onnx_model.cpp:150
static const std::string meta_doc
Documentation.
Definition: onnx_model.hpp:90
static const Options options_
Options.
Definition: onnx_model.hpp:77
Function create(const std::string &name)
Create a CasADi Function from the loaded ONNX graph.
void load_bytes(const std::vector< uint8_t > &data)
Load a graph from serialized ONNX bytes.
Definition: onnx_model.cpp:74
~Onnx() override
Definition: onnx_model.cpp:64
static GraphModelInternal * creator(const std::vector< uint8_t > &model_data)
Plugin factory.
Definition: onnx_model.hpp:68
std::vector< uint8_t > save_bytes() const
Serialize the loaded graph/model to ONNX bytes.
Definition: onnx_model.cpp:80
onnx::ModelProto model_
ONNX model protocol buffer.
Definition: onnx_model.hpp:111
Function import_symbolic(const GraphBuilderInternal &gb, const std::string &name) override
Rebuild the graph as a CasADi Function (symbolic import; mutates the backend's engine)
Definition: onnx_model.cpp:121
void init(const Dict &opts) override
Initialize.
Definition: onnx_model.cpp:67
bool has_model_
Whether a model has been loaded.
Definition: onnx_model.hpp:117
void load(const Function &f)
Load a CasADi Function and convert to the ONNX representation.
Definition: onnx_export.cpp:77
Onnx(const std::vector< uint8_t > &model_data)
Definition: onnx_model.cpp:60
void set_dimension(const std::string &name, casadi_int dim)
Set dimension for a symbolic variable.
void fill_metadata(GraphBuilderInternal &gb) const override
Populate a builder's Node metadata from the parsed model.
Definition: onnx_model.cpp:108
static void registerPlugin(const Plugin &plugin, bool needs_lock=true)
Register an integrator in the factory.
Sparsity T() const
Transposed pattern.
Definition: sparsity.cpp:396
The casadi namespace.
Definition: archiver.cpp:28
std::string onnx_dtype_name(casadi_int t)
Human-readable name of an ONNX element-type enum (1=FLOAT, 11=DOUBLE, 7=INT64, ......
static Node io_node(const onnx::ValueInfoProto &vi, const std::string &io)
Definition: onnx_model.cpp:88
GenericType::Dict Dict
C++ equivalent of Python's dict or MATLAB's struct.
int CASADI_GRAPHMODEL_ONNX_EXPORT casadi_register_graphmodel_onnx(GraphModelInternal::Plugin *plugin)
Definition: onnx_model.cpp:36
void CASADI_GRAPHMODEL_ONNX_EXPORT casadi_load_graphmodel_onnx()
Definition: onnx_model.cpp:46
Metadata for one graph tensor (graph input or output)
std::vector< casadi_int > dimension
Declared shape, -1 for dynamic dimensions.
std::string name
Tensor name.
std::string io
"input" or "output"
std::string dtype
Element type name (FLOAT, INT64, ...)
std::vector< std::string > dim_params
Symbolic name per axis ("" if static)