Custom op shape inference API (#17737)

Add c/cxx API to allow custom ops do shape  inference.

---------

Co-authored-by: Randy Shuai <rashuai@microsoft.com>
This commit is contained in:
RandySheriffH 2023-10-13 12:57:42 -07:00 committed by GitHub
parent 762703e037
commit c6c3555d0e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 771 additions and 25 deletions

View file

@ -299,6 +299,7 @@ ORT_RUNTIME_CLASS(DnnlProviderOptions);
ORT_RUNTIME_CLASS(Op);
ORT_RUNTIME_CLASS(OpAttr);
ORT_RUNTIME_CLASS(Logger);
ORT_RUNTIME_CLASS(ShapeInferContext);
#ifdef _WIN32
typedef _Return_type_success_(return == 0) OrtStatus* OrtStatusPtr;
@ -4444,6 +4445,73 @@ struct OrtApi {
*/
ORT_API2_STATUS(SetUserLoggingFunction, _Inout_ OrtSessionOptions* options,
_In_ OrtLoggingFunction user_logging_function, _In_opt_ void* user_logging_param);
/**
* Get number of input from OrtShapeInferContext
*
* \param[in] context
* \param[out] out The number of inputs
*
* \since Version 1.17.
*/
ORT_API2_STATUS(ShapeInferContext_GetInputCount, _In_ const OrtShapeInferContext* context, _Out_ size_t* out);
/**
* Get type and shape info of an input
*
* \param[in] context
* \param[in] index The index of the input
* \param[out] info Type shape info of the input
*
* \since Version 1.17.
*/
ORT_API2_STATUS(ShapeInferContext_GetInputTypeShape, _In_ const OrtShapeInferContext* context, _In_ size_t index, _Outptr_ OrtTensorTypeAndShapeInfo** info);
/**
* Get attribute from OrtShapeInferContext. Note that OrtShapeInferContext is a per-node context, one could only read attribute from current node.
*
* \param[in] context
* \param[in] attr_name Name of the attribute
* \param[out] attr Handle of the attribute fetched
*
* \since Version 1.17.
*/
ORT_API2_STATUS(ShapeInferContext_GetAttribute, _In_ const OrtShapeInferContext* context, _In_ const char* attr_name, _Outptr_ const OrtOpAttr** attr);
/**
* Set type and shape info of an ouput
*
* \param[in] context
* \param[in] index The index of the ouput
* \param[out] info Type shape info of the output
*
* \since Version 1.17.
*/
ORT_API2_STATUS(ShapeInferContext_SetOutputTypeShape, _In_ const OrtShapeInferContext* context, _In_ size_t index, _In_ const OrtTensorTypeAndShapeInfo* info);
/**
* Set symbolic shape to type shape info
*
* \param[in] info Type shape info
* \param[in] dim_params Symbolic strings
* \param[in] dim_params_length Number of strings
*
* \since Version 1.17.
*/
ORT_API2_STATUS(SetSymbolicDimensions, _In_ OrtTensorTypeAndShapeInfo* info, _In_ const char* dim_params[], _In_ size_t dim_params_length);
/**
* Read contents of an attribute to data
*
* \param[in] op_attr
* \param[in] type Attribute type
* \param[out] data Memory address to save raw content of the attribute
* \param[in] len Number of bytes allowed to store in data
* \param[out] out Number of bytes required to save the data when the call failed, or the real number of bytes saved to data on success
*
* \since Version 1.17.
*/
ORT_API2_STATUS(ReadOpAttr, _In_ const OrtOpAttr* op_attr, _In_ OrtOpAttrType type, _Inout_ void* data, _In_ size_t len, _Out_ size_t* out);
};
/*
@ -4535,6 +4603,8 @@ struct OrtCustomOp {
// Perform the computation step.
OrtStatusPtr(ORT_API_CALL* KernelComputeV2)(_In_ void* op_kernel, _In_ OrtKernelContext* context);
OrtStatusPtr(ORT_API_CALL* InferOutputShapeFn)(_In_ const struct OrtCustomOp* op, _In_ OrtShapeInferContext*);
};
/*

View file

@ -2156,6 +2156,78 @@ struct Op : detail::Base<OrtOp> {
size_t output_count);
};
/// <summary>
/// Provide access to per-node attributes and input shapes, so one could compute and set output shapes.
/// </summary>
struct ShapeInferContext {
struct SymbolicInteger {
SymbolicInteger(int64_t i) : i_(i), is_int_(true){};
SymbolicInteger(const char* s) : s_(s), is_int_(false){};
SymbolicInteger(const SymbolicInteger&) = default;
SymbolicInteger(SymbolicInteger&&) = default;
SymbolicInteger& operator=(const SymbolicInteger&) = default;
SymbolicInteger& operator=(SymbolicInteger&&) = default;
bool operator==(const SymbolicInteger& dim) const {
if (is_int_ == dim.is_int_) {
if (is_int_) {
return i_ == dim.i_;
} else {
return std::string{s_} == std::string{dim.s_};
}
}
return false;
}
bool IsInt() const { return is_int_; }
int64_t AsInt() const { return i_; }
const char* AsSym() const { return s_; }
static constexpr int INVALID_INT_DIM = -2;
private:
union {
int64_t i_;
const char* s_;
};
bool is_int_;
};
using Shape = std::vector<SymbolicInteger>;
ShapeInferContext(const OrtApi* ort_api, OrtShapeInferContext* ctx);
const Shape& GetInputShape(size_t indice) const { return input_shapes_.at(indice); }
size_t GetInputCount() const { return input_shapes_.size(); }
Status SetOutputShape(size_t indice, const Shape& shape);
int64_t GetAttrInt(const char* attr_name);
using Ints = std::vector<int64_t>;
Ints GetAttrInts(const char* attr_name);
float GetAttrFloat(const char* attr_name);
using Floats = std::vector<float>;
Floats GetAttrFloats(const char* attr_name);
std::string GetAttrString(const char* attr_name);
using Strings = std::vector<std::string>;
Strings GetAttrStrings(const char* attr_name);
private:
const OrtOpAttr* GetAttrHdl(const char* attr_name) const;
const OrtApi* ort_api_;
OrtShapeInferContext* ctx_;
std::vector<Shape> input_shapes_;
};
using ShapeInferFn = Ort::Status (*)(Ort::ShapeInferContext&);
template <typename TOp, typename TKernel, bool WithStatus = false>
struct CustomOpBase : OrtCustomOp {
CustomOpBase() {
@ -2206,6 +2278,8 @@ struct CustomOpBase : OrtCustomOp {
static_cast<TKernel*>(op_kernel)->Compute(context);
};
}
SetShapeInferFn<TOp>(0);
}
// Default implementation of GetExecutionProviderType that returns nullptr to default to the CPU provider
@ -2257,6 +2331,20 @@ struct CustomOpBase : OrtCustomOp {
return std::vector<std::string>{};
}
template <typename C>
decltype(&C::InferOutputShape) SetShapeInferFn(decltype(&C::InferOutputShape)) {
OrtCustomOp::InferOutputShapeFn = [](const OrtCustomOp*, OrtShapeInferContext* ort_ctx) -> OrtStatusPtr {
ShapeInferContext ctx(&GetApi(), ort_ctx);
return C::InferOutputShape(ctx);
};
return {};
}
template <typename C>
void SetShapeInferFn(...) {
OrtCustomOp::InferOutputShapeFn = {};
}
protected:
// Helper function that returns a map of session config entries specified by CustomOpBase::GetSessionConfigKeys.
void GetSessionConfigs(std::unordered_map<std::string, std::string>& out, ConstSessionOptions options) const;

View file

@ -8,6 +8,15 @@
// the main C++ file with implementation details.
#include <cstring>
#include <functional>
#define RETURN_ON_API_FAIL(expression) \
{ \
auto err = (expression); \
if (err) { \
return Status(err); \
} \
}
namespace Ort {
@ -1883,4 +1892,154 @@ void CustomOpBase<TOp, TKernel, WithStatus>::GetSessionConfigs(std::unordered_ma
}
}
inline ShapeInferContext::ShapeInferContext(const OrtApi* ort_api,
OrtShapeInferContext* ctx) : ort_api_(ort_api), ctx_(ctx) {
size_t input_count = 0;
Ort::ThrowOnError(ort_api_->ShapeInferContext_GetInputCount(ctx_, &input_count));
for (size_t ith_input = 0; ith_input < input_count; ++ith_input) {
OrtTensorTypeAndShapeInfo* info{};
Ort::ThrowOnError(ort_api_->ShapeInferContext_GetInputTypeShape(ctx, ith_input, &info));
TensorTypeAndShapeInfo type_shape_info(info);
auto integer_shape = type_shape_info.GetShape();
std::vector<const char*> symbolic_shape(integer_shape.size(), {});
type_shape_info.GetSymbolicDimensions(&symbolic_shape[0], integer_shape.size());
Shape shape;
for (size_t ith = 0; ith < integer_shape.size(); ++ith) {
if (symbolic_shape[ith] && std::string{symbolic_shape[ith]}.size() > 0) {
shape.emplace_back(symbolic_shape[ith]);
} else {
shape.emplace_back(integer_shape[ith]);
}
}
input_shapes_.push_back(std::move(shape));
type_shape_info.release();
}
}
inline Status ShapeInferContext::SetOutputShape(size_t indice, const Shape& shape) {
OrtTensorTypeAndShapeInfo* info = {};
RETURN_ON_API_FAIL(ort_api_->CreateTensorTypeAndShapeInfo(&info));
using InfoPtr = std::unique_ptr<OrtTensorTypeAndShapeInfo, std::function<void(OrtTensorTypeAndShapeInfo*)>>;
InfoPtr info_ptr(info, [this](OrtTensorTypeAndShapeInfo* obj) {
ort_api_->ReleaseTensorTypeAndShapeInfo(obj);
});
std::vector<int64_t> integer_dims;
std::vector<const char*> symbolic_dims;
for (const auto dim : shape) {
if (dim.IsInt()) {
integer_dims.push_back(dim.IsInt());
symbolic_dims.push_back("");
} else {
if (!dim.AsSym() || std::string{dim.AsSym()}.empty()) {
ORT_CXX_API_THROW("Symbolic dim must not be an empty string", ORT_INVALID_ARGUMENT);
}
integer_dims.push_back(SymbolicInteger::INVALID_INT_DIM);
symbolic_dims.push_back(dim.AsSym());
}
}
RETURN_ON_API_FAIL(ort_api_->SetDimensions(info, integer_dims.data(), integer_dims.size()));
RETURN_ON_API_FAIL(ort_api_->SetSymbolicDimensions(info, symbolic_dims.data(), symbolic_dims.size()));
RETURN_ON_API_FAIL(ort_api_->ShapeInferContext_SetOutputTypeShape(ctx_, indice, info));
return Status{nullptr};
}
inline int64_t ShapeInferContext::GetAttrInt(const char* attr_name) {
const auto* attr = GetAttrHdl(attr_name);
int64_t i = {};
size_t out = {};
Ort::ThrowOnError(ort_api_->ReadOpAttr(attr, ORT_OP_ATTR_INT, &i, sizeof(i), &out));
return i;
}
inline ShapeInferContext::Ints ShapeInferContext::GetAttrInts(const char* attr_name) {
const auto* attr = GetAttrHdl(attr_name);
int64_t i = {};
size_t out = {};
// first call to get the bytes needed
auto status = ort_api_->ReadOpAttr(attr, ORT_OP_ATTR_INTS, &i, sizeof(i), &out);
if (status) {
size_t num_i = out / sizeof(int64_t);
ShapeInferContext::Ints ints(num_i, 0);
Ort::ThrowOnError(ort_api_->ReadOpAttr(attr, ORT_OP_ATTR_INTS, ints.data(), out, &out));
return ints;
} else {
return {i};
}
}
inline float ShapeInferContext::GetAttrFloat(const char* attr_name) {
const auto* attr = GetAttrHdl(attr_name);
float f = {};
size_t out = {};
Ort::ThrowOnError(ort_api_->ReadOpAttr(attr, ORT_OP_ATTR_FLOAT, &f, sizeof(f), &out));
return f;
}
inline ShapeInferContext::Floats ShapeInferContext::GetAttrFloats(const char* attr_name) {
const auto* attr = GetAttrHdl(attr_name);
float f = {};
size_t out = {};
// first call to get the bytes needed
auto status = ort_api_->ReadOpAttr(attr, ORT_OP_ATTR_FLOATS, &f, sizeof(f), &out);
if (status) {
size_t num_f = out / sizeof(float);
ShapeInferContext::Floats floats(num_f, 0);
Ort::ThrowOnError(ort_api_->ReadOpAttr(attr, ORT_OP_ATTR_FLOATS, floats.data(), out, &out));
return floats;
} else {
return {f};
}
}
inline std::string ShapeInferContext::GetAttrString(const char* attr_name) {
const auto* attr = GetAttrHdl(attr_name);
char c = {};
size_t out = {};
// first call to get the bytes needed
auto status = ort_api_->ReadOpAttr(attr, ORT_OP_ATTR_STRING, &c, sizeof(char), &out);
if (status) {
std::vector<char> chars(out, '\0');
Ort::ThrowOnError(ort_api_->ReadOpAttr(attr, ORT_OP_ATTR_STRING, chars.data(), out, &out));
return {chars.data()};
} else {
return {c};
}
}
inline ShapeInferContext::Strings ShapeInferContext::GetAttrStrings(const char* attr_name) {
const auto* attr = GetAttrHdl(attr_name);
char c = {};
size_t out = {};
// first call to get the bytes needed
auto status = ort_api_->ReadOpAttr(attr, ORT_OP_ATTR_STRINGS, &c, sizeof(char), &out);
if (status) {
std::vector<char> chars(out, '\0');
Ort::ThrowOnError(ort_api_->ReadOpAttr(attr, ORT_OP_ATTR_STRINGS, chars.data(), out, &out));
ShapeInferContext::Strings strings;
char* char_st = chars.data();
char* char_ed = char_st + out;
while (char_st < char_ed) {
strings.emplace_back(char_st);
while (*char_st != '\0') {
char_st++;
}
char_st++;
}
return strings;
} else {
return {std::string{c}};
}
}
inline const OrtOpAttr* ShapeInferContext::GetAttrHdl(const char* attr_name) const {
const OrtOpAttr* attr_hdl = {};
Ort::ThrowOnError(ort_api_->ShapeInferContext_GetAttribute(ctx_, attr_name, &attr_hdl));
return attr_hdl;
}
} // namespace Ort

View file

@ -835,6 +835,8 @@ struct OrtLiteCustomOp : public OrtCustomOp {
OrtCustomOp::CreateKernelV2 = {};
OrtCustomOp::KernelComputeV2 = {};
OrtCustomOp::KernelCompute = {};
OrtCustomOp::InferOutputShapeFn = {};
}
const std::string op_name_;
@ -870,8 +872,10 @@ struct OrtLiteCustomFunc : public OrtLiteCustomOp {
OrtLiteCustomFunc(const char* op_name,
const char* execution_provider,
ComputeFn compute_fn) : OrtLiteCustomOp(op_name, execution_provider),
compute_fn_(compute_fn) {
ComputeFn compute_fn,
ShapeInferFn shape_infer_fn = {}) : OrtLiteCustomOp(op_name, execution_provider),
compute_fn_(compute_fn),
shape_infer_fn_(shape_infer_fn) {
ParseArgs<Args...>(input_types_, output_types_);
OrtCustomOp::KernelCompute = [](void* op_kernel, OrtKernelContext* context) {
@ -894,12 +898,22 @@ struct OrtLiteCustomFunc : public OrtLiteCustomOp {
OrtCustomOp::KernelDestroy = [](void* op_kernel) {
delete reinterpret_cast<Kernel*>(op_kernel);
};
if (shape_infer_fn_) {
OrtCustomOp::InferOutputShapeFn = [](const OrtCustomOp* op, OrtShapeInferContext* ort_ctx) -> OrtStatusPtr {
auto shape_info_fn = static_cast<const MyType*>(op)->shape_infer_fn_;
ShapeInferContext ctx(&GetApi(), ort_ctx);
return shape_info_fn(ctx);
};
}
}
OrtLiteCustomFunc(const char* op_name,
const char* execution_provider,
ComputeFnReturnStatus compute_fn_return_status) : OrtLiteCustomOp(op_name, execution_provider),
compute_fn_return_status_(compute_fn_return_status) {
ComputeFnReturnStatus compute_fn_return_status,
ShapeInferFn shape_infer_fn = {}) : OrtLiteCustomOp(op_name, execution_provider),
compute_fn_return_status_(compute_fn_return_status),
shape_infer_fn_(shape_infer_fn) {
ParseArgs<Args...>(input_types_, output_types_);
OrtCustomOp::KernelComputeV2 = [](void* op_kernel, OrtKernelContext* context) -> OrtStatusPtr {
@ -922,10 +936,19 @@ struct OrtLiteCustomFunc : public OrtLiteCustomOp {
OrtCustomOp::KernelDestroy = [](void* op_kernel) {
delete reinterpret_cast<Kernel*>(op_kernel);
};
if (shape_infer_fn_) {
OrtCustomOp::InferOutputShapeFn = [](const OrtCustomOp* op, OrtShapeInferContext* ort_ctx) -> OrtStatusPtr {
auto shape_info_fn = static_cast<const MyType*>(op)->shape_infer_fn_;
ShapeInferContext ctx(&GetApi(), ort_ctx);
return shape_info_fn(ctx);
};
}
}
ComputeFn compute_fn_ = {};
ComputeFnReturnStatus compute_fn_return_status_ = {};
ShapeInferFn shape_infer_fn_ = {};
}; // struct OrtLiteCustomFunc
/////////////////////////// OrtLiteCustomStruct ///////////////////////////
@ -964,7 +987,7 @@ struct OrtLiteCustomStruct : public OrtLiteCustomOp {
OrtLiteCustomStruct(const char* op_name,
const char* execution_provider) : OrtLiteCustomOp(op_name,
execution_provider) {
init(&CustomOp::Compute);
SetCompute(&CustomOp::Compute);
OrtCustomOp::CreateKernel = [](const OrtCustomOp* this_, const OrtApi* ort_api, const OrtKernelInfo* info) {
auto kernel = std::make_unique<Kernel>();
@ -979,10 +1002,12 @@ struct OrtLiteCustomStruct : public OrtLiteCustomOp {
OrtCustomOp::KernelDestroy = [](void* op_kernel) {
delete reinterpret_cast<Kernel*>(op_kernel);
};
SetShapeInfer<CustomOp>(0);
}
template <typename... Args>
void init(CustomComputeFn<Args...>) {
void SetCompute(CustomComputeFn<Args...>) {
ParseArgs<Args...>(input_types_, output_types_);
OrtCustomOp::KernelCompute = [](void* op_kernel, OrtKernelContext* context) {
auto kernel = reinterpret_cast<Kernel*>(op_kernel);
@ -993,7 +1018,7 @@ struct OrtLiteCustomStruct : public OrtLiteCustomOp {
}
template <typename... Args>
void init(CustomComputeFnReturnStatus<Args...>) {
void SetCompute(CustomComputeFnReturnStatus<Args...>) {
ParseArgs<Args...>(input_types_, output_types_);
OrtCustomOp::KernelComputeV2 = [](void* op_kernel, OrtKernelContext* context) -> OrtStatusPtr {
auto kernel = reinterpret_cast<Kernel*>(op_kernel);
@ -1002,6 +1027,20 @@ struct OrtLiteCustomStruct : public OrtLiteCustomOp {
return std::apply([kernel](Args const&... t_args) { Status status = kernel->custom_op_->Compute(t_args...); return status.release(); }, t);
};
}
template <typename C>
decltype(&C::InferOutputShape) SetShapeInfer(decltype(&C::InferOutputShape)) {
OrtCustomOp::InferOutputShapeFn = [](const OrtCustomOp*, OrtShapeInferContext* ort_ctx) -> OrtStatusPtr {
ShapeInferContext ctx(&GetApi(), ort_ctx);
return C::InferOutputShape(ctx);
};
return {};
}
template <typename C>
void SetShapeInfer(...) {
OrtCustomOp::InferOutputShapeFn = {};
}
}; // struct OrtLiteCustomStruct
/////////////////////////// CreateLiteCustomOp ////////////////////////////
@ -1009,17 +1048,19 @@ struct OrtLiteCustomStruct : public OrtLiteCustomOp {
template <typename... Args>
OrtLiteCustomOp* CreateLiteCustomOp(const char* op_name,
const char* execution_provider,
void (*custom_compute_fn)(Args...)) {
void (*custom_compute_fn)(Args...),
Status (*shape_infer_fn)(ShapeInferContext&) = {}) {
using LiteOp = OrtLiteCustomFunc<Args...>;
return std::make_unique<LiteOp>(op_name, execution_provider, custom_compute_fn).release();
return std::make_unique<LiteOp>(op_name, execution_provider, custom_compute_fn, shape_infer_fn).release();
}
template <typename... Args>
OrtLiteCustomOp* CreateLiteCustomOp(const char* op_name,
const char* execution_provider,
Status (*custom_compute_fn_v2)(Args...)) {
Status (*custom_compute_fn_v2)(Args...),
Status (*shape_infer_fn)(ShapeInferContext&) = {}) {
using LiteOp = OrtLiteCustomFunc<Args...>;
return std::make_unique<LiteOp>(op_name, execution_provider, custom_compute_fn_v2).release();
return std::make_unique<LiteOp>(op_name, execution_provider, custom_compute_fn_v2, shape_infer_fn).release();
}
template <typename CustomOp>

View file

@ -85,6 +85,16 @@ ORT_API_STATUS_IMPL(OrtApis::GetSymbolicDimensions,
return nullptr;
}
ORT_API_STATUS_IMPL(OrtApis::SetSymbolicDimensions,
_In_ struct OrtTensorTypeAndShapeInfo* info,
_In_ const char** names, _In_ size_t dim_params_length) {
info->dim_params.clear();
for (size_t idx = 0; idx < dim_params_length; ++idx) {
info->dim_params.push_back(names[idx]);
}
return nullptr;
}
ORT_API_STATUS_IMPL(OrtApis::GetTensorShapeElementCount,
_In_ const OrtTensorTypeAndShapeInfo* this_ptr, _Out_ size_t* out) {
API_IMPL_BEGIN

View file

@ -29,6 +29,7 @@ static constexpr uint32_t min_ort_version_with_variadic_io_support = 14;
#if !defined(ORT_MINIMAL_BUILD) || defined(ORT_MINIMAL_BUILD_CUSTOM_OPS)
static constexpr uint32_t min_ort_version_with_compute_v2_support = 17;
static constexpr uint32_t min_ort_version_with_shape_inference = 17;
#endif
#if !defined(DISABLE_FLOAT8_TYPES)
@ -37,6 +38,231 @@ static constexpr uint32_t min_ort_version_with_compute_v2_support = 17;
#define SUPPORTED_TENSOR_TYPES DataTypeImpl::AllTensorTypesIRv4()
#endif
#if defined(ORT_MINIMAL_BUILD)
struct OrtShapeInferContext {
size_t GetInputCount() const { return 0; }
OrtTensorTypeAndShapeInfo* GetInputTypeShape(size_t) const { return {}; }
onnxruntime::Status SetOutputTypeShape(size_t, const OrtTensorTypeAndShapeInfo*) const {
return ORT_MAKE_STATUS(ONNXRUNTIME, NOT_IMPLEMENTED, "OrtShapeInferContext::SetOutputTypeShape not implemented for minimal build");
}
const ONNX_NAMESPACE::AttributeProto* GetAttr(const char*) const { return {}; }
};
#else
struct OrtShapeInferContext {
OrtShapeInferContext(ONNX_NAMESPACE::InferenceContext& ctx) : ctx_(ctx) {
auto num_inputs = ctx_.getNumInputs();
for (size_t ith_input = 0; ith_input < num_inputs; ++ith_input) {
const auto* input_type = ctx_.getInputType(ith_input);
const auto& value_case = input_type->value_case();
ORT_ENFORCE(value_case == ONNX_NAMESPACE::TypeProto::kTensorType, "shape inference not yet supported for non-tensor types");
const auto& shape_proto = input_type->tensor_type().shape();
const auto& type_proto = input_type->tensor_type();
auto elem_type = ::onnxruntime::utils::CApiElementTypeFromProtoType(type_proto.elem_type());
auto tensor_shape = ::onnxruntime::utils::GetTensorShapeFromTensorShapeProto(shape_proto);
auto symbolic_dims = GetSymbolicDims(shape_proto);
input_type_shapes_.emplace_back(OrtTensorTypeAndShapeInfo::GetTensorShapeAndTypeHelper(elem_type, tensor_shape, &symbolic_dims).release());
}
}
~OrtShapeInferContext() = default;
size_t GetInputCount() const { return input_type_shapes_.size(); }
OrtTensorTypeAndShapeInfo* GetInputTypeShape(size_t idx) const {
return input_type_shapes_.at(idx).get();
}
onnxruntime::Status SetOutputTypeShape(size_t index, const OrtTensorTypeAndShapeInfo* info) const {
ORT_RETURN_IF_NOT(info, "Invalid shape info");
ONNX_NAMESPACE::TensorShapeProto shape_proto;
const auto& symbolic_dims = info->dim_params;
const auto& integer_dims = info->shape.GetDims();
ORT_RETURN_IF_NOT(symbolic_dims.size() == integer_dims.size(), "symbolic and integer dims mismatch!");
for (size_t ith = 0; ith < symbolic_dims.size(); ith++) {
auto* dim_proto = shape_proto.add_dim();
if (symbolic_dims[ith].size() > 0) {
dim_proto->set_dim_param(symbolic_dims[ith]);
} else {
dim_proto->set_dim_value(integer_dims[ith]);
}
}
ONNX_NAMESPACE::updateOutputShape(ctx_, index, shape_proto);
return onnxruntime::Status::OK();
}
const ONNX_NAMESPACE::AttributeProto* GetAttr(const char* attr_name) const {
return ctx_.getAttribute(attr_name);
}
private:
static std::vector<std::string> GetSymbolicDims(const ONNX_NAMESPACE::TensorShapeProto& shape_proto) {
std::vector<std::string> symblic_dims;
for (int ith = 0; ith < shape_proto.dim_size(); ith++) {
const auto& dim = shape_proto.dim(ith);
if (::onnxruntime::utils::HasDimValue(dim)) {
symblic_dims.emplace_back();
} else {
symblic_dims.emplace_back(dim.dim_param());
}
}
return symblic_dims;
}
ONNX_NAMESPACE::InferenceContext& ctx_;
using TypeShapePtr = std::unique_ptr<OrtTensorTypeAndShapeInfo>;
onnxruntime::InlinedVector<TypeShapePtr> input_type_shapes_;
};
#endif
ORT_API_STATUS_IMPL(OrtApis::ShapeInferContext_GetInputCount, _In_ const OrtShapeInferContext* context, _Out_ size_t* out) {
API_IMPL_BEGIN
*out = context->GetInputCount();
return nullptr;
API_IMPL_END
}
ORT_API_STATUS_IMPL(OrtApis::ShapeInferContext_GetInputTypeShape, _In_ const OrtShapeInferContext* context, _In_ size_t index, _Outptr_ OrtTensorTypeAndShapeInfo** info) {
API_IMPL_BEGIN
*info = context->GetInputTypeShape(index);
if (*info) {
return nullptr;
} else {
return OrtApis::CreateStatus(OrtErrorCode::ORT_INVALID_ARGUMENT, "Failed to fetch type shape info for the index.");
}
API_IMPL_END
}
ORT_API_STATUS_IMPL(OrtApis::ShapeInferContext_GetAttribute, _In_ const OrtShapeInferContext* context, _In_ const char* attr_name, _Outptr_ const OrtOpAttr** attr) {
API_IMPL_BEGIN
*attr = reinterpret_cast<const OrtOpAttr*>(context->GetAttr(attr_name));
if (*attr) {
return nullptr;
} else {
return OrtApis::CreateStatus(OrtErrorCode::ORT_INVALID_ARGUMENT, "Attribute does not exist.");
}
API_IMPL_END
}
ORT_API_STATUS_IMPL(OrtApis::ReadOpAttr,
_In_ const OrtOpAttr* op_attr,
_In_ OrtOpAttrType type,
_Inout_ void* data,
_In_ size_t len,
_Out_ size_t* out) {
API_IMPL_BEGIN
if (!op_attr) {
return OrtApis::CreateStatus(OrtErrorCode::ORT_INVALID_ARGUMENT, "Invalid attribute.");
}
auto attr = reinterpret_cast<const ONNX_NAMESPACE::AttributeProto*>(op_attr);
OrtStatusPtr ret = nullptr;
*out = 0;
if (type == OrtOpAttrType::ORT_OP_ATTR_FLOAT) {
if (len < sizeof(float)) {
ret = OrtApis::CreateStatus(OrtErrorCode::ORT_INVALID_ARGUMENT, "Size of data not large enough to hold a float.");
} else {
if (attr->has_f()) {
auto output_f = reinterpret_cast<float*>(data);
*output_f = attr->f();
} else {
ret = OrtApis::CreateStatus(OrtErrorCode::ORT_INVALID_ARGUMENT, "Attribute has no float value.");
}
}
*out = sizeof(float);
} else if (type == OrtOpAttrType::ORT_OP_ATTR_FLOATS) {
const auto& floats = attr->floats();
auto num_floats = floats.size();
if (len < sizeof(float) * num_floats) {
ret = OrtApis::CreateStatus(OrtErrorCode::ORT_INVALID_ARGUMENT, "Size of data not large enough to hold the array of floats.");
} else {
auto output_f = reinterpret_cast<float*>(data);
for (auto f : floats) {
*output_f = f;
output_f++;
}
}
*out = num_floats * sizeof(float);
} else if (type == OrtOpAttrType::ORT_OP_ATTR_INT) {
if (len < sizeof(int)) {
ret = OrtApis::CreateStatus(OrtErrorCode::ORT_INVALID_ARGUMENT, "Size of data not large enough to hold an int64.");
} else {
if (attr->has_i()) {
auto output_i = reinterpret_cast<int64_t*>(data);
*output_i = attr->i();
} else {
ret = OrtApis::CreateStatus(OrtErrorCode::ORT_INVALID_ARGUMENT, "Attribute has no int64 value.");
}
}
*out = sizeof(int64_t);
} else if (type == OrtOpAttrType::ORT_OP_ATTR_INTS) {
const auto& ints = attr->ints();
auto num_ints = ints.size();
if (len < sizeof(int64_t) * num_ints) {
ret = OrtApis::CreateStatus(OrtErrorCode::ORT_INVALID_ARGUMENT, "Size of data not large enough to hold the array of int64.");
} else {
auto output_i = reinterpret_cast<int64_t*>(data);
for (auto i : ints) {
*output_i = i;
output_i++;
}
}
*out = num_ints * sizeof(int64_t);
} else if (type == OrtOpAttrType::ORT_OP_ATTR_STRING) {
const auto& s = attr->s();
if (len < s.size() + 1) {
ret = OrtApis::CreateStatus(OrtErrorCode::ORT_INVALID_ARGUMENT, "Size of data not large enough to hold the string.");
} else {
char* output_c = reinterpret_cast<char*>(data);
for (char c : s) {
*output_c++ = c;
}
*output_c = '\0';
}
*out = s.size() + 1;
} else if (type == OrtOpAttrType::ORT_OP_ATTR_STRINGS) {
const auto& ss = attr->strings();
size_t num_bytes = 0;
for_each(ss.begin(), ss.end(), [&num_bytes](const std::string& s) { num_bytes += s.size() + 1; });
if (len < num_bytes) {
ret = OrtApis::CreateStatus(OrtErrorCode::ORT_INVALID_ARGUMENT, "Size of data not large enough to hold the array of strings.");
} else {
char* output_c = reinterpret_cast<char*>(data);
for (const auto& s : ss) {
for (char c : s) {
*output_c++ = c;
}
*output_c++ = '\0';
}
}
*out = num_bytes;
} else {
ret = OrtApis::CreateStatus(OrtErrorCode::ORT_INVALID_ARGUMENT, "Unknown attribute type.");
}
return ret;
API_IMPL_END
}
ORT_API_STATUS_IMPL(OrtApis::ShapeInferContext_SetOutputTypeShape, _In_ const OrtShapeInferContext* context, _In_ size_t index, _In_ const OrtTensorTypeAndShapeInfo* info) {
API_IMPL_BEGIN
auto status = context->SetOutputTypeShape(index, info);
if (status.IsOK()) {
return nullptr;
} else {
return OrtApis::CreateStatus(static_cast<OrtErrorCode>(status.Code()), status.ErrorMessage().c_str());
}
API_IMPL_END
}
ORT_API_STATUS_IMPL(OrtApis::KernelInfoGetAttribute_float, _In_ const OrtKernelInfo* info, _In_ const char* name, _Out_ float* out) {
API_IMPL_BEGIN
auto status = reinterpret_cast<const onnxruntime::OpKernelInfo*>(info)->GetAttr<float>(name, out);
@ -596,6 +822,13 @@ ONNX_NAMESPACE::OpSchema CreateSchema(const std::string& domain, const OrtCustom
schema.SetDomain(domain);
schema.SinceVersion(1);
schema.AllowUncheckedAttributes();
if (op->version >= min_ort_version_with_shape_inference && op->InferOutputShapeFn) {
schema.TypeAndShapeInferenceFunction([op](ONNX_NAMESPACE::InferenceContext& infer_ctx) {
OrtShapeInferContext ctx(infer_ctx);
op->InferOutputShapeFn(op, &ctx);
});
}
return schema;
}
@ -764,10 +997,14 @@ common::Status CreateCustomRegistry(gsl::span<OrtCustomOpDomain* const> op_domai
for (auto schema_iter : schema_map) {
schemas.push_back(schema_iter.second);
InlinedVector<const KernelDef*> kernel_defs = std::move(kernel_def_map[schema_iter.first]);
ONNX_NAMESPACE::InferenceFunction infer_fn = [kernel_defs](ONNX_NAMESPACE::InferenceContext& infer_ctx) {
auto infer_fn = schemas.back().GetTypeAndShapeInferenceFunction();
ONNX_NAMESPACE::InferenceFunction extended_infer_fn = [infer_fn, kernel_defs](ONNX_NAMESPACE::InferenceContext& infer_ctx) {
InferOutputTypes(kernel_defs, infer_ctx);
if (infer_fn) {
infer_fn(infer_ctx);
}
};
schemas.back().TypeAndShapeInferenceFunction(infer_fn);
schemas.back().TypeAndShapeInferenceFunction(extended_infer_fn);
}
ORT_RETURN_IF_ERROR(output->RegisterOpSet(schemas,

View file

@ -2715,6 +2715,12 @@ static constexpr OrtApi ort_api_1_to_17 = {
// End of Version 16 - DO NOT MODIFY ABOVE (see above text for more information)
&OrtApis::SetUserLoggingFunction,
&OrtApis::ShapeInferContext_GetInputCount,
&OrtApis::ShapeInferContext_GetInputTypeShape,
&OrtApis::ShapeInferContext_GetAttribute,
&OrtApis::ShapeInferContext_SetOutputTypeShape,
&OrtApis::SetSymbolicDimensions,
&OrtApis::ReadOpAttr,
};
// OrtApiBase can never change as there is no way to know what version of OrtApiBase is returned by OrtGetApiBase.

View file

@ -491,6 +491,14 @@ ORT_API_STATUS_IMPL(GetTensorRTProviderOptionsByName, _In_ const OrtTensorRTProv
ORT_API_STATUS_IMPL(UpdateCUDAProviderOptionsWithValue, _Inout_ OrtCUDAProviderOptionsV2* cuda_options, _In_ const char* key, _In_ void* value);
ORT_API_STATUS_IMPL(GetCUDAProviderOptionsByName, _In_ const OrtCUDAProviderOptionsV2* cuda_options, _In_ const char* key, _Outptr_ void** ptr);
ORT_API_STATUS_IMPL(KernelContext_GetResource, _In_ const OrtKernelContext* context, _In_ int resource_version, _In_ int resource_id, _Outptr_ void** stream);
ORT_API_STATUS_IMPL(SetUserLoggingFunction, _Inout_ OrtSessionOptions* options,
_In_ OrtLoggingFunction user_logging_function, _In_opt_ void* user_logging_param);
ORT_API_STATUS_IMPL(ShapeInferContext_GetInputCount, _In_ const OrtShapeInferContext* context, _Out_ size_t* out);
ORT_API_STATUS_IMPL(ShapeInferContext_GetInputTypeShape, _In_ const OrtShapeInferContext* context, _In_ size_t index, _Outptr_ OrtTensorTypeAndShapeInfo** info);
ORT_API_STATUS_IMPL(ShapeInferContext_GetAttribute, _In_ const OrtShapeInferContext* context, _In_ const char* attr_name, _Outptr_ const OrtOpAttr** attr);
ORT_API_STATUS_IMPL(ShapeInferContext_SetOutputTypeShape, _In_ const OrtShapeInferContext* context, _In_ size_t index, _In_ const OrtTensorTypeAndShapeInfo* info);
ORT_API_STATUS_IMPL(SetSymbolicDimensions, _In_ OrtTensorTypeAndShapeInfo* info, _In_ const char* dim_params[], _In_ size_t dim_params_length);
ORT_API_STATUS_IMPL(ReadOpAttr, _In_ const OrtOpAttr* op_attr, _In_ OrtOpAttrType type, _Inout_ void* data, _In_ size_t len, _Out_ size_t* out);
} // namespace OrtApis

View file

@ -183,6 +183,7 @@ static constexpr PATH_TYPE SEQUENCE_MODEL_URI = TSTR("testdata/sequence_length.o
static constexpr PATH_TYPE SEQUENCE_MODEL_URI_2 = TSTR("testdata/optional_sequence_tensor.onnx");
#endif
static constexpr PATH_TYPE CUSTOM_OP_MODEL_URI = TSTR("testdata/foo_1.onnx");
static constexpr PATH_TYPE CUSTOM_OP_LIBRARY_ATTR_TESTER_URI = TSTR("testdata/custom_op_library/attr_tester.onnx");
static constexpr PATH_TYPE CUSTOM_OP_LIBRARY_TEST_MODEL_URI = TSTR("testdata/custom_op_library/custom_op_test.onnx");
static constexpr PATH_TYPE CUSTOM_OP_LIBRARY_COPY_TENSOR_ARRAY_2 = TSTR("testdata/custom_op_library/copy_2_inputs_2_outputs.onnx");
static constexpr PATH_TYPE CUSTOM_OP_LIBRARY_COPY_TENSOR_ARRAY_3 = TSTR("testdata/custom_op_library/copy_3_inputs_3_outputs.onnx");
@ -1408,6 +1409,36 @@ TEST(CApiTest, test_custom_op_library) {
#endif
}
#if defined(__ANDROID__)
TEST(CApiTest, DISABLED_test_custom_op_shape_infer_attr) {
// To accomodate a reduced op build pipeline
#elif defined(REDUCED_OPS_BUILD) && defined(USE_CUDA)
TEST(CApiTest, DISABLED_test_custom_op_shape_infer_attr) {
#else
TEST(CApiTest, test_custom_op_shape_infer_attr) {
#endif
std::vector<Input> inputs(1);
inputs[0].name = "input_0";
inputs[0].dims = {5};
inputs[0].values = {1.f, 2.f, 3.f, 4.f, 5.f};
// prepare expected inputs and outputs
std::vector<int64_t> expected_dims_y = {5};
std::vector<float> expected_values_y = {6.f, 12.f, 18.f, 24.f, 30.f};
onnxruntime::PathString lib_name;
#if defined(_WIN32)
lib_name = ORT_TSTR("custom_op_library.dll");
#elif defined(__APPLE__)
lib_name = ORT_TSTR("libcustom_op_library.dylib");
#else
lib_name = ORT_TSTR("./libcustom_op_library.so");
#endif
TestInference<float>(*ort_env, CUSTOM_OP_LIBRARY_ATTR_TESTER_URI, inputs, "output_0", expected_dims_y,
expected_values_y, 0, nullptr, lib_name.c_str());
}
// It has memory leak. The OrtCustomOpDomain created in custom_op_library.cc:RegisterCustomOps function was not freed
#if defined(__ANDROID__)
TEST(CApiTest, test_custom_op_library_copy_variadic) {
@ -3201,6 +3232,15 @@ struct Merge {
strings_out->SetStringOutput(string_pool, {static_cast<int64_t>(string_pool.size())});
return Ort::Status(nullptr);
}
static Ort::Status InferOutputShape(Ort::ShapeInferContext& ctx) {
auto input_count = ctx.GetInputCount();
if (input_count != 2) {
return Ort::Status("input count should be 2", OrtErrorCode::ORT_INVALID_ARGUMENT);
}
Ort::ShapeInferContext::Shape shape_1 = {{-1}};
ctx.SetOutputShape(0, shape_1);
return Ort::Status(nullptr);
}
bool reverse_ = false;
};

Binary file not shown.

View file

@ -7,25 +7,47 @@
#include "onnxruntime_lite_custom_op.h"
#define CUSTOM_ENFORCE(cond, msg) \
if (!(cond)) { \
ORT_CXX_API_THROW(msg, OrtErrorCode::ORT_RUNTIME_EXCEPTION); \
}
using namespace Ort::Custom;
namespace Cpu {
Ort::Status KernelOne(const Ort::Custom::Tensor<float>& X,
struct KernelOne {
KernelOne(const OrtApi*, const OrtKernelInfo*) {}
Ort::Status Compute(const Ort::Custom::Tensor<float>& X,
const Ort::Custom::Tensor<float>& Y,
Ort::Custom::Tensor<float>& Z) {
if (X.NumberOfElement() != Y.NumberOfElement()) {
return Ort::Status("x and y has different number of elements", OrtErrorCode::ORT_INVALID_ARGUMENT);
if (X.NumberOfElement() != Y.NumberOfElement()) {
return Ort::Status("x and y has different number of elements", OrtErrorCode::ORT_INVALID_ARGUMENT);
}
auto x_shape = X.Shape();
auto x_raw = X.Data();
auto y_raw = Y.Data();
auto z_raw = Z.Allocate(x_shape);
for (int64_t i = 0; i < Z.NumberOfElement(); ++i) {
z_raw[i] = x_raw[i] + y_raw[i];
}
return Ort::Status{nullptr};
}
auto x_shape = X.Shape();
auto x_raw = X.Data();
auto y_raw = Y.Data();
auto z_raw = Z.Allocate(x_shape);
for (int64_t i = 0; i < Z.NumberOfElement(); ++i) {
z_raw[i] = x_raw[i] + y_raw[i];
static Ort::Status InferOutputShape(Ort::ShapeInferContext& ctx) {
auto input_count = ctx.GetInputCount();
if (input_count != 2) {
return Ort::Status("input count should be 2", OrtErrorCode::ORT_INVALID_ARGUMENT);
}
Ort::ShapeInferContext::Shape shape_3_5 = {{3}, {5}};
if (ctx.GetInputShape(0) != shape_3_5 ||
ctx.GetInputShape(1) != shape_3_5) {
return Ort::Status("input shape mismatch", OrtErrorCode::ORT_INVALID_ARGUMENT);
}
return Ort::Status{nullptr};
}
return Ort::Status{nullptr};
}
};
// lite custom op as a function
void KernelTwo(const Ort::Custom::Tensor<float>& X,
@ -206,8 +228,68 @@ Ort::Status CopyTensorArrayCombined(const Ort::Custom::Tensor<float>& first_inpu
return CopyTensorArrayAllVariadic<T>(other_inputs, other_outputs);
}
Ort::Status AttrTesterIntFloatCompute(const Ort::Custom::Tensor<float>& X, Ort::Custom::Tensor<float>& Z) {
auto x_shape = X.Shape();
auto x_raw = X.Data();
auto z_raw = Z.Allocate(x_shape);
for (int64_t i = 0; i < X.NumberOfElement(); ++i) {
z_raw[i] = x_raw[i] * 2;
}
return Ort::Status{nullptr};
}
Ort::Status AttrTesterIntFloatShapeInfer(Ort::ShapeInferContext& ctx) {
CUSTOM_ENFORCE(ctx.GetAttrInt("a_int") == 1, "int attr mismatch");
CUSTOM_ENFORCE(ctx.GetAttrFloat("a_float") == 2.f, "float attr mismatch");
std::vector<int64_t> ints{3, 4, 5};
CUSTOM_ENFORCE(ctx.GetAttrInts("ints") == ints, "ints attr mismatch");
std::vector<float> floats{6, 7, 8};
CUSTOM_ENFORCE(ctx.GetAttrFloats("floats") == floats, "floats attr mismatch");
auto input_shape = ctx.GetInputShape(0);
CUSTOM_ENFORCE(input_shape.size() == 1 &&
!input_shape[0].IsInt() &&
std::string{input_shape[0].AsSym()} == "d",
"input dim is not symbolic");
ctx.SetOutputShape(0, input_shape);
return Ort::Status{nullptr};
}
struct AttrTesterStringKernel {
void Compute(OrtKernelContext* context) {
Ort::KernelContext ctx(context);
auto input_X = ctx.GetInput(0);
const auto* X = input_X.GetTensorData<float>();
auto dimensions = input_X.GetTensorTypeAndShapeInfo().GetShape();
auto output = ctx.GetOutput(0, dimensions);
auto* out = output.GetTensorMutableData<float>();
const size_t size = output.GetTensorTypeAndShapeInfo().GetElementCount();
for (size_t i = 0; i < size; i++) {
out[i] = X[i] * 3;
}
}
};
struct AttrTesterStringOp : Ort::CustomOpBase<AttrTesterStringOp, AttrTesterStringKernel> {
void* CreateKernel(const OrtApi&, const OrtKernelInfo*) const {
return std::make_unique<AttrTesterStringKernel>().release();
};
const char* GetName() const { return "AttrTesterString"; };
const char* GetExecutionProviderType() const { return "CPUExecutionProvider"; };
size_t GetInputTypeCount() const { return 1; };
ONNXTensorElementDataType GetInputType(size_t) const { return ONNX_TENSOR_ELEMENT_DATA_TYPE_FLOAT; };
size_t GetOutputTypeCount() const { return 1; };
ONNXTensorElementDataType GetOutputType(size_t) const { return ONNX_TENSOR_ELEMENT_DATA_TYPE_FLOAT; };
static Ort::Status InferOutputShape(Ort::ShapeInferContext& ctx) {
CUSTOM_ENFORCE(ctx.GetAttrString("a_string") == "iamastring", "string attr mismatch");
std::vector<std::string> strings{"more", "strings"};
CUSTOM_ENFORCE(ctx.GetAttrStrings("strings") == strings, "strings attr mismatch");
return Ort::Status{nullptr};
}
};
void RegisterOps(Ort::CustomOpDomain& domain) {
static const std::unique_ptr<OrtLiteCustomOp> c_CustomOpOne{Ort::Custom::CreateLiteCustomOp("CustomOpOne", "CPUExecutionProvider", KernelOne)};
static const std::unique_ptr<OrtLiteCustomOp> c_CustomOpOne{Ort::Custom::CreateLiteCustomOp<KernelOne>("CustomOpOne", "CPUExecutionProvider")};
static const std::unique_ptr<OrtLiteCustomOp> c_CustomOpTwo{Ort::Custom::CreateLiteCustomOp("CustomOpTwo", "CPUExecutionProvider", KernelTwo)};
static const std::unique_ptr<OrtLiteCustomOp> c_MulTopOpFloat{Ort::Custom::CreateLiteCustomOp("MulTop", "CPUExecutionProvider", MulTop<float>)};
static const std::unique_ptr<OrtLiteCustomOp> c_MulTopOpInt32{Ort::Custom::CreateLiteCustomOp("MulTop", "CPUExecutionProvider", MulTop<int32_t>)};
@ -218,6 +300,9 @@ void RegisterOps(Ort::CustomOpDomain& domain) {
static const std::unique_ptr<OrtLiteCustomOp> c_CopyTensorArrayAllVariadic{Ort::Custom::CreateLiteCustomOp("CopyTensorArrayAllVariadic", "CPUExecutionProvider", CopyTensorArrayAllVariadic<float>)};
static const std::unique_ptr<OrtLiteCustomOp> c_CopyTensorArrayCombined{Ort::Custom::CreateLiteCustomOp("CopyTensorArrayCombined", "CPUExecutionProvider", CopyTensorArrayCombined<float>)};
static const std::unique_ptr<OrtLiteCustomOp> c_AtterTesterIntFloat{Ort::Custom::CreateLiteCustomOp("AttrTesterIntFloat", "CPUExecutionProvider", AttrTesterIntFloatCompute, AttrTesterIntFloatShapeInfer)};
static const AttrTesterStringOp c_AtterTesterString;
#if !defined(DISABLE_FLOAT8_TYPES)
static const CustomOpOneFloat8 c_CustomOpOneFloat8;
static const std::unique_ptr<OrtLiteCustomOp> c_FilterFloat8{Ort::Custom::CreateLiteCustomOp("FilterFloat8", "CPUExecutionProvider", FilterFloat8)};
@ -233,6 +318,8 @@ void RegisterOps(Ort::CustomOpDomain& domain) {
domain.Add(c_Box.get());
domain.Add(c_CopyTensorArrayAllVariadic.get());
domain.Add(c_CopyTensorArrayCombined.get());
domain.Add(c_AtterTesterIntFloat.get());
domain.Add(&c_AtterTesterString);
#if !defined(DISABLE_FLOAT8_TYPES)
domain.Add(&c_CustomOpOneFloat8);