ort_interface.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 "ort_interface.hpp"
26 
27 namespace casadi {
28 
29  extern "C"
30  int CASADI_ONNX_ORT_EXPORT
31  casadi_register_onnx_ort(OnnxFunction::Plugin* plugin) {
32  plugin->creator = OnnxRuntimeInterface::creator;
33  plugin->name = "ort";
34  plugin->doc = OnnxRuntimeInterface::meta_doc.c_str();
35  plugin->version = CASADI_VERSION;
36  plugin->options = &OnnxRuntimeInterface::options_;
37  plugin->deserialize = &OnnxRuntimeInterface::deserialize;
38  return 0;
39  }
40 
41  extern "C"
42  void CASADI_ONNX_ORT_EXPORT casadi_load_onnx_ort() {
44  }
45 
46  const std::string OnnxRuntimeInterface::meta_doc =
47  "Black-box ONNX model evaluation through Microsoft's ONNX Runtime.\n";
48 
49  const Options OnnxRuntimeInterface::options_
51  {{"provider",
52  {OT_STRING, "Execution provider ('CPU', 'CUDA') [CPU]"}}
53  }
54  };
55 
57  const GraphBuilderInternal* gb,
58  const std::vector<std::string>& inputs,
59  const std::vector<std::string>& outputs)
60  : OnnxFunction(name, gb, inputs, outputs),
61  ort_api_(OrtGetApiBase()->GetApi(ORT_API_VERSION)), provider_("CPU") {
62  casadi_assert(ort_api_ != nullptr, "Failed to obtain the ONNX Runtime API");
63  }
64 
65  void OnnxRuntimeInterface::ort_check(OrtStatus* status, const std::string& what) const {
66  if (!status) return;
67  std::string msg = ort_api_->GetErrorMessage(status);
68  ort_api_->ReleaseStatus(status);
69  casadi_error("ONNX Runtime error (" + what + "): " + msg);
70  }
71 
72  void OnnxRuntimeInterface::build_prob() {
73  // Metadata (all_in_, in_, out_, in_src_, in_val_, resolved shapes) is frozen by the base
74  in_names_c_.clear(); out_names_c_.clear();
75  in_elem_.clear(); out_elem_.clear(); in_ndim_.clear(); out_ndim_.clear();
76  in_dims_.clear(); out_dims_.clear(); in_numel_.clear(); out_numel_.clear();
77  // Inputs: ALL model inputs (the base in_src_/in_val_ feed map covers them)
78  for (const OnnxTensorInfo& t : all_in_) {
79  in_names_c_.push_back(t.name.c_str());
80  in_elem_.push_back(t.elem_type);
81  in_ndim_.push_back(static_cast<casadi_int>(t.shape.size()));
82  for (casadi_int d : t.shape) in_dims_.push_back(d);
83  in_numel_.push_back(t.numel);
84  }
85  // Outputs: only the exposed selection (ORT runs just the needed subgraph)
86  for (const OnnxTensorInfo& t : out_) {
87  out_names_c_.push_back(t.name.c_str());
88  out_elem_.push_back(t.elem_type);
89  out_ndim_.push_back(static_cast<casadi_int>(t.shape.size()));
90  for (casadi_int d : t.shape) out_dims_.push_back(d);
91  out_numel_.push_back(t.numel);
92  }
93  prob_.n_in = all_in_.size();
94  prob_.n_out = out_.size();
95  prob_.input_names = in_names_c_.data();
96  prob_.output_names = out_names_c_.data();
97  prob_.in_src = in_src_.data();
98  prob_.in_val = in_val_.data();
99  prob_.in_elem_type = in_elem_.data();
100  prob_.out_elem_type = out_elem_.data();
101  prob_.in_ndim = in_ndim_.data();
102  prob_.out_ndim = out_ndim_.data();
103  prob_.in_dims = in_dims_.data();
104  prob_.out_dims = out_dims_.data();
105  prob_.in_numel = in_numel_.data();
106  prob_.out_numel = out_numel_.data();
107  prob_.model_data = model_data_.data();
108  prob_.model_size = static_cast<casadi_int>(model_data_.size());
109  }
110 
111  void OnnxRuntimeInterface::init(const Dict& opts) {
112  OnnxFunction::init(opts);
113 
114  for (auto&& op : opts) {
115  if (op.first == "provider") provider_ = op.second.to_string();
116  }
117 
118  build_prob();
119  }
120 
121  int OnnxRuntimeInterface::init_mem(void* mem) const {
122  if (OnnxFunction::init_mem(mem)) return 1;
123  auto* m = static_cast<OnnxRuntimeMemory*>(mem);
124  casadi_assert(casadi_onnxruntime_init(&m->d, &prob_) == 0,
125  "Failed to create ONNX Runtime session for '" + name_ + "'");
126  return 0;
127  }
128 
129  void OnnxRuntimeInterface::free_mem(void* mem) const {
130  auto* m = static_cast<OnnxRuntimeMemory*>(mem);
131  casadi_onnxruntime_data& d = m->d;
132  // Release the reusable scaffolding (built lazily by the runtime's prepare step) ...
133  if (d.inv) {
134  for (casadi_int i = 0; i < prob_.n_in; ++i) if (d.inv[i]) ort_api_->ReleaseValue(d.inv[i]);
135  free(d.inv);
136  }
137  if (d.buf) {
138  for (casadi_int i = 0; i < prob_.n_in; ++i) if (d.buf[i]) free(d.buf[i]);
139  free(d.buf);
140  }
141  free(d.outv);
142  free(d.row);
143  // ... then the session handles created by init_mem
144  if (d.mem) ort_api_->ReleaseMemoryInfo(d.mem);
145  if (d.session) ort_api_->ReleaseSession(d.session);
146  if (d.env) ort_api_->ReleaseEnv(d.env);
147  delete m;
148  }
149 
151  // Free the memory pool while this is still the dynamic type, so the virtual
152  // free_mem() above runs (the base ~OnnxFunction::clear_mem() is too late).
153  clear_mem();
154  }
155 
158  s.version("OnnxRuntimeInterface", 1);
159  s.pack("OnnxRuntimeInterface::provider", provider_);
160  }
161 
163  : OnnxFunction(s),
164  ort_api_(OrtGetApiBase()->GetApi(ORT_API_VERSION)), provider_("CPU") {
165  casadi_assert(ort_api_ != nullptr, "Failed to obtain the ONNX Runtime API");
166  s.version("OnnxRuntimeInterface", 1);
167  s.unpack("OnnxRuntimeInterface::provider", provider_);
168  build_prob();
169  }
170 
171  int OnnxRuntimeInterface::eval(const double** arg, double** res,
172  casadi_int* iw, double* w, void* mem) const {
173  auto* m = static_cast<OnnxRuntimeMemory*>(mem);
174  return casadi_onnxruntime_solve(&m->d, &prob_, arg, res);
175  }
176 
178  g.add_include("ort_runtime.h", false);
179  }
180 
182  // Embed the model and its metadata (all inputs + the in_src map), then call the runtime
183  std::vector<std::string> in_nm, out_nm;
184  for (const OnnxTensorInfo& t : all_in_) in_nm.push_back(t.name);
185  for (const OnnxTensorInfo& t : out_) out_nm.push_back(t.name);
186  std::string model = g.constant(std::vector<char>(model_data_.begin(), model_data_.end()));
187  std::string in_names = g.constant(in_nm), out_names = g.constant(out_nm);
188  std::string in_src = g.constant(in_src_);
189  std::string in_val = g.constant(in_val_.empty() ? std::vector<double>{0.0} : in_val_);
190  std::string in_elem = g.constant(in_elem_), out_elem = g.constant(out_elem_);
191  std::string in_ndim = g.constant(in_ndim_), out_ndim = g.constant(out_ndim_);
192  std::string in_dims = g.constant(in_dims_.empty() ? std::vector<casadi_int>{0} : in_dims_);
193  std::string out_dims = g.constant(out_dims_.empty() ? std::vector<casadi_int>{0} : out_dims_);
194  std::string in_numel = g.constant(in_numel_), out_numel = g.constant(out_numel_);
195 
196  g << "static struct casadi_onnxruntime_prob prob = {"
197  << all_in_.size() << ", " << out_.size() << ", "
198  << in_names << ", " << out_names << ", "
199  << in_src << ", " << in_val << ", "
200  << in_elem << ", " << out_elem << ", "
201  << in_ndim << ", " << out_ndim << ", "
202  << in_dims << ", " << out_dims << ", "
203  << in_numel << ", " << out_numel << ", "
204  << "(const unsigned char*)" << model << ", " << model_data_.size() << "};\n";
205  g << "static struct casadi_onnxruntime_data data = {0};\n";
206  g << "if (casadi_onnxruntime_init(&data, &prob)) return 1;\n";
207  g << "return casadi_onnxruntime_solve(&data, &prob, arg, res);\n";
208  }
209 
210 } // namespace casadi
Helper class for C code generation.
std::string constant(const std::vector< casadi_int > &v)
Represent an array constant; adding it when new.
void add_include(const std::string &new_include, bool relative_path=false, const std::string &use_ifdef=std::string())
Add an include file optionally using a relative path "..." instead of an absolute path <....
Helper class for Serialization.
void unpack(Sparsity &e)
Reconstruct an object from the input stream.
void version(const std::string &name, int v)
Internal class for GraphBuilder.
Black-box ONNX function base; backends (e.g. onnxruntime) derive from this.
std::vector< casadi_int > in_src_
Per all_in_ entry: exposed-arg index (>=0), -2 baked value, or -1 unwired (default)
void serialize_body(SerializingStream &s) const override
Serialize an object without type information.
static const Options options_
Options.
std::vector< OnnxTensorInfo > out_
std::vector< OnnxTensorInfo > all_in_
Metadata for every model input (a runtime backend must feed all of them)
std::vector< uint8_t > model_data_
Serialized ONNX model.
std::vector< double > in_val_
Baked input values, flat over all_in_ (numel each; placeholder block when not baked)
void init(const Dict &opts) override
Initialize.
static const std::string meta_doc
Documentation.
int init_mem(void *mem) const override
Initialize memory block: create the ONNX Runtime session.
static OnnxFunction * creator(const std::string &name, const GraphBuilderInternal *gb, const std::vector< std::string > &inputs, const std::vector< std::string > &outputs, const Dict &opts)
Plugin factory.
void free_mem(void *mem) const override
Free memory block: tear down the session and reusable scaffolding.
int eval(const double **arg, double **res, casadi_int *iw, double *w, void *mem) const override
Evaluate numerically.
void init(const Dict &opts) override
Initialize.
void codegen_declarations(CodeGenerator &g) const override
Generate code for the declarations.
static const Options options_
Options.
void codegen_body(CodeGenerator &g) const override
Generate code for the body.
void serialize_body(SerializingStream &s) const override
Serialize an object without type information.
static ProtoFunction * deserialize(DeserializingStream &s)
Deserialize into an OnnxRuntimeInterface.
OnnxRuntimeInterface(const std::string &name, const GraphBuilderInternal *gb, const std::vector< std::string > &inputs, const std::vector< std::string > &outputs)
static void registerPlugin(const Plugin &plugin, bool needs_lock=true)
Register an integrator in the factory.
virtual int init_mem(void *mem) const
Initalize memory block.
void clear_mem()
Clear all memory (called from destructor)
Helper class for Serialization.
void version(const std::string &name, int v)
void pack(const Sparsity &e)
Serializes an object to the output stream.
The casadi namespace.
Definition: archiver.cpp:28
int CASADI_ONNX_ORT_EXPORT casadi_register_onnx_ort(OnnxFunction::Plugin *plugin)
GenericType::Dict Dict
C++ equivalent of Python's dict or MATLAB's struct.
void CASADI_ONNX_ORT_EXPORT casadi_load_onnx_ort()
Per-checkout ONNX Runtime state (session + reusable eval scaffolding)
casadi_onnxruntime_data d
Metadata for a single ONNX tensor (input or output); shape is resolved by the builder.