Expose GetOverridableInitializers via Python and C/C++ API (#1878)

Implement GetOverridableInitializers()
 Add unit test for initializer override.
Expose in Python and C/C++ API
This commit is contained in:
Dmitri Smirnov 2019-09-19 15:43:28 -07:00 committed by GitHub
parent 429f05138a
commit 6a9ae65f41
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 240 additions and 38 deletions

View file

@ -519,6 +519,14 @@ class Graph {
return graph_inputs_including_initializers_;
}
/** Gets the Graph inputs that are initializers
These are overridable initializers. This is a difference between
graph_inputs_including_initializers_ and graph_inputs_excluding_initializers_
@remarks Contains no nullptr values. */
const std::vector<const NodeArg*>& GetOverridableInitializers () const {
return graph_overridable_initializers_;
}
/** Gets the Graph outputs.
@remarks Contains no nullptr values.*/
const std::vector<const NodeArg*>& GetOutputs() const noexcept { return graph_outputs_; }
@ -852,6 +860,9 @@ class Graph {
// Initialize all the graph inputs, initializers and outputs
common::Status InitInputsInitializersOutputs();
// Initialize overridable initializers container
void ComputeOverridableInitializers();
// recursively accumulate and set the outer scope node args in the resolve context for all subgraphs
// so they can be used to resolve outer scope dependencies when running BuildConnections for the subgraphs.
common::Status SetOuterScopeNodeArgs(const std::unordered_set<std::string>& outer_scope_node_args);
@ -965,6 +976,10 @@ class Graph {
// Graph inputs excluding initializers.
std::vector<const NodeArg*> graph_inputs_excluding_initializers_;
// Overridable Initializers. The difference between graph_inputs_including_initializers_
// and graph_inputs_excluding_initializers_
std::vector<const NodeArg*> graph_overridable_initializers_;
// Graph outputs.
std::vector<const NodeArg*> graph_outputs_;
bool graph_outputs_manually_set_ = false;

View file

@ -269,6 +269,7 @@ ORT_API_STATUS(OrtSetInterOpNumThreads, _Inout_ OrtSessionOptions* options, int
ORT_API_STATUS(OrtSessionGetInputCount, _In_ const OrtSession* sess, _Out_ size_t* out);
ORT_API_STATUS(OrtSessionGetOutputCount, _In_ const OrtSession* sess, _Out_ size_t* out);
ORT_API_STATUS(OrtSessionGetOverridableInitializerCount, _In_ const OrtSession* sess, _Out_ size_t* out);
/**
* \param out should be freed by OrtReleaseTypeInfo after use
@ -280,6 +281,12 @@ ORT_API_STATUS(OrtSessionGetInputTypeInfo, _In_ const OrtSession* sess, size_t i
*/
ORT_API_STATUS(OrtSessionGetOutputTypeInfo, _In_ const OrtSession* sess, size_t index, _Outptr_ OrtTypeInfo** type_info);
/**
* \param out should be freed by OrtReleaseTypeInfo after use
*/
ORT_API_STATUS(OrtSessionGetOverridableInitializerTypeInfo, _In_ const OrtSession* sess, size_t index, _Outptr_ OrtTypeInfo** type_info);
/**
* \param value is set to a null terminated string allocated using 'allocator'. The caller is responsible in freeing it.
*/
@ -287,6 +294,8 @@ ORT_API_STATUS(OrtSessionGetInputName, _In_ const OrtSession* sess, size_t index
_Inout_ OrtAllocator* allocator, _Outptr_ char** value);
ORT_API_STATUS(OrtSessionGetOutputName, _In_ const OrtSession* sess, size_t index,
_Inout_ OrtAllocator* allocator, _Outptr_ char** value);
ORT_API_STATUS(OrtSessionGetOverridableInitializerName, _In_ const OrtSession* sess, size_t index,
_Inout_ OrtAllocator* allocator, _Outptr_ char** value);
/**
* \return A pointer to the newly created object. The pointer should be freed by OrtReleaseRunOptions after use

View file

@ -72,6 +72,7 @@ struct Base {
protected:
Base(const Base&) = delete;
Base& operator=(const Base&) = delete;
Base(Base&& v) noexcept : p_{v.p_} { v.p_ = nullptr; }
void operator=(Base&& v) noexcept {
OrtRelease(p_);
@ -175,12 +176,15 @@ struct Session : Base<OrtSession> {
size_t GetInputCount() const;
size_t GetOutputCount() const;
size_t GetOverridableInitializerCount() const;
char* GetInputName(size_t index, OrtAllocator* allocator) const;
char* GetOutputName(size_t index, OrtAllocator* allocator) const;
char* GetOverridableInitializerName(size_t index, OrtAllocator* allocator) const;
TypeInfo GetInputTypeInfo(size_t index) const;
TypeInfo GetOutputTypeInfo(size_t index) const;
TypeInfo GetOverridableInitializerTypeInfo(size_t index) const;
};
struct TensorTypeAndShapeInfo : Base<OrtTensorTypeAndShapeInfo> {
@ -223,6 +227,8 @@ struct Value : Base<OrtValue> {
explicit Value(nullptr_t) {}
explicit Value(OrtValue* p) : Base<OrtValue>{p} {}
Value(Value&&) = default;
Value& operator=(Value&&) = default;
bool IsTensor() const;
size_t GetCount() const; // If a non tensor, returns 2 for map and N for sequence, where N is the number of elements

View file

@ -38,6 +38,8 @@ template <>
struct TypeToTensorType<uint32_t> { static constexpr ONNXTensorElementDataType type = ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT32; };
template <>
struct TypeToTensorType<uint64_t> { static constexpr ONNXTensorElementDataType type = ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT64; };
template <>
struct TypeToTensorType<bool> { static constexpr ONNXTensorElementDataType type = ONNX_TENSOR_ELEMENT_DATA_TYPE_BOOL; };
inline AllocatorWithDefaultOptions::AllocatorWithDefaultOptions() {
ORT_THROW_ON_ERROR(OrtGetAllocatorWithDefaultOptions(&p_));
@ -242,6 +244,12 @@ inline size_t Session::GetOutputCount() const {
return out;
}
inline size_t Session::GetOverridableInitializerCount () const {
size_t out;
ORT_THROW_ON_ERROR(OrtSessionGetOverridableInitializerCount(p_, &out));
return out;
}
inline char* Session::GetInputName(size_t index, OrtAllocator* allocator) const {
char* out;
ORT_THROW_ON_ERROR(OrtSessionGetInputName(p_, index, allocator, &out));
@ -254,6 +262,12 @@ inline char* Session::GetOutputName(size_t index, OrtAllocator* allocator) const
return out;
}
inline char* Session::GetOverridableInitializerName(size_t index, OrtAllocator* allocator) const {
char* out;
ORT_THROW_ON_ERROR(OrtSessionGetOverridableInitializerName(p_, index, allocator, &out));
return out;
}
inline TypeInfo Session::GetInputTypeInfo(size_t index) const {
OrtTypeInfo* out;
ORT_THROW_ON_ERROR(OrtSessionGetInputTypeInfo(p_, index, &out));
@ -266,6 +280,12 @@ inline TypeInfo Session::GetOutputTypeInfo(size_t index) const {
return TypeInfo{out};
}
inline TypeInfo Session::GetOverridableInitializerTypeInfo(size_t index) const {
OrtTypeInfo* out;
ORT_THROW_ON_ERROR(OrtSessionGetOverridableInitializerTypeInfo(p_, index, &out));
return TypeInfo{out};
}
inline ONNXTensorElementDataType TensorTypeAndShapeInfo::GetElementType() const {
ONNXTensorElementDataType out;
ORT_THROW_ON_ERROR(OrtGetTensorElementType(p_, &out));

View file

@ -126,7 +126,6 @@ const TensorShapeProto* NodeArg::Shape() const {
}
void NodeArg::SetShape(const TensorShapeProto& shape) {
const auto type_case = node_arg_info_.type().value_case();
switch (type_case) {
case TypeProto::kTensorType:
@ -1356,7 +1355,7 @@ Status Graph::InferAndVerifySubgraphTypes(const Node& node, Graph& subgraph,
" inputs and requires ", num_required_subgraph_inputs,
" inputs. Either provide all subgraph inputs, or just the required inputs.");
}
subgraph_inputs = &required_subgraph_inputs;
num_subgraph_inputs = num_required_subgraph_inputs;
}
@ -2459,9 +2458,34 @@ Status Graph::SetGraphInputsOutputs() {
}
}
ComputeOverridableInitializers();
return Status::OK();
}
void Graph::ComputeOverridableInitializers() {
graph_overridable_initializers_.clear();
if (CanOverrideInitializer()) {
// graph_inputs_excluding_initializers_ and graph_inputs_including_initializers_
// are inserted in the same order. So we walk and compute the difference.
auto f_incl = graph_inputs_including_initializers_.cbegin();
const auto l_incl = graph_inputs_including_initializers_.cend();
auto f_excl = graph_inputs_excluding_initializers_.cbegin();
const auto l_excl = graph_inputs_excluding_initializers_.cend();
while (f_incl != l_incl) {
// Equal means not an initializer
if (f_excl != l_excl && *f_incl == *f_excl) {
++f_incl;
++f_excl;
continue;
}
graph_overridable_initializers_.push_back(*f_incl);
++f_incl;
}
}
}
// calling private ctor
GSL_SUPPRESS(r .11)
gsl::not_null<Node*> Graph::AllocateNode() {

View file

@ -77,6 +77,9 @@ OrtSessionGetInputTypeInfo
OrtSessionGetOutputCount
OrtSessionGetOutputName
OrtSessionGetOutputTypeInfo
OrtSessionGetOverridableInitializerCount
OrtSessionGetOverridableInitializerName
OrtSessionGetOverridableInitializerTypeInfo
OrtSessionOptionsAppendExecutionProvider_CPU
OrtSetDimensions
OrtSetSessionGraphOptimizationLevel

View file

@ -814,6 +814,20 @@ std::pair<common::Status, const InputDefList*> InferenceSession::GetModelInputs(
return std::make_pair(common::Status::OK(), &model_->MainGraph().GetInputs());
}
std::pair<common::Status, const InputDefList*> InferenceSession::GetOverridableInitializers() const {
{
std::lock_guard<onnxruntime::OrtMutex> l(session_mutex_);
if (!is_model_loaded_) {
LOGS(*session_logger_, ERROR) << "Model was not loaded";
return std::make_pair(common::Status(common::ONNXRUNTIME, common::FAIL, "Model was not loaded."),
nullptr);
}
}
// returns a list of initializers that can be overriden.
return std::make_pair(common::Status::OK(), &model_->MainGraph().GetOverridableInitializers());
}
std::pair<common::Status, const OutputDefList*> InferenceSession::GetModelOutputs() const {
{
std::lock_guard<onnxruntime::OrtMutex> l(session_mutex_);

View file

@ -271,6 +271,15 @@ class InferenceSession {
*/
std::pair<common::Status, const InputDefList*> GetModelInputs() const;
/**
* Get all definitions of the model for overridable initializers.
* This does not include weights. Use this to get the name/type/shapes of the overridable initializers.
* @return pair.first = OK; FAIL otherwise. pair.second is non-NULL when pair.first = OK.
* @note lifetime of the returned pointer is valid as long as the Session object is live.
* @note for IR < 4 returned list will always be empty.
*/
std::pair<common::Status, const InputDefList*> GetOverridableInitializers() const;
/**
* Get all output definitions of the model. Use this to get the name/type/shapes of the outputs.
* @return pair.first = OK; FAIL otherwise. pair.second is non-NULL when pair.first = OK.

View file

@ -525,10 +525,16 @@ ORT_API_STATUS_IMPL(OrtGetStringTensorContent, _In_ const OrtValue* value,
delete reinterpret_cast<REAL_TYPE*>(value); \
}
ORT_API_STATUS_IMPL(OrtSessionGetInputCount, _In_ const OrtSession* sess, _Out_ size_t* out) {
using DefListResult = std::pair<Status, const InputDefList*>;
using GetDefListFn = DefListResult (*)(const ::onnxruntime::InferenceSession*);
const auto get_inputs_fn = [](const ::onnxruntime::InferenceSession* session) -> DefListResult { return session->GetModelInputs(); };
const auto get_outputs_fn = [](const ::onnxruntime::InferenceSession* session) -> DefListResult { return session->GetModelOutputs(); };
const auto get_overridable_initializers_fn = [](const ::onnxruntime::InferenceSession* session) -> DefListResult { return session->GetOverridableInitializers(); };
static OrtStatus* GetNodeDefListCountHelper(const OrtSession* sess, GetDefListFn get_fn, size_t* out) {
API_IMPL_BEGIN
auto session = reinterpret_cast<const ::onnxruntime::InferenceSession*>(sess);
std::pair<Status, const InputDefList*> p = session->GetModelInputs();
std::pair<Status, const InputDefList*> p = get_fn(session);
if (!p.first.IsOK())
return ToOrtStatus(p.first);
*out = p.second->size();
@ -536,40 +542,41 @@ ORT_API_STATUS_IMPL(OrtSessionGetInputCount, _In_ const OrtSession* sess, _Out_
API_IMPL_END
}
ORT_API_STATUS_IMPL(OrtSessionGetInputCount, _In_ const OrtSession* sess, _Out_ size_t* out) {
return GetNodeDefListCountHelper(sess, get_inputs_fn, out);
}
ORT_API_STATUS_IMPL(OrtSessionGetOutputCount, _In_ const OrtSession* sess, _Out_ size_t* out) {
return GetNodeDefListCountHelper(sess, get_outputs_fn, out);
}
ORT_API_STATUS_IMPL(OrtSessionGetOverridableInitializerCount, _In_ const OrtSession* sess, _Out_ size_t* out) {
return GetNodeDefListCountHelper(sess, get_overridable_initializers_fn, out);
}
static OrtStatus* GetNodeDefTypeInfoHelper(const OrtSession* sess, GetDefListFn get_fn, size_t index, _Outptr_ struct OrtTypeInfo** out) {
API_IMPL_BEGIN
auto session = reinterpret_cast<const ::onnxruntime::InferenceSession*>(sess);
std::pair<Status, const InputDefList*> p = session->GetModelOutputs();
std::pair<Status, const InputDefList*> p = get_fn(session);
if (!p.first.IsOK())
return ToOrtStatus(p.first);
*out = p.second->size();
return nullptr;
if (p.second->size() <= index)
return OrtCreateStatus(ORT_FAIL, "out of index");
const ONNX_NAMESPACE::TypeProto* type_proto = (*p.second)[index]->TypeAsProto();
return OrtTypeInfo::FromDataTypeImpl(type_proto, out);
API_IMPL_END
}
ORT_API_STATUS_IMPL(OrtSessionGetInputTypeInfo, _In_ const OrtSession* sess, size_t index, _Outptr_ struct OrtTypeInfo** out) {
API_IMPL_BEGIN
auto session = reinterpret_cast<const ::onnxruntime::InferenceSession*>(sess);
std::pair<Status, const InputDefList*> p = session->GetModelInputs();
if (!p.first.IsOK())
return ToOrtStatus(p.first);
if (p.second->size() <= index)
return OrtCreateStatus(ORT_FAIL, "out of index");
const ONNX_NAMESPACE::TypeProto* type_proto = (*p.second)[index]->TypeAsProto();
return OrtTypeInfo::FromDataTypeImpl(type_proto, out);
API_IMPL_END
return GetNodeDefTypeInfoHelper(sess, get_inputs_fn, index, out);
}
ORT_API_STATUS_IMPL(OrtSessionGetOutputTypeInfo, _In_ const OrtSession* sess, size_t index, _Outptr_ struct OrtTypeInfo** out) {
API_IMPL_BEGIN
auto session = reinterpret_cast<const ::onnxruntime::InferenceSession*>(sess);
std::pair<Status, const InputDefList*> p = session->GetModelOutputs();
if (!p.first.IsOK())
return ToOrtStatus(p.first);
if (p.second->size() <= index)
return OrtCreateStatus(ORT_FAIL, "out of index");
const ONNX_NAMESPACE::TypeProto* type_proto = (*p.second)[index]->TypeAsProto();
return OrtTypeInfo::FromDataTypeImpl(type_proto, out);
API_IMPL_END
return GetNodeDefTypeInfoHelper(sess, get_outputs_fn, index, out);
}
ORT_API_STATUS_IMPL(OrtSessionGetOverridableInitializerTypeInfo, _In_ const OrtSession* sess, size_t index, _Outptr_ struct OrtTypeInfo** out) {
return GetNodeDefTypeInfoHelper(sess, get_overridable_initializers_fn, index, out);
}
static char* StrDup(const std::string& str, OrtAllocator* allocator) {
@ -579,11 +586,11 @@ static char* StrDup(const std::string& str, OrtAllocator* allocator) {
return output_string;
}
static OrtStatus* GetInputOutputNameImpl(_In_ const OrtSession* sess, size_t index,
_Inout_ OrtAllocator* allocator, bool is_input,
_Outptr_ char** output) {
static OrtStatus* GetNodeDefNameImpl(_In_ const OrtSession* sess, size_t index,
_Inout_ OrtAllocator* allocator, GetDefListFn get_fn,
_Outptr_ char** output) {
auto session = reinterpret_cast<const ::onnxruntime::InferenceSession*>(sess);
std::pair<Status, const InputDefList*> p = is_input ? session->GetModelInputs() : session->GetModelOutputs();
std::pair<Status, const InputDefList*> p = get_fn(session);
if (!p.first.IsOK())
return ToOrtStatus(p.first);
if (p.second == nullptr)
@ -625,14 +632,21 @@ ORT_API_STATUS_IMPL(OrtAllocatorGetInfo, _In_ const OrtAllocator* ptr, _Outptr_
ORT_API_STATUS_IMPL(OrtSessionGetInputName, _In_ const OrtSession* sess, size_t index,
_Inout_ OrtAllocator* allocator, _Outptr_ char** output) {
API_IMPL_BEGIN
return GetInputOutputNameImpl(sess, index, allocator, true, output);
return GetNodeDefNameImpl(sess, index, allocator, get_inputs_fn, output);
API_IMPL_END
}
ORT_API_STATUS_IMPL(OrtSessionGetOutputName, _In_ const OrtSession* sess, size_t index,
_Inout_ OrtAllocator* allocator, _Outptr_ char** output) {
API_IMPL_BEGIN
return GetInputOutputNameImpl(sess, index, allocator, false, output);
return GetNodeDefNameImpl(sess, index, allocator, get_outputs_fn, output);
API_IMPL_END
}
ORT_API_STATUS_IMPL(OrtSessionGetOverridableInitializerName, _In_ const OrtSession* sess, size_t index,
_Inout_ OrtAllocator* allocator, _Outptr_ char** output) {
API_IMPL_BEGIN
return GetNodeDefNameImpl(sess, index, allocator, get_overridable_initializers_fn, output);
API_IMPL_END
}
@ -1073,13 +1087,13 @@ ORT_API_STATUS_IMPL(OrtCreateValue, const OrtValue* const* in, size_t num_values
}
ORT_API_STATUS_IMPL(OrtCreateOpaqueValue, const char* domain_name, const char* type_name, const void* data_container,
size_t data_container_size, OrtValue** out) {
size_t data_container_size, OrtValue** out) {
API_IMPL_BEGIN
std::string dtype("opaque(");
dtype.append(domain_name).append(",").append(type_name).append(")");
MLDataType ml_type = DataTypeImpl::GetDataType(dtype);
ORT_ENFORCE(ml_type != nullptr,
"Specified domain and type names combination does not refer to a registered opaque type");
"Specified domain and type names combination does not refer to a registered opaque type");
const auto* non_tensor_base = ml_type->AsNonTensorTypeBase();
ORT_ENFORCE(non_tensor_base != nullptr, "Opaque type is not a non_tensor type!!!");
std::unique_ptr<OrtValue> ort_val(new OrtValue);
@ -1089,8 +1103,8 @@ ORT_API_STATUS_IMPL(OrtCreateOpaqueValue, const char* domain_name, const char* t
return nullptr;
}
ORT_API_STATUS_IMPL(OrtGetOpaqueValue, const char* domain_name, const char* type_name, const OrtValue* in,
void* data_container, size_t data_container_size) {
ORT_API_STATUS_IMPL(OrtGetOpaqueValue, const char* domain_name, const char* type_name, const OrtValue* in,
void* data_container, size_t data_container_size) {
API_IMPL_BEGIN
std::string dtype("opaque(");
dtype.append(domain_name).append(",").append(type_name).append(")");
@ -1104,7 +1118,6 @@ ORT_API_STATUS_IMPL(OrtGetOpaqueValue, const char* domain_name, const char* type
return nullptr;
}
// End support for non-tensor types
DEFINE_RELEASE_ORT_OBJECT_FUNCTION(Env, OrtEnv)

View file

@ -768,6 +768,14 @@ including arg name, arg type (contains both type and shape).)pbdoc")
return *(res.second);
}
})
.def_property_readonly("overridable_initializers", [](const InferenceSession* sess) -> const std::vector<const onnxruntime::NodeArg*>& {
auto res = sess->GetOverridableInitializers();
if (!res.first.IsOK()) {
throw std::runtime_error(res.first.ToString().c_str());
} else {
return *res.second;
}
})
.def_property_readonly("model_meta", [](const InferenceSession* sess) -> const onnxruntime::ModelMetadata& {
auto res = sess->GetModelMetadata();
if (!res.first.IsOK()) {

View file

@ -42,6 +42,7 @@ class InferenceSession:
self._inputs_meta = self._sess.inputs_meta
self._outputs_meta = self._sess.outputs_meta
self._overridable_initializers = self._sess.overridable_initializers
self._model_meta = self._sess.model_meta
self._providers = self._sess.get_providers()
@ -51,6 +52,7 @@ class InferenceSession:
# so they must be set to None to decrement _sess reference count.
self._inputs_meta = None
self._outputs_meta = None
self._overridable_initializers = None
self._model_meta = None
self._providers = None
self._sess = None
@ -63,6 +65,10 @@ class InferenceSession:
"Return the outputs metadata as a list of :class:`onnxruntime.NodeArg`."
return self._outputs_meta
def get_overridable_initializers(self):
"Return the inputs (including initializers) metadata as a list of :class:`onnxruntime.NodeArg`."
return self._overridable_initializers
def get_modelmeta(self):
"Return the metadata. See :class:`onnxruntime.ModelMetadata`."
return self._model_meta

View file

@ -74,6 +74,25 @@ def main():
input_meta.type, input_meta.name))
sys.exit(-1)
# Starting with IR4 some initializers provide default values
# and can be overridden (available in IR4). For IR < 4 models
# the list would be empty
for initializer in sess.get_overridable_initializers():
shape = [dim if dim else 1 for dim in initializer.shape]
if initializer.type in float_dict:
feeds[initializer.name] = np.random.rand(
*shape).astype(float_dict[initializer.type])
elif initializer.type in integer_dict:
feeds[initializer.name] = np.random.uniform(
high=1000, size=tuple(shape)).astype(integer_dict[initializer.type])
elif initializer.type == 'tensor(bool)':
feeds[initializer.name] = np.random.randint(
2, size=tuple(shape)).astype('bool')
else:
print("unsupported initializer type {} for initializer {}".format(
initializer.type, initializer.name))
sys.exit(-1)
start = timer()
for i in range(iters):
sess.run([], feeds) # fetch all outputs

View file

@ -116,6 +116,7 @@ void TestInference(Ort::Env& env, T model_uri,
static constexpr PATH_TYPE MODEL_URI = TSTR("testdata/mul_1.onnx");
static constexpr PATH_TYPE CUSTOM_OP_MODEL_URI = TSTR("testdata/foo_1.onnx");
static constexpr PATH_TYPE OVERRIDABLE_INITIALIZER_MODEL_URI = TSTR("testdata/overridable_initializer.onnx");
#ifdef ENABLE_LANGUAGE_INTEROP_OPS
static constexpr PATH_TYPE PYOP_FLOAT_MODEL_URI = TSTR("testdata/pyop_1.onnx");
#endif
@ -290,6 +291,61 @@ TEST_F(CApiTest, create_tensor_with_data) {
ASSERT_EQ(1, tensor_info.GetDimensionsCount());
}
TEST_F(CApiTest, override_initializer) {
Ort::MemoryInfo info("Cpu", OrtDeviceAllocator, 0, OrtMemTypeDefault);
auto allocator = std::make_unique<MockedOrtAllocator>();
// CreateTensor which is not owning this ptr
bool Label_input[] = {true};
std::vector<int64_t> dims = {1, 1};
Ort::Value label_input_tensor = Ort::Value::CreateTensor<bool>(info, Label_input, 1U, dims.data(), dims.size());
std::string f2_data{"f2_string"};
// Place a string into Tensor OrtValue and assign to the
Ort::Value f2_input_tensor = Ort::Value::CreateTensor(allocator.get(), dims.data(), dims.size(), ONNX_TENSOR_ELEMENT_DATA_TYPE_STRING);
// No C++ Api to either create a string Tensor or to fill one with string, so we use C
const char* const input_char_string[] = {f2_data.c_str()};
ORT_THROW_ON_ERROR(OrtFillStringTensor(static_cast<OrtValue*>(f2_input_tensor), input_char_string, 1U));
Ort::SessionOptions session_options;
Ort::Session session(env_, OVERRIDABLE_INITIALIZER_MODEL_URI, session_options);
// Get Overrideable initializers
size_t init_count = session.GetOverridableInitializerCount();
ASSERT_EQ(init_count, 1U);
char* f1_init_name = session.GetOverridableInitializerName(0, allocator.get());
ASSERT_TRUE(strcmp("F1", f1_init_name) == 0);
allocator->Free(f1_init_name);
Ort::TypeInfo init_type_info = session.GetOverridableInitializerTypeInfo(0);
ASSERT_EQ(ONNX_TYPE_TENSOR, init_type_info.GetONNXType());
// Let's override the initializer
float f11_input_data[] = {2.0f};
Ort::Value f11_input_tensor = Ort::Value::CreateTensor<float>(info, f11_input_data, 1U, dims.data(), dims.size());
std::vector<Ort::Value> ort_inputs;
ort_inputs.push_back(std::move(label_input_tensor));
ort_inputs.push_back(std::move(f2_input_tensor));
ort_inputs.push_back(std::move(f11_input_tensor));
std::vector<const char*> input_names = {"Label", "F2", "F1"};
const char* const output_names[] = {"Label0", "F20", "F11"};
std::vector<Ort::Value> ort_outputs = session.Run(Ort::RunOptions{nullptr}, input_names.data(),
ort_inputs.data(), ort_inputs.size(),
output_names, countof(output_names));
ASSERT_EQ(ort_outputs.size(), 3U);
// Expecting the last output would be the overridden value of the initializer
auto type_info = ort_outputs[2].GetTensorTypeAndShapeInfo();
ASSERT_EQ(type_info.GetShape(), dims);
ASSERT_EQ(type_info.GetElementType(), ONNX_TENSOR_ELEMENT_DATA_TYPE_FLOAT);
ASSERT_EQ(type_info.GetElementCount(), 1U);
float* output_data = ort_outputs[2].GetTensorMutableData<float>();
ASSERT_EQ(*output_data, f11_input_data[0]);
}
int main(int argc, char** argv) {
::testing::InitGoogleTest(&argc, argv);
int ret = RUN_ALL_TESTS();

Binary file not shown.