5cbd3f29e3
CodeQL / Analyze (actions) (push) Waiting to run
CodeQL / Analyze (cpp) (push) Waiting to run
CodeQL / Analyze (python) (push) Waiting to run
Copilot Setup Steps / copilot-setup-steps (push) Waiting to run
Generate and publish ONNX docs / build (push) Waiting to run
Generate and publish ONNX docs / deploy (push) Blocked by required conditions
Scorecard supply-chain security / Scorecard analysis (push) Waiting to run
Fuzz / Run fuzz harnesses (${{ github.event_name == 'schedule' && 'nightly' || 'smoke' }}) (push) Has been cancelled
Create Releases / call-mac (push) Has been cancelled
Create Releases / call-linux (push) Has been cancelled
Create Releases / call-sdist (push) Has been cancelled
Create Releases / call-win (push) Has been cancelled
Create Releases / call-pyodide (push) Has been cancelled
Windows_No_Exception_CI / build (x64, 3.10) (push) Has been cancelled
Check URLs / build (push) Has been cancelled
Create Releases / Attest CI build artifacts (push) Has been cancelled
Create Releases / Check for Publish release build to pypi (push) Has been cancelled
Create Releases / Check for Publish preview build to test.pypi-weekly (push) Has been cancelled
Create Releases / Publish preview build to test.pypi-weekly (push) Has been cancelled
Create Releases / Check for Publish release build to test.pypi (rc-candidates) (push) Has been cancelled
Create Releases / Publish release build to test.pypi (push) Has been cancelled
Create Releases / Check for Publish preview build to pypi-weekly (push) Has been cancelled
Create Releases / Publish preview build to pypi-weekly (push) Has been cancelled
Create Releases / Publish release build to pypi (push) Has been cancelled
Create Releases / test source distribution (push) Has been cancelled
clang-tidy / clang-tidy (push) Has been cancelled
Lint / Validate SBOM (push) Has been cancelled
Lint / Enforce style (push) Has been cancelled
CI / Test windows-2022, 3.14, External, debug=0, unity_build=0, onnx_ml=1, autogen=0 (push) Has been cancelled
CI / Test windows-latest, 3.10, Internal, debug=0, unity_build=0, onnx_ml=1, autogen=0 (push) Has been cancelled
CI / Test windows-latest, 3.14, Internal, debug=0, unity_build=0, onnx_ml=1, autogen=0 (push) Has been cancelled
CI / Test windows-latest, 3.14t, Internal, debug=0, unity_build=0, onnx_ml=1, autogen=0 (push) Has been cancelled
CI / Test ubuntu-24.04, 3.14, Internal, debug=1, unity_build=0, onnx_ml=1, autogen=0 (push) Has been cancelled
CI / Test ubuntu-24.04, 3.14, External, debug=0, unity_build=1, onnx_ml=1, autogen=1 (push) Has been cancelled
CI / Test ubuntu-24.04, 3.14, External, debug=0, unity_build=0, onnx_ml=0, autogen=0 (push) Has been cancelled
CI / Test macos-latest, 3.10, Internal, debug=0, unity_build=0, onnx_ml=1, autogen=0 (push) Has been cancelled
CI / Test macos-latest, 3.14, Internal, debug=0, unity_build=0, onnx_ml=1, autogen=0 (push) Has been cancelled
CI / Test macos-latest, 3.14t, Internal, debug=0, unity_build=0, onnx_ml=1, autogen=0 (push) Has been cancelled
CI / Test ubuntu-24.04, 3.14, External, debug=0, unity_build=0, onnx_ml=1, autogen=0 (push) Has been cancelled
CI / Test ubuntu-24.04, 3.10, Internal, debug=0, unity_build=0, onnx_ml=1, autogen=0 (push) Has been cancelled
CI / Test ubuntu-24.04, 3.14, Internal, debug=0, unity_build=0, onnx_ml=1, autogen=0 (push) Has been cancelled
CI / Test ubuntu-24.04, 3.14t, Internal, debug=0, unity_build=0, onnx_ml=1, autogen=0 (push) Has been cancelled
Pixi CI / Install and lint (ubuntu-24.04-arm) (push) Has been cancelled
Pixi CI / Install and lint (windows-2022) (push) Has been cancelled
Pixi CI / Xcode generator build (push) Has been cancelled
Pixi CI / Install and test (macos-latest, default) (push) Has been cancelled
Pixi CI / Install and test (ubuntu-24.04-arm, default) (push) Has been cancelled
Pixi CI / Install and test (ubuntu-latest, default) (push) Has been cancelled
Pixi CI / Install and test (windows-2022, default) (push) Has been cancelled
Pixi CI / Install and test (macos-latest, oldies) (push) Has been cancelled
Pixi CI / Install and test (ubuntu-24.04-arm, oldies) (push) Has been cancelled
Pixi CI / Install and test (ubuntu-latest, oldies) (push) Has been cancelled
Pixi CI / Install and test (windows-2022, oldies) (push) Has been cancelled
815 lines
37 KiB
C++
815 lines
37 KiB
C++
// Copyright (c) ONNX Project Contributors
|
|
//
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
#include <nanobind/nanobind.h>
|
|
#include <nanobind/operators.h>
|
|
#include <nanobind/stl/function.h>
|
|
#include <nanobind/stl/pair.h>
|
|
#include <nanobind/stl/string.h>
|
|
#include <nanobind/stl/tuple.h>
|
|
#include <nanobind/stl/unordered_map.h>
|
|
#include <nanobind/stl/unordered_set.h>
|
|
#include <nanobind/stl/vector.h>
|
|
|
|
#include <algorithm>
|
|
#include <climits>
|
|
#include <limits>
|
|
#include <string>
|
|
#include <tuple>
|
|
#include <unordered_map>
|
|
#include <utility>
|
|
#include <vector>
|
|
|
|
#include "onnx/checker.h"
|
|
#include "onnx/common/ir_pb_converter.h"
|
|
#include "onnx/defs/parser.h"
|
|
#include "onnx/defs/printer.h"
|
|
#include "onnx/defs/schema.h"
|
|
#include "onnx/defs/shape_inference.h"
|
|
#include "onnx/inliner/inliner.h"
|
|
#include "onnx/py_utils.h"
|
|
#include "onnx/shape_inference/implementation.h"
|
|
#include "onnx/version_converter/convert.h"
|
|
|
|
#ifdef ONNX_USE_LITE_PROTO
|
|
using BASE_PROTO_TYPE = ::google::protobuf::MessageLite;
|
|
#else
|
|
using BASE_PROTO_TYPE = ::google::protobuf::Message;
|
|
#endif
|
|
|
|
// Generic type caster template for ONNX Proto classes
|
|
#define ONNX_DEFINE_TYPE_CASTER(ProtoType, PythonClassName) \
|
|
template <> \
|
|
struct nanobind::detail::type_caster<ONNX_NAMESPACE::ProtoType> { \
|
|
public: \
|
|
NB_TYPE_CASTER(ONNX_NAMESPACE::ProtoType, nanobind::detail::const_name(PythonClassName)); \
|
|
\
|
|
bool from_python(handle py_proto, uint8_t, cleanup_list*) noexcept { \
|
|
try { \
|
|
if (!nanobind::hasattr(py_proto, "SerializeToString")) { \
|
|
return false; \
|
|
} \
|
|
auto serialized = nanobind::cast<nanobind::bytes>(py_proto.attr("SerializeToString")()); \
|
|
return ParseProtoFromPyBytes(&value, serialized); \
|
|
} catch (...) { \
|
|
/* from_python is noexcept: nanobind::cast (cast_error) and ParseProtoFromPyBytes */ \
|
|
/* (bad_alloc) can throw non-python_error types, which would otherwise call terminate. */ \
|
|
return false; \
|
|
} \
|
|
} \
|
|
\
|
|
static handle from_cpp( \
|
|
const ONNX_NAMESPACE::ProtoType& cpp_proto, \
|
|
rv_policy /* policy */, \
|
|
cleanup_list* /* cleanup */) noexcept { \
|
|
try { \
|
|
std::string serialized = cpp_proto.SerializeAsString(); \
|
|
auto py_proto = nanobind::module_::import_("onnx").attr(#ProtoType)(); \
|
|
py_proto.attr("ParseFromString")(nanobind::bytes(serialized.c_str(), serialized.size())); \
|
|
return py_proto.release(); \
|
|
} catch (...) { \
|
|
return handle(); \
|
|
} \
|
|
} \
|
|
};
|
|
|
|
// Define type casters for common ONNX proto types
|
|
ONNX_DEFINE_TYPE_CASTER(AttributeProto, "onnx.AttributeProto")
|
|
ONNX_DEFINE_TYPE_CASTER(TypeProto, "onnx.TypeProto")
|
|
ONNX_DEFINE_TYPE_CASTER(TensorShapeProto, "onnx.TensorShapeProto")
|
|
ONNX_DEFINE_TYPE_CASTER(TensorProto, "onnx.TensorProto")
|
|
ONNX_DEFINE_TYPE_CASTER(SparseTensorProto, "onnx.SparseTensorProto")
|
|
ONNX_DEFINE_TYPE_CASTER(ValueInfoProto, "onnx.ValueInfoProto")
|
|
ONNX_DEFINE_TYPE_CASTER(NodeProto, "onnx.NodeProto")
|
|
ONNX_DEFINE_TYPE_CASTER(GraphProto, "onnx.GraphProto")
|
|
ONNX_DEFINE_TYPE_CASTER(ModelProto, "onnx.ModelProto")
|
|
ONNX_DEFINE_TYPE_CASTER(FunctionProto, "onnx.FunctionProto")
|
|
|
|
namespace ONNX_NAMESPACE {
|
|
namespace nb = nanobind;
|
|
|
|
// Serialize a proto message into Python bytes.
|
|
static nb::bytes ProtoToBytes(const BASE_PROTO_TYPE& proto) {
|
|
std::string out = proto.SerializeAsString();
|
|
return nb::bytes(out.data(), out.size());
|
|
}
|
|
|
|
// Parse a vector of Python bytes into a vector of protos.
|
|
template <typename ProtoType>
|
|
static std::vector<ProtoType> ParseProtoVector(const std::vector<nb::bytes>& bytes_vec) {
|
|
std::vector<ProtoType> protos(bytes_vec.size());
|
|
for (size_t i = 0; i < bytes_vec.size(); ++i) {
|
|
ParseProtoFromPyBytes(&protos[i], bytes_vec[i]);
|
|
}
|
|
return protos;
|
|
}
|
|
|
|
// Cast a proto pointer to a Python object, returning None when null.
|
|
template <typename T>
|
|
static nb::object CastOrNone(const T* ptr) {
|
|
return ptr ? nb::cast(*ptr) : nb::none();
|
|
}
|
|
|
|
template <typename ProtoType>
|
|
static std::tuple<bool, nb::bytes, nb::bytes> Parse(const char* cstr) {
|
|
ProtoType proto{};
|
|
OnnxParser parser(cstr);
|
|
auto status = parser.Parse(proto);
|
|
const std::string& error_msg = status.ErrorMessage();
|
|
return std::make_tuple(status.IsOK(), nb::bytes(error_msg.c_str(), error_msg.size()), ProtoToBytes(proto));
|
|
}
|
|
|
|
template <typename ProtoType>
|
|
static std::string ProtoBytesToText(const nb::bytes& bytes) {
|
|
ProtoType proto{};
|
|
ParseProtoFromPyBytes(&proto, bytes);
|
|
return ProtoToString(proto);
|
|
}
|
|
|
|
template <typename T, typename Ts = std::remove_const_t<T>>
|
|
static std::pair<std::vector<Ts>, std::unordered_map<std::string, T*>> ParseProtoFromBytesMap(
|
|
const std::unordered_map<std::string, nb::bytes>& bytesMap) {
|
|
std::vector<Ts> values(bytesMap.size());
|
|
std::unordered_map<std::string, T*> result;
|
|
size_t i = 0;
|
|
for (const auto& [key, bytes] : bytesMap) {
|
|
ParseProtoFromPyBytes(&values[i], bytes);
|
|
result[key] = &values[i];
|
|
i++;
|
|
}
|
|
// C++ guarantees that the pointers remain valid after std::vector<Ts> is moved.
|
|
return std::make_pair(std::move(values), result);
|
|
}
|
|
|
|
static std::unordered_map<std::string, nb::bytes> CallNodeInferenceFunction(
|
|
OpSchema* schema,
|
|
const nb::bytes& nodeBytes,
|
|
const std::unordered_map<std::string, nb::bytes>& valueTypesByNameBytes,
|
|
const std::unordered_map<std::string, nb::bytes>& inputDataByNameBytes,
|
|
const std::unordered_map<std::string, nb::bytes>& inputSparseDataByNameBytes,
|
|
std::unordered_map<std::string, int> opsetImports,
|
|
const int irVersion) {
|
|
NodeProto node{};
|
|
ParseProtoFromPyBytes(&node, nodeBytes);
|
|
// Early fail if node is badly defined - may throw ValidationError
|
|
schema->Verify(node);
|
|
|
|
// Convert arguments to C++ types, allocating memory
|
|
const auto& valueTypes = ParseProtoFromBytesMap<TypeProto>(valueTypesByNameBytes);
|
|
const auto& inputData = ParseProtoFromBytesMap<const TensorProto>(inputDataByNameBytes);
|
|
const auto& inputSparseData = ParseProtoFromBytesMap<const SparseTensorProto>(inputSparseDataByNameBytes);
|
|
if (opsetImports.empty()) {
|
|
opsetImports[schema->domain()] = schema->SinceVersion();
|
|
}
|
|
|
|
shape_inference::GraphInferenceContext graphInferenceContext(
|
|
valueTypes.second, opsetImports, nullptr, {}, OpSchemaRegistry::Instance(), nullptr, irVersion);
|
|
// Construct inference context and get results - may throw InferenceError
|
|
// TODO(ONNX): if it is desirable for infer_node_outputs to provide check_type, strict_mode, data_prop,
|
|
// we can add them to the Python API. For now we just assume the default options.
|
|
ShapeInferenceOptions options{false, 0, false};
|
|
shape_inference::InferenceContextImpl ctx(
|
|
node, valueTypes.second, inputData.second, inputSparseData.second, options, nullptr, &graphInferenceContext);
|
|
schema->GetTypeAndShapeInferenceFunction()(ctx);
|
|
// Verify the inference succeeded - may also throw ValidationError
|
|
// Note that input types were not validated until now (except that their count was correct)
|
|
schema->CheckInputOutputType(ctx);
|
|
|
|
// Convert back into bytes returned to Python
|
|
std::unordered_map<std::string, nb::bytes> typeProtoBytes;
|
|
for (size_t i = 0; i < ctx.allOutputTypes_.size(); i++) {
|
|
const auto& proto = ctx.allOutputTypes_[i];
|
|
if (proto.IsInitialized()) {
|
|
typeProtoBytes[node.output(static_cast<int>(i))] = ProtoToBytes(proto);
|
|
}
|
|
}
|
|
|
|
return typeProtoBytes;
|
|
}
|
|
|
|
template <typename T>
|
|
static std::tuple<std::vector<T>, std::vector<const T*>> ConvertPyObjToPtr(const std::vector<nb::object>& pyObjs) {
|
|
std::vector<T> objs;
|
|
std::vector<const T*> ptrs;
|
|
objs.reserve(pyObjs.size());
|
|
ptrs.reserve(pyObjs.size());
|
|
for (const auto& obj : pyObjs) {
|
|
if (obj.is_none()) {
|
|
ptrs.push_back(nullptr);
|
|
continue;
|
|
}
|
|
objs.emplace_back(nanobind::cast<T>(obj));
|
|
ptrs.push_back(&objs.back());
|
|
}
|
|
return std::make_tuple(std::move(objs), std::move(ptrs));
|
|
}
|
|
|
|
// Build a binding that parses a proto from Python bytes and runs the given
|
|
// checker function on it. The proto type and the trailing context arguments
|
|
// are deduced from the checker's signature, so this covers both the 2-arg
|
|
// (CheckerContext) and 3-arg (CheckerContext + LexicalScopeContext) checkers.
|
|
template <typename ProtoType, typename... Ctx>
|
|
static auto MakeChecker(void (*check_fn)(const ProtoType&, const Ctx&...)) {
|
|
return [check_fn](const nb::bytes& bytes, const Ctx&... ctx) {
|
|
ProtoType proto{};
|
|
ParseProtoFromPyBytes(&proto, bytes);
|
|
check_fn(proto, ctx...);
|
|
};
|
|
}
|
|
|
|
NB_MODULE(onnx_cpp2py_export, onnx_cpp2py_export) {
|
|
// Disabling nanobind leak warnings
|
|
// TODO(#7283): Avoid leaks if possible
|
|
nb::set_leak_warnings(false);
|
|
|
|
onnx_cpp2py_export.doc() = "Python interface to ONNX";
|
|
|
|
onnx_cpp2py_export.attr("ONNX_ML") = nb::bool_(
|
|
#ifdef ONNX_ML
|
|
true
|
|
#else // ONNX_ML
|
|
false
|
|
#endif // ONNX_ML
|
|
);
|
|
|
|
// Submodule `schema`
|
|
auto defs = onnx_cpp2py_export.def_submodule("defs");
|
|
defs.doc() = "Schema submodule";
|
|
nb::exception<SchemaError>(defs, "SchemaError"); // NOLINT(bugprone-unused-raii,bugprone-throw-keyword-missing)
|
|
|
|
nb::class_<OpSchema> op_schema(defs, "OpSchema", "Schema of an operator.");
|
|
|
|
// Define the class enums first because they are used as default values in function definitions
|
|
nb::enum_<OpSchema::FormalParameterOption>(op_schema, "FormalParameterOption", nb::is_arithmetic())
|
|
.value("Single", OpSchema::Single)
|
|
.value("Optional", OpSchema::Optional)
|
|
.value("Variadic", OpSchema::Variadic);
|
|
|
|
nb::enum_<OpSchema::DifferentiationCategory>(op_schema, "DifferentiationCategory", nb::is_arithmetic())
|
|
.value("Unknown", OpSchema::Unknown)
|
|
.value("Differentiable", OpSchema::Differentiable)
|
|
.value("NonDifferentiable", OpSchema::NonDifferentiable);
|
|
|
|
nb::enum_<OpSchema::NodeDeterminism>(op_schema, "NodeDeterminism")
|
|
.value("Deterministic", OpSchema::NodeDeterminism::Deterministic)
|
|
.value("NonDeterministic", OpSchema::NodeDeterminism::NonDeterministic)
|
|
.value("Unknown", OpSchema::NodeDeterminism::Unknown);
|
|
|
|
nb::enum_<AttributeProto::AttributeType>(op_schema, "AttrType", nb::is_arithmetic())
|
|
.value("FLOAT", AttributeProto::FLOAT)
|
|
.value("INT", AttributeProto::INT)
|
|
.value("STRING", AttributeProto::STRING)
|
|
.value("TENSOR", AttributeProto::TENSOR)
|
|
.value("GRAPH", AttributeProto::GRAPH)
|
|
.value("FLOATS", AttributeProto::FLOATS)
|
|
.value("INTS", AttributeProto::INTS)
|
|
.value("STRINGS", AttributeProto::STRINGS)
|
|
.value("TENSORS", AttributeProto::TENSORS)
|
|
.value("GRAPHS", AttributeProto::GRAPHS)
|
|
.value("SPARSE_TENSOR", AttributeProto::SPARSE_TENSOR)
|
|
.value("SPARSE_TENSORS", AttributeProto::SPARSE_TENSORS)
|
|
.value("TYPE_PROTO", AttributeProto::TYPE_PROTO)
|
|
.value("TYPE_PROTOS", AttributeProto::TYPE_PROTOS);
|
|
|
|
nb::enum_<OpSchema::SupportType>(op_schema, "SupportType", nb::is_arithmetic())
|
|
.value("COMMON", OpSchema::SupportType::COMMON)
|
|
.value("EXPERIMENTAL", OpSchema::SupportType::EXPERIMENTAL);
|
|
|
|
nb::class_<OpSchema::Attribute>(op_schema, "Attribute")
|
|
.def(
|
|
"__init__",
|
|
[](OpSchema::Attribute* self,
|
|
std::string name,
|
|
AttributeProto::AttributeType type,
|
|
std::string description,
|
|
bool required) {
|
|
// Construct an attribute.
|
|
// Use a lambda to swap the order of the arguments to match the Python API
|
|
new (self) OpSchema::Attribute(std::move(name), std::move(description), type, required);
|
|
},
|
|
nb::arg("name"),
|
|
nb::arg("type"),
|
|
nb::arg("description") = "",
|
|
nb::kw_only(),
|
|
nb::arg("required") = true)
|
|
.def(
|
|
"__init__",
|
|
[](OpSchema::Attribute* self, std::string name, const nb::object& default_value, std::string description) {
|
|
// Construct an attribute with a default value.
|
|
// Attributes with default values are not required
|
|
auto bytes = nb::cast<nb::bytes>(default_value.attr("SerializeToString")());
|
|
AttributeProto proto{};
|
|
ParseProtoFromPyBytes(&proto, bytes);
|
|
new (self) OpSchema::Attribute(std::move(name), std::move(description), std::move(proto));
|
|
},
|
|
nb::arg("name"),
|
|
nb::arg("default_value"), // type: onnx.AttributeProto
|
|
nb::arg("description") = "")
|
|
.def_ro("name", &OpSchema::Attribute::name)
|
|
.def_ro("description", &OpSchema::Attribute::description)
|
|
.def_ro("type", &OpSchema::Attribute::type)
|
|
.def_prop_ro(
|
|
"_default_value", [](OpSchema::Attribute* attr) -> nb::bytes { return ProtoToBytes(attr->default_value); })
|
|
.def_ro("required", &OpSchema::Attribute::required);
|
|
|
|
nb::class_<OpSchema::TypeConstraintParam>(op_schema, "TypeConstraintParam")
|
|
.def(
|
|
nb::init<std::string, std::vector<std::string>, std::string>(),
|
|
nb::arg("type_param_str"),
|
|
nb::arg("allowed_type_strs"),
|
|
nb::arg("description") = "")
|
|
.def_ro("type_param_str", &OpSchema::TypeConstraintParam::type_param_str)
|
|
.def_ro("allowed_type_strs", &OpSchema::TypeConstraintParam::allowed_type_strs)
|
|
.def_ro("description", &OpSchema::TypeConstraintParam::description);
|
|
|
|
nb::class_<OpSchema::FormalParameter>(op_schema, "FormalParameter")
|
|
.def(
|
|
"__init__",
|
|
[](OpSchema::FormalParameter* self,
|
|
std::string name,
|
|
std::string type_str,
|
|
const std::string& description,
|
|
OpSchema::FormalParameterOption param_option,
|
|
bool is_homogeneous,
|
|
int min_arity,
|
|
OpSchema::DifferentiationCategory differentiation_category) {
|
|
// Use a lambda to swap the order of the arguments to match the Python API
|
|
new (self) OpSchema::FormalParameter(
|
|
std::move(name),
|
|
description,
|
|
std::move(type_str),
|
|
param_option,
|
|
is_homogeneous,
|
|
min_arity,
|
|
differentiation_category);
|
|
},
|
|
nb::arg("name"),
|
|
nb::arg("type_str"),
|
|
nb::arg("description") = "",
|
|
nb::kw_only(),
|
|
nb::arg("param_option") = OpSchema::Single,
|
|
nb::arg("is_homogeneous") = true,
|
|
nb::arg("min_arity") = 1,
|
|
nb::arg("differentiation_category") = OpSchema::DifferentiationCategory::Unknown)
|
|
|
|
.def_prop_ro("name", &OpSchema::FormalParameter::GetName)
|
|
.def_prop_ro("types", &OpSchema::FormalParameter::GetTypes)
|
|
.def_prop_ro("type_str", &OpSchema::FormalParameter::GetTypeStr)
|
|
.def_prop_ro("description", &OpSchema::FormalParameter::GetDescription)
|
|
.def_prop_ro("option", &OpSchema::FormalParameter::GetOption)
|
|
.def_prop_ro("is_homogeneous", &OpSchema::FormalParameter::GetIsHomogeneous)
|
|
.def_prop_ro("min_arity", &OpSchema::FormalParameter::GetMinArity)
|
|
.def_prop_ro("differentiation_category", &OpSchema::FormalParameter::GetDifferentiationCategory);
|
|
|
|
op_schema
|
|
.def(
|
|
"__init__",
|
|
[](OpSchema* self,
|
|
std::string name,
|
|
std::string domain,
|
|
int since_version,
|
|
const std::string& doc,
|
|
std::vector<OpSchema::FormalParameter> inputs,
|
|
std::vector<OpSchema::FormalParameter> outputs,
|
|
std::vector<std::tuple<std::string, std::vector<std::string>, std::string>> type_constraints,
|
|
std::vector<OpSchema::Attribute> attributes,
|
|
OpSchema::NodeDeterminism node_determinism) {
|
|
new (self) OpSchema();
|
|
|
|
self->SetName(std::move(name)).SetDomain(std::move(domain)).SinceVersion(since_version).SetDoc(doc);
|
|
self->SetNodeDeterminism(node_determinism);
|
|
// Add inputs and outputs
|
|
for (size_t i = 0; i < inputs.size(); ++i) {
|
|
self->Input(static_cast<int>(i), std::move(inputs[i]));
|
|
}
|
|
for (size_t i = 0; i < outputs.size(); ++i) {
|
|
self->Output(static_cast<int>(i), std::move(outputs[i]));
|
|
}
|
|
// Add type constraints
|
|
for (auto& [type_str, constraints, description] : type_constraints) {
|
|
self->TypeConstraint(std::move(type_str), std::move(constraints), std::move(description));
|
|
}
|
|
// Add attributes
|
|
for (auto& attribute : attributes) {
|
|
self->Attr(std::move(attribute));
|
|
}
|
|
|
|
self->Finalize();
|
|
},
|
|
nb::arg("name"),
|
|
nb::arg("domain"),
|
|
nb::arg("since_version"),
|
|
nb::arg("doc") = "",
|
|
nb::kw_only(),
|
|
nb::arg("inputs") = std::vector<OpSchema::FormalParameter>{},
|
|
nb::arg("outputs") = std::vector<OpSchema::FormalParameter>{},
|
|
nb::arg("type_constraints") = std::vector<std::tuple<
|
|
std::string /* type_str */,
|
|
std::vector<std::string> /* constraints */,
|
|
std::string /* description */>>{},
|
|
nb::arg("attributes") = std::vector<OpSchema::Attribute>{},
|
|
nb::arg("node_determinism") = OpSchema::NodeDeterminism::Unknown)
|
|
.def_prop_rw("name", &OpSchema::Name, [](OpSchema& self, const std::string& name) { self.SetName(name); })
|
|
.def_prop_rw(
|
|
"domain", &OpSchema::domain, [](OpSchema& self, const std::string& domain) { self.SetDomain(domain); })
|
|
.def_prop_rw("doc", &OpSchema::doc, [](OpSchema& self, const std::string& doc) { self.SetDoc(doc); })
|
|
.def_prop_ro("file", &OpSchema::file)
|
|
.def_prop_ro("line", &OpSchema::line)
|
|
.def_prop_ro("support_level", &OpSchema::support_level)
|
|
.def_prop_ro("since_version", &OpSchema::since_version)
|
|
.def_prop_ro("deprecated", &OpSchema::deprecated)
|
|
.def_prop_ro("function_opset_versions", &OpSchema::function_opset_versions)
|
|
.def_prop_ro("context_dependent_function_opset_versions", &OpSchema::context_dependent_function_opset_versions)
|
|
.def_prop_ro(
|
|
"all_function_opset_versions",
|
|
[](OpSchema* op) -> std::vector<int> {
|
|
std::vector<int> all_function_opset_versions = op->function_opset_versions();
|
|
std::vector<int> context_dependent_function_opset_versions =
|
|
op->context_dependent_function_opset_versions();
|
|
all_function_opset_versions.insert(
|
|
all_function_opset_versions.end(),
|
|
context_dependent_function_opset_versions.begin(),
|
|
context_dependent_function_opset_versions.end());
|
|
std::sort(all_function_opset_versions.begin(), all_function_opset_versions.end());
|
|
all_function_opset_versions.erase(
|
|
std::unique(all_function_opset_versions.begin(), all_function_opset_versions.end()),
|
|
all_function_opset_versions.end());
|
|
return all_function_opset_versions;
|
|
})
|
|
.def_prop_ro("min_input", &OpSchema::min_input)
|
|
.def_prop_ro("max_input", &OpSchema::max_input)
|
|
.def_prop_ro("min_output", &OpSchema::min_output)
|
|
.def_prop_ro("max_output", &OpSchema::max_output)
|
|
.def_prop_ro("attributes", &OpSchema::attributes)
|
|
.def_prop_ro("inputs", &OpSchema::inputs)
|
|
.def_prop_ro("outputs", &OpSchema::outputs)
|
|
.def_prop_ro("has_type_and_shape_inference_function", &OpSchema::has_type_and_shape_inference_function)
|
|
.def_prop_ro("has_data_propagation_function", &OpSchema::has_data_propagation_function)
|
|
.def_prop_ro("type_constraints", &OpSchema::typeConstraintParams)
|
|
.def_prop_ro("node_determinism", &OpSchema::GetNodeDeterminism)
|
|
.def_static("is_infinite", [](int v) { return v == std::numeric_limits<int>::max(); })
|
|
.def(
|
|
"_infer_node_outputs",
|
|
CallNodeInferenceFunction,
|
|
nb::arg("nodeBytes"),
|
|
nb::arg("valueTypesByNameBytes"),
|
|
nb::arg("inputDataByNameBytes") = std::unordered_map<std::string, nb::bytes>{},
|
|
nb::arg("inputSparseDataByNameBytes") = std::unordered_map<std::string, nb::bytes>{},
|
|
nb::arg("opsetImports") = std::unordered_map<std::string, int>{},
|
|
nb::arg("irVersion") = static_cast<int>(IR_VERSION))
|
|
.def_prop_ro("has_function", &OpSchema::HasFunction)
|
|
.def_prop_ro(
|
|
"_function_body",
|
|
[](OpSchema* op) -> nb::bytes {
|
|
return op->HasFunction() ? ProtoToBytes(*op->GetFunction()) : nb::bytes("", 0);
|
|
})
|
|
.def(
|
|
"get_function_with_opset_version",
|
|
[](OpSchema* op, int opset_version) -> nb::bytes {
|
|
const FunctionProto* function_proto = op->GetFunction(opset_version);
|
|
return function_proto ? ProtoToBytes(*function_proto) : nb::bytes("", 0);
|
|
})
|
|
.def_prop_ro("has_context_dependent_function", &OpSchema::HasContextDependentFunction)
|
|
.def(
|
|
"get_context_dependent_function",
|
|
[](OpSchema* op, const nb::bytes& bytes, const std::vector<nb::bytes>& input_types_bytes) -> nb::bytes {
|
|
NodeProto proto{};
|
|
ParseProtoFromPyBytes(&proto, bytes);
|
|
if (op->HasContextDependentFunction()) {
|
|
std::vector<TypeProto> input_types = ParseProtoVector<TypeProto>(input_types_bytes);
|
|
FunctionBodyBuildContextImpl ctx(proto, input_types);
|
|
FunctionProto func_proto;
|
|
op->BuildContextDependentFunction(ctx, func_proto);
|
|
return ProtoToBytes(func_proto);
|
|
}
|
|
return nb::bytes("", 0);
|
|
})
|
|
.def(
|
|
"get_context_dependent_function_with_opset_version",
|
|
[](OpSchema* op, int opset_version, const nb::bytes& bytes, const std::vector<nb::bytes>& input_types_bytes)
|
|
-> nb::bytes {
|
|
NodeProto proto{};
|
|
ParseProtoFromPyBytes(&proto, bytes);
|
|
if (op->HasContextDependentFunctionWithOpsetVersion(opset_version)) {
|
|
std::vector<TypeProto> input_types = ParseProtoVector<TypeProto>(input_types_bytes);
|
|
FunctionBodyBuildContextImpl ctx(proto, input_types);
|
|
FunctionProto func_proto;
|
|
op->BuildContextDependentFunction(ctx, func_proto, opset_version);
|
|
return ProtoToBytes(func_proto);
|
|
}
|
|
return nb::bytes("", 0);
|
|
})
|
|
.def(
|
|
"set_type_and_shape_inference_function",
|
|
[](OpSchema& op, const std::function<void(InferenceContext*)>& func) -> OpSchema& {
|
|
auto wrapper = [=](InferenceContext& ctx) { func(&ctx); };
|
|
return op.TypeAndShapeInferenceFunction(wrapper);
|
|
},
|
|
nb::rv_policy::reference_internal)
|
|
.def("get_type_and_shape_inference_function", &OpSchema::GetTypeAndShapeInferenceFunction);
|
|
|
|
defs.def(
|
|
"has_schema",
|
|
[](const std::string& op_type, const std::string& domain) -> bool {
|
|
return OpSchemaRegistry::Schema(op_type, domain) != nullptr;
|
|
},
|
|
nb::arg("op_type"),
|
|
nb::arg("domain") = ONNX_DOMAIN)
|
|
.def(
|
|
"has_schema",
|
|
[](const std::string& op_type, int max_inclusive_version, const std::string& domain) -> bool {
|
|
return OpSchemaRegistry::Schema(op_type, max_inclusive_version, domain) != nullptr;
|
|
},
|
|
nb::arg("op_type"),
|
|
nb::arg("max_inclusive_version"),
|
|
nb::arg("domain") = ONNX_DOMAIN)
|
|
.def(
|
|
"schema_version_map",
|
|
[]() -> std::unordered_map<std::string, std::pair<int, int>> {
|
|
return OpSchemaRegistry::DomainToVersionRange::Instance().Map();
|
|
})
|
|
.def(
|
|
"get_schema",
|
|
[](const std::string& op_type, const int max_inclusive_version, const std::string& domain) -> OpSchema {
|
|
const auto* const schema = OpSchemaRegistry::Schema(op_type, max_inclusive_version, domain);
|
|
if (!schema) {
|
|
fail_schema(
|
|
"No schema registered for '" + op_type + "' version '" + std::to_string(max_inclusive_version) +
|
|
"' and domain '" + domain + "'!");
|
|
}
|
|
return *schema;
|
|
},
|
|
nb::arg("op_type"),
|
|
nb::arg("max_inclusive_version"),
|
|
nb::arg("domain") = ONNX_DOMAIN,
|
|
"Return the schema of the operator *op_type* and for a specific version.")
|
|
.def(
|
|
"get_schema",
|
|
[](const std::string& op_type, const std::string& domain) -> OpSchema {
|
|
const auto* const schema = OpSchemaRegistry::Schema(op_type, domain);
|
|
if (!schema) {
|
|
fail_schema("No schema registered for '" + op_type + "' and domain '" + domain + "'!");
|
|
}
|
|
return *schema;
|
|
},
|
|
nb::arg("op_type"),
|
|
nb::arg("domain") = ONNX_DOMAIN,
|
|
"Return the schema of the operator *op_type* and for a specific version.")
|
|
.def(
|
|
"get_all_schemas",
|
|
[]() -> std::vector<OpSchema> { return OpSchemaRegistry::get_all_schemas(); },
|
|
"Return the schema of all existing operators for the latest version.")
|
|
.def(
|
|
"get_all_schemas_with_history",
|
|
[]() -> std::vector<OpSchema> { return OpSchemaRegistry::get_all_schemas_with_history(); },
|
|
"Return the schema of all existing operators and all versions.")
|
|
.def(
|
|
"set_domain_to_version",
|
|
[](const std::string& domain, int min_version, int max_version, int last_release_version) {
|
|
auto& obj = OpSchemaRegistry::DomainToVersionRange::Instance();
|
|
if (obj.Map().count(domain) == 0) {
|
|
obj.AddDomainToVersion(domain, min_version, max_version, last_release_version);
|
|
} else {
|
|
obj.UpdateDomainToVersion(domain, min_version, max_version, last_release_version);
|
|
}
|
|
},
|
|
nb::arg("domain"),
|
|
nb::arg("min_version"),
|
|
nb::arg("max_version"),
|
|
nb::arg("last_release_version") = -1,
|
|
"Set the version range and last release version of the specified domain.")
|
|
.def(
|
|
"register_schema",
|
|
[](OpSchema schema) { RegisterSchema(std::move(schema), 0, true, true); },
|
|
nb::arg("schema"),
|
|
"Register a user provided OpSchema.")
|
|
.def(
|
|
"deregister_schema",
|
|
&DeregisterSchema,
|
|
nb::arg("op_type"),
|
|
nb::arg("version"),
|
|
nb::arg("domain"),
|
|
"Deregister the specified OpSchema.");
|
|
|
|
// Submodule `checker`
|
|
auto checker = onnx_cpp2py_export.def_submodule("checker");
|
|
checker.doc() = "Checker submodule";
|
|
|
|
nb::class_<checker::CheckerContext> checker_context(checker, "CheckerContext");
|
|
checker_context.def(nb::init<>())
|
|
.def_prop_rw("ir_version", &checker::CheckerContext::get_ir_version, &checker::CheckerContext::set_ir_version)
|
|
.def_prop_rw(
|
|
"opset_imports", &checker::CheckerContext::get_opset_imports, &checker::CheckerContext::set_opset_imports);
|
|
|
|
nb::class_<checker::LexicalScopeContext> lexical_scope_context(checker, "LexicalScopeContext");
|
|
lexical_scope_context.def(nb::init<>());
|
|
|
|
nb::exception<checker::ValidationError>( // NOLINT(bugprone-unused-raii,bugprone-throw-keyword-missing)
|
|
checker,
|
|
"ValidationError");
|
|
|
|
checker.def("check_value_info", MakeChecker(checker::check_value_info));
|
|
checker.def("check_tensor", MakeChecker(checker::check_tensor));
|
|
checker.def("check_sparse_tensor", MakeChecker(checker::check_sparse_tensor));
|
|
checker.def("check_attribute", MakeChecker(checker::check_attribute));
|
|
checker.def("check_node", MakeChecker(checker::check_node));
|
|
checker.def("check_function", MakeChecker(checker::check_function));
|
|
checker.def("check_graph", MakeChecker(checker::check_graph));
|
|
|
|
checker.def(
|
|
"check_model",
|
|
[](const nb::bytes& bytes, bool full_check, bool skip_opset_compatibility_check, bool check_custom_domain)
|
|
-> void {
|
|
ModelProto proto{};
|
|
ParseProtoFromPyBytes(&proto, bytes);
|
|
checker::check_model(proto, full_check, skip_opset_compatibility_check, check_custom_domain);
|
|
},
|
|
nb::arg("bytes"),
|
|
nb::arg("full_check") = false,
|
|
nb::arg("skip_opset_compatibility_check") = false,
|
|
nb::arg("check_custom_domain") = false);
|
|
|
|
checker.def(
|
|
"check_model_path",
|
|
static_cast<void (*)(
|
|
const std::string& path, bool full_check, bool skip_opset_compatibility_check, bool check_custom_domain)>(
|
|
&checker::check_model),
|
|
nb::arg("path"),
|
|
nb::arg("full_check") = false,
|
|
nb::arg("skip_opset_compatibility_check") = false,
|
|
nb::arg("check_custom_domain") = false);
|
|
|
|
checker.def("_open_external_data", &checker::open_external_data);
|
|
|
|
// Submodule `version_converter`
|
|
auto version_converter = onnx_cpp2py_export.def_submodule("version_converter");
|
|
version_converter.doc() = "VersionConverter submodule";
|
|
nb::exception<ConvertError>( // NOLINT(bugprone-unused-raii,bugprone-throw-keyword-missing)
|
|
version_converter,
|
|
"ConvertError");
|
|
|
|
version_converter.def("convert_version", [](const nb::bytes& bytes, int target) {
|
|
ModelProto proto{};
|
|
ParseProtoFromPyBytes(&proto, bytes);
|
|
shape_inference::InferShapes(proto);
|
|
return ProtoToBytes(version_conversion::ConvertVersion(proto, target));
|
|
});
|
|
|
|
// Submodule `inliner`
|
|
auto inliner = onnx_cpp2py_export.def_submodule("inliner");
|
|
inliner.doc() = "Inliner submodule";
|
|
|
|
inliner.def("inline_local_functions", [](const nb::bytes& bytes, bool convert_version) {
|
|
ModelProto model{};
|
|
ParseProtoFromPyBytes(&model, bytes);
|
|
inliner::InlineLocalFunctions(model, convert_version);
|
|
return ProtoToBytes(model);
|
|
});
|
|
|
|
// inline_selected_functions: Inlines all functions specified in function_ids, unless
|
|
// exclude is true, in which case it inlines all functions except those specified in
|
|
// function_ids.
|
|
inliner.def(
|
|
"inline_selected_functions",
|
|
[](const nb::bytes& bytes, std::vector<std::pair<std::string, std::string>> function_ids, bool exclude) {
|
|
ModelProto model{};
|
|
ParseProtoFromPyBytes(&model, bytes);
|
|
auto function_id_set = inliner::FunctionIdSet::Create(std::move(function_ids), exclude);
|
|
inliner::InlineSelectedLocalFunctions(model, *function_id_set);
|
|
return ProtoToBytes(model);
|
|
});
|
|
|
|
inliner.def(
|
|
"inline_selected_functions2",
|
|
[](const nb::bytes& bytes, std::vector<std::pair<std::string, std::string>> function_ids, bool exclude) {
|
|
ModelProto model{};
|
|
ParseProtoFromPyBytes(&model, bytes);
|
|
auto function_id_set = inliner::FunctionIdSet::Create(std::move(function_ids), exclude);
|
|
inliner::InlineSelectedFunctions(model, *function_id_set, nullptr);
|
|
return ProtoToBytes(model);
|
|
});
|
|
|
|
// Submodule `shape_inference`
|
|
auto shape_inference = onnx_cpp2py_export.def_submodule("shape_inference");
|
|
shape_inference.doc() = "Shape Inference submodule";
|
|
nb::exception<InferenceError>( // NOLINT(bugprone-unused-raii,bugprone-throw-keyword-missing)
|
|
shape_inference,
|
|
"InferenceError");
|
|
|
|
nb::class_<InferenceContext> inference_context(shape_inference, "InferenceContext", "Inference context");
|
|
|
|
inference_context.def("get_attribute", [](InferenceContext& self, const std::string& name) {
|
|
return CastOrNone(self.getAttribute(name));
|
|
});
|
|
inference_context.def("get_num_inputs", &InferenceContext::getNumInputs);
|
|
inference_context.def(
|
|
"get_input_type", [](InferenceContext& self, size_t idx) { return CastOrNone(self.getInputType(idx)); });
|
|
inference_context.def("has_input", &InferenceContext::hasInput);
|
|
inference_context.def(
|
|
"get_input_data", [](InferenceContext& self, size_t idx) { return CastOrNone(self.getInputData(idx)); });
|
|
inference_context.def("get_num_outputs", &InferenceContext::getNumOutputs);
|
|
inference_context.def(
|
|
"get_output_type", [](InferenceContext& self, size_t idx) { return CastOrNone(self.getOutputType(idx)); });
|
|
inference_context.def("set_output_type", [](InferenceContext& self, size_t idx, const TypeProto& src) {
|
|
auto* dst = self.getOutputType(idx);
|
|
if (dst == nullptr) {
|
|
return false;
|
|
}
|
|
dst->CopyFrom(src);
|
|
return true;
|
|
});
|
|
inference_context.def("has_output", &InferenceContext::hasOutput);
|
|
inference_context.def(
|
|
"get_graph_attribute_inferencer",
|
|
&InferenceContext::getGraphAttributeInferencer,
|
|
nb::rv_policy::reference_internal);
|
|
inference_context.def("get_input_sparse_data", [](InferenceContext& self, size_t idx) {
|
|
return CastOrNone(self.getInputSparseData(idx));
|
|
});
|
|
inference_context.def(
|
|
"get_symbolic_input", [](InferenceContext& self, size_t idx) { return CastOrNone(self.getSymbolicInput(idx)); });
|
|
inference_context.def("get_display_name", &InferenceContext::getDisplayName);
|
|
|
|
nb::class_<GraphInferencer> graph_inferencer(shape_inference, "GraphInferencer", "Graph Inferencer");
|
|
graph_inferencer.def(
|
|
"do_inferencing",
|
|
[](GraphInferencer& self,
|
|
const std::vector<nb::object>& inputTypesObj,
|
|
const std::vector<nb::object>& inputDataObj) {
|
|
auto inputTypesTuple = ConvertPyObjToPtr<ONNX_NAMESPACE::TypeProto>(inputTypesObj);
|
|
auto inputDataTuple = ConvertPyObjToPtr<ONNX_NAMESPACE::TensorProto>(inputDataObj);
|
|
auto ret = self.doInferencing(std::get<1>(inputTypesTuple), std::get<1>(inputDataTuple));
|
|
std::vector<nb::object> ret_obj(ret.size());
|
|
for (size_t i = 0; i < ret.size(); ++i) {
|
|
ret_obj[i] = nb::cast(ret[i]);
|
|
}
|
|
return ret_obj;
|
|
});
|
|
|
|
shape_inference.def(
|
|
"infer_shapes",
|
|
[](const nb::bytes& bytes, bool check_type, bool strict_mode, bool data_prop) {
|
|
ModelProto proto{};
|
|
ParseProtoFromPyBytes(&proto, bytes);
|
|
ShapeInferenceOptions options{check_type, strict_mode ? 1 : 0, data_prop};
|
|
shape_inference::InferShapes(proto, OpSchemaRegistry::Instance(), options);
|
|
return ProtoToBytes(proto);
|
|
},
|
|
nb::arg("bytes"),
|
|
nb::arg("check_type") = false,
|
|
nb::arg("strict_mode") = false,
|
|
nb::arg("data_prop") = false);
|
|
|
|
shape_inference.def(
|
|
"infer_shapes_path",
|
|
[](const std::string& model_path,
|
|
const std::string& output_path,
|
|
bool check_type,
|
|
bool strict_mode,
|
|
bool data_prop) -> void {
|
|
ShapeInferenceOptions options{check_type, strict_mode ? 1 : 0, data_prop};
|
|
shape_inference::InferShapes(model_path, output_path, OpSchemaRegistry::Instance(), options);
|
|
});
|
|
|
|
shape_inference.def(
|
|
"infer_function_output_types",
|
|
[](const nb::bytes& function_proto_bytes,
|
|
const std::vector<nb::bytes>& input_types_bytes,
|
|
const std::vector<nb::bytes>& attributes_bytes) -> std::vector<nb::bytes> {
|
|
FunctionProto proto{};
|
|
ParseProtoFromPyBytes(&proto, function_proto_bytes);
|
|
|
|
std::vector<TypeProto> input_types = ParseProtoVector<TypeProto>(input_types_bytes);
|
|
std::vector<AttributeProto> attributes = ParseProtoVector<AttributeProto>(attributes_bytes);
|
|
|
|
std::vector<TypeProto> output_types = shape_inference::InferFunctionOutputTypes(proto, input_types, attributes);
|
|
std::vector<nb::bytes> result;
|
|
result.reserve(output_types.size());
|
|
for (const auto& type_proto : output_types) {
|
|
result.push_back(ProtoToBytes(type_proto));
|
|
}
|
|
return result;
|
|
});
|
|
|
|
// Submodule `parser`
|
|
auto parser = onnx_cpp2py_export.def_submodule("parser");
|
|
parser.doc() = "Parser submodule";
|
|
|
|
parser.def("parse_model", Parse<ModelProto>);
|
|
parser.def("parse_graph", Parse<GraphProto>);
|
|
parser.def("parse_function", Parse<FunctionProto>);
|
|
parser.def("parse_node", Parse<NodeProto>);
|
|
|
|
// Submodule `printer`
|
|
auto printer = onnx_cpp2py_export.def_submodule("printer");
|
|
printer.doc() = "Printer submodule";
|
|
|
|
printer.def("model_to_text", ProtoBytesToText<ModelProto>);
|
|
printer.def("function_to_text", ProtoBytesToText<FunctionProto>);
|
|
printer.def("graph_to_text", ProtoBytesToText<GraphProto>);
|
|
printer.def("node_to_text", ProtoBytesToText<NodeProto>);
|
|
}
|
|
|
|
} // namespace ONNX_NAMESPACE
|