Update CUDA custom op unit tests to account for recent ORT change (#6971)

This commit is contained in:
Hariharan Seshadri 2021-03-11 22:22:45 -08:00 committed by GitHub
parent 694389a85d
commit 12b5ab3bab
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 471 additions and 392 deletions

View file

@ -319,7 +319,9 @@ set (onnxruntime_shared_lib_test_SRC
${ONNXRUNTIME_SHARED_LIB_TEST_SRC_DIR}/test_model_loading.cc
${ONNXRUNTIME_SHARED_LIB_TEST_SRC_DIR}/test_ort_format_models.cc
${ONNXRUNTIME_SHARED_LIB_TEST_SRC_DIR}/utils.h
${ONNXRUNTIME_SHARED_LIB_TEST_SRC_DIR}/utils.cc)
${ONNXRUNTIME_SHARED_LIB_TEST_SRC_DIR}/utils.cc
${ONNXRUNTIME_SHARED_LIB_TEST_SRC_DIR}/custom_op_utils.h
${ONNXRUNTIME_SHARED_LIB_TEST_SRC_DIR}/custom_op_utils.cc)
if (NOT onnxruntime_MINIMAL_BUILD)
list(APPEND onnxruntime_shared_lib_test_SRC ${ONNXRUNTIME_SHARED_LIB_TEST_SRC_DIR}/test_inference.cc)

View file

@ -16,8 +16,8 @@ __global__ void cuda_add_impl(int64_t N, T3* O, const T1* X, const T2* Y) {
}
template <typename T1, typename T2, typename T3>
void cuda_add(int64_t N, T3* O, const T1* X, const T2* Y) {
cuda_add_impl<<<1, 256, 0, 0>>>(N, O, X, Y);
void cuda_add(int64_t N, T3* O, const T1* X, const T2* Y, cudaStream_t compute_stream) {
cuda_add_impl<<<1, 256, 0, compute_stream>>>(N, O, X, Y);
}
template <typename T>
@ -29,12 +29,12 @@ __global__ void cuda_slice_impl(const T* X, int64_t from, int64_t to, T* Y) {
}
template <typename T>
void cuda_slice(const T* X, int64_t from, int64_t to, T* Y) {
cuda_slice_impl<T><<<1, 256, 0, 0>>>(X, from, to, Y);
void cuda_slice(const T* X, int64_t from, int64_t to, T* Y, cudaStream_t compute_stream) {
cuda_slice_impl<T><<<1, 256, 0, compute_stream>>>(X, from, to, Y);
}
template void cuda_slice(const float*, int64_t, int64_t, float*);
template void cuda_slice(const double*, int64_t, int64_t, double*);
template void cuda_slice(const float*, int64_t, int64_t, float*, cudaStream_t compute_stream);
template void cuda_slice(const double*, int64_t, int64_t, double*, cudaStream_t compute_stream);
template void cuda_add(int64_t, float*, const float*, const float*);
template void cuda_add(int64_t, float*, const float*, const double*);
template void cuda_add(int64_t, float*, const float*, const float*, cudaStream_t compute_stream);
template void cuda_add(int64_t, float*, const float*, const double*, cudaStream_t compute_stream);

View file

@ -0,0 +1,184 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
#include "custom_op_utils.h"
#include "core/common/common.h"
#ifdef USE_CUDA
#include <cuda_runtime.h>
template <typename T1, typename T2, typename T3>
void cuda_add(int64_t, T3*, const T1*, const T2*, cudaStream_t compute_stream);
template <typename T>
void cuda_slice(const T*, int64_t, int64_t, T*, cudaStream_t compute_stream);
#endif
void MyCustomKernel::Compute(OrtKernelContext* context) {
// Setup inputs
const OrtValue* input_X = ort_.KernelContext_GetInput(context, 0);
const OrtValue* input_Y = ort_.KernelContext_GetInput(context, 1);
const float* X = ort_.GetTensorData<float>(input_X);
const float* Y = ort_.GetTensorData<float>(input_Y);
// Setup output
OrtTensorDimensions dimensions(ort_, input_X);
OrtValue* output = ort_.KernelContext_GetOutput(context, 0, dimensions.data(), dimensions.size());
float* out = ort_.GetTensorMutableData<float>(output);
OrtTensorTypeAndShapeInfo* output_info = ort_.GetTensorTypeAndShape(output);
int64_t size = ort_.GetTensorShapeElementCount(output_info);
ort_.ReleaseTensorTypeAndShapeInfo(output_info);
// Do computation
#ifdef USE_CUDA
// Launch on stream 0 or user provided stream
cuda_add(size, out, X, Y, compute_stream_ == nullptr ? 0 : reinterpret_cast<cudaStream_t>(compute_stream_));
// If everything is setup correctly, custom op implementations need not have such synchronization logic.
// To make sure custom ops and ORT CUDA kernels are implicitly synchronized, create your session with a compute stream
// passed in via SessionOptions and use the same compute stream ti launch the custom op (as shown in this example)
// cudaStreamSynchronize(nullptr);
#else
ORT_UNUSED_PARAMETER(compute_stream_);
for (int64_t i = 0; i < size; i++) {
out[i] = X[i] + Y[i];
}
#endif
}
void MyCustomKernelMultipleDynamicInputs::Compute(OrtKernelContext* context) {
// Setup inputs
const OrtValue* input_X = ort_.KernelContext_GetInput(context, 0);
const OrtValue* input_Y = ort_.KernelContext_GetInput(context, 1);
// Even though this kernel backs an operator where-in both inputs can be any type and need not be homogeneous
// as a proof-of-concept, support the case where-in the first input is of float type and the second input
// is of double type. Users need to extend this logic to handle any arbitrary type should the need arise.
const float* X = ort_.GetTensorData<float>(input_X);
const double* Y = ort_.GetTensorData<double>(input_Y);
// Setup output
OrtTensorDimensions dimensions(ort_, input_X);
OrtValue* output = ort_.KernelContext_GetOutput(context, 0, dimensions.data(), dimensions.size());
float* out = ort_.GetTensorMutableData<float>(output);
OrtTensorTypeAndShapeInfo* output_info = ort_.GetTensorTypeAndShape(output);
int64_t size = ort_.GetTensorShapeElementCount(output_info);
ort_.ReleaseTensorTypeAndShapeInfo(output_info);
// Do computation
#ifdef USE_CUDA
// Launch on stream 0 or user provided stream
cuda_add(size, out, X, Y, compute_stream_ == nullptr ? 0 : reinterpret_cast<cudaStream_t>(compute_stream_));
// If everything is setup correctly, custom op implementations need not have such synchronization logic.
// To make sure custom ops and ORT CUDA kernels are implicitly synchronized, create your session with a compute stream
// passed in via SessionOptions and use the same compute stream ti launch the custom op (as shown in this example)
// cudaStreamSynchronize(nullptr);
#else
ORT_UNUSED_PARAMETER(compute_stream_);
for (int64_t i = 0; i < size; i++) {
out[i] = static_cast<float>(X[i] + Y[i]);
}
#endif
}
void MyCustomKernelWithOptionalInput::Compute(OrtKernelContext* context) {
// Setup inputs
const OrtValue* input_X1 = ort_.KernelContext_GetInput(context, 0);
const OrtValue* input_X2 = ort_.KernelContext_GetInput(context, 1);
const OrtValue* input_X3 = ort_.KernelContext_GetInput(context, 2);
const float* X1 = ort_.GetTensorData<float>(input_X1);
// The second input may or may not be present
const float* X2 = (input_X2 != nullptr) ? ort_.GetTensorData<float>(input_X2) : nullptr;
const float* X3 = ort_.GetTensorData<float>(input_X3);
// Setup output
int64_t output_dim_value = 1;
OrtValue* output = ort_.KernelContext_GetOutput(context, 0, &output_dim_value, 1);
float* out = ort_.GetTensorMutableData<float>(output);
// Only CPU EP is supported in this kernel
for (int64_t i = 0; i < output_dim_value; i++) {
out[i] = X1[i] + (X2 != nullptr ? X2[i] : 0) + X3[i];
}
}
void MyCustomKernelWithAttributes::Compute(OrtKernelContext* context) {
// Setup inputs
const OrtValue* input_X = ort_.KernelContext_GetInput(context, 0);
const float* X = ort_.GetTensorData<float>(input_X);
// Setup output
OrtTensorDimensions dimensions(ort_, input_X);
OrtValue* output = ort_.KernelContext_GetOutput(context, 0, dimensions.data(), dimensions.size());
float* out = ort_.GetTensorMutableData<float>(output);
OrtTensorTypeAndShapeInfo* output_info = ort_.GetTensorTypeAndShape(output);
int64_t size = ort_.GetTensorShapeElementCount(output_info);
ort_.ReleaseTensorTypeAndShapeInfo(output_info);
// This kernel only supports CPU EP
if (string_arr_ == "add") { // Test that the string attribute parsing went correctly
for (int64_t i = 0; i < size; i++) {
out[i] = X[i] +
float_attr_ + static_cast<float>(int_attr_) +
floats_attr_[0] + floats_attr_[1] +
static_cast<float>(ints_attr_[0]) + static_cast<float>(ints_attr_[1]);
}
} else { // if the string attribute parsing had not gone correctly - it will trigger this path and fail the test due to result mis-match
for (int64_t i = 0; i < size; i++) {
out[i] = 0.f;
}
}
}
template <typename T>
static void custom_slice(const T* X, int64_t from, int64_t to, T* Y, void* compute_stream) {
#ifdef USE_CUDA
// Launch on stream 0 or user provided stream
cuda_slice(X, from, to, Y, compute_stream == nullptr ? 0 : reinterpret_cast<cudaStream_t>(compute_stream));
// If everything is setup correctly, custom op implementations need not have such synchronization logic.
// To make sure custom ops and ORT CUDA kernels are implicitly synchronized, create your session with a compute stream
// passed in via SessionOptions and use the same compute stream ti launch the custom op (as shown in this example)
// cudaStreamSynchronize(nullptr);
#else
ORT_UNUSED_PARAMETER(compute_stream);
for (auto i = from; i < to; i++) {
Y[i - from] = X[i];
}
#endif
}
void SliceCustomOpKernel::Compute(OrtKernelContext* context) {
// Setup inputs and outputs
const OrtValue* input_X = ort_.KernelContext_GetInput(context, 0);
const OrtValue* input_from = ort_.KernelContext_GetInput(context, 1);
const OrtValue* input_to = ort_.KernelContext_GetInput(context, 2);
OrtTensorTypeAndShapeInfo* input_X_info = ort_.GetTensorTypeAndShape(input_X);
ONNXTensorElementDataType input_X_type = ort_.GetTensorElementType(input_X_info);
ort_.ReleaseTensorTypeAndShapeInfo(input_X_info);
#if USE_CUDA
int64_t slice_from = 0;
int64_t slice_to = 0;
cudaMemcpy(&slice_from, ort_.GetTensorData<int64_t>(input_from), sizeof(int64_t), cudaMemcpyDeviceToHost);
cudaMemcpy(&slice_to, ort_.GetTensorData<int64_t>(input_to), sizeof(int64_t), cudaMemcpyDeviceToHost);
#else
int64_t slice_from = *ort_.GetTensorData<int64_t>(input_from);
int64_t slice_to = *ort_.GetTensorData<int64_t>(input_to);
#endif
std::vector<int64_t> output_dims = {slice_to - slice_from};
OrtValue* output = ort_.KernelContext_GetOutput(context, 0, output_dims.data(), output_dims.size());
// do slice
switch (input_X_type) {
case ONNX_TENSOR_ELEMENT_DATA_TYPE_FLOAT:
custom_slice(ort_.GetTensorData<float>(input_X), slice_from, slice_to,
ort_.GetTensorMutableData<float>(output), compute_stream_);
break;
case ONNX_TENSOR_ELEMENT_DATA_TYPE_DOUBLE:
custom_slice(ort_.GetTensorData<double>(input_X), slice_from, slice_to,
ort_.GetTensorMutableData<double>(output), compute_stream_);
break;
default:
ORT_THROW("Unsupported input type");
}
}

View file

@ -0,0 +1,211 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
#include "core/session/onnxruntime_cxx_api.h"
#include <vector>
#ifdef USE_CUDA
#include <cuda_runtime.h>
#endif
struct Input {
const char* name = nullptr;
std::vector<int64_t> dims;
std::vector<float> values;
};
struct OrtTensorDimensions : std::vector<int64_t> {
OrtTensorDimensions(Ort::CustomOpApi ort, const OrtValue* value) {
OrtTensorTypeAndShapeInfo* info = ort.GetTensorTypeAndShape(value);
std::vector<int64_t>::operator=(ort.GetTensorShape(info));
ort.ReleaseTensorTypeAndShapeInfo(info);
}
};
struct MyCustomKernel {
MyCustomKernel(Ort::CustomOpApi ort, const OrtKernelInfo* /*info*/, void* compute_stream)
: ort_(ort), compute_stream_(compute_stream) {
}
void Compute(OrtKernelContext* context);
private:
Ort::CustomOpApi ort_;
void* compute_stream_;
};
struct MyCustomOp : Ort::CustomOpBase<MyCustomOp, MyCustomKernel> {
explicit MyCustomOp(const char* provider, void* compute_stream) : provider_(provider), compute_stream_(compute_stream) {}
void* CreateKernel(Ort::CustomOpApi api, const OrtKernelInfo* info) const { return new MyCustomKernel(api, info, compute_stream_); };
const char* GetName() const { return "Foo"; };
const char* GetExecutionProviderType() const { return provider_; };
size_t GetInputTypeCount() const { return 2; };
ONNXTensorElementDataType GetInputType(size_t /*index*/) const {
// Both the inputs need to be necessarily of float type
return ONNX_TENSOR_ELEMENT_DATA_TYPE_FLOAT;
};
size_t GetOutputTypeCount() const { return 1; };
ONNXTensorElementDataType GetOutputType(size_t /*index*/) const { return ONNX_TENSOR_ELEMENT_DATA_TYPE_FLOAT; };
private:
const char* provider_;
void* compute_stream_;
};
struct MyCustomKernelMultipleDynamicInputs {
MyCustomKernelMultipleDynamicInputs(Ort::CustomOpApi ort, const OrtKernelInfo* /*info*/, void* compute_stream)
: ort_(ort), compute_stream_(compute_stream) {
}
void Compute(OrtKernelContext* context);
private:
Ort::CustomOpApi ort_;
void* compute_stream_;
};
struct MyCustomOpMultipleDynamicInputs : Ort::CustomOpBase<MyCustomOpMultipleDynamicInputs, MyCustomKernelMultipleDynamicInputs> {
explicit MyCustomOpMultipleDynamicInputs(const char* provider, void* compute_stream) : provider_(provider), compute_stream_(compute_stream) {}
void* CreateKernel(Ort::CustomOpApi api, const OrtKernelInfo* info) const { return new MyCustomKernelMultipleDynamicInputs(api, info, compute_stream_); };
const char* GetName() const { return "Foo"; };
const char* GetExecutionProviderType() const { return provider_; };
size_t GetInputTypeCount() const { return 2; };
ONNXTensorElementDataType GetInputType(size_t /*index*/) const {
// Both the inputs are dynamic typed (i.e.) they can be any type and need not be
// homogeneous
return ONNX_TENSOR_ELEMENT_DATA_TYPE_UNDEFINED;
};
size_t GetOutputTypeCount() const { return 1; };
ONNXTensorElementDataType GetOutputType(size_t /*index*/) const { return ONNX_TENSOR_ELEMENT_DATA_TYPE_FLOAT; };
private:
const char* provider_;
void* compute_stream_;
};
struct MyCustomKernelWithOptionalInput {
MyCustomKernelWithOptionalInput(Ort::CustomOpApi ort, const OrtKernelInfo* /*info*/) : ort_(ort) {
}
void Compute(OrtKernelContext* context);
private:
Ort::CustomOpApi ort_;
};
struct MyCustomOpWithOptionalInput : Ort::CustomOpBase<MyCustomOpWithOptionalInput, MyCustomKernelWithOptionalInput> {
explicit MyCustomOpWithOptionalInput(const char* provider) : provider_(provider) {}
void* CreateKernel(Ort::CustomOpApi api, const OrtKernelInfo* info) const { return new MyCustomKernelWithOptionalInput(api, info); };
const char* GetName() const { return "FooBar"; };
const char* GetExecutionProviderType() const { return provider_; };
size_t GetInputTypeCount() const { return 3; };
ONNXTensorElementDataType GetInputType(size_t /*index*/) const { return ONNX_TENSOR_ELEMENT_DATA_TYPE_FLOAT; };
OrtCustomOpInputOutputCharacteristic GetInputCharacteristic(size_t index) const {
// The second input (index == 1) is optional
if (index == 1)
return OrtCustomOpInputOutputCharacteristic::INPUT_OUTPUT_OPTIONAL;
return OrtCustomOpInputOutputCharacteristic::INPUT_OUTPUT_REQUIRED;
}
size_t GetOutputTypeCount() const { return 1; };
ONNXTensorElementDataType GetOutputType(size_t /*index*/) const { return ONNX_TENSOR_ELEMENT_DATA_TYPE_FLOAT; };
OrtCustomOpInputOutputCharacteristic GetOutputCharacteristic(size_t /*index*/) const {
return OrtCustomOpInputOutputCharacteristic::INPUT_OUTPUT_REQUIRED;
}
private:
const char* provider_;
};
struct MyCustomKernelWithAttributes {
MyCustomKernelWithAttributes(Ort::CustomOpApi ort, const OrtKernelInfo* info) : ort_(ort) {
int_attr_ = ort_.KernelInfoGetAttribute<int64_t>(info, "int_attr");
float_attr_ = ort_.KernelInfoGetAttribute<float>(info, "float_attr");
ints_attr_ = ort_.KernelInfoGetAttribute<std::vector<int64_t>>(info, "ints_attr");
floats_attr_ = ort_.KernelInfoGetAttribute<std::vector<float>>(info, "floats_attr");
string_arr_ = ort_.KernelInfoGetAttribute<std::string>(info, "string_attr");
}
void Compute(OrtKernelContext* context);
private:
Ort::CustomOpApi ort_;
int64_t int_attr_;
float float_attr_;
std::vector<int64_t> ints_attr_;
std::vector<float> floats_attr_;
std::string string_arr_;
};
struct MyCustomOpWithAttributes : Ort::CustomOpBase<MyCustomOpWithAttributes, MyCustomKernelWithAttributes> {
explicit MyCustomOpWithAttributes(const char* provider) : provider_(provider) {}
void* CreateKernel(Ort::CustomOpApi api, const OrtKernelInfo* info) const { return new MyCustomKernelWithAttributes(api, info); };
const char* GetName() const { return "FooBar_Attr"; };
const char* GetExecutionProviderType() const { return provider_; };
size_t GetInputTypeCount() const { return 1; };
ONNXTensorElementDataType GetInputType(size_t /*index*/) const { return ONNX_TENSOR_ELEMENT_DATA_TYPE_FLOAT; };
size_t GetOutputTypeCount() const { return 1; };
ONNXTensorElementDataType GetOutputType(size_t /*index*/) const { return ONNX_TENSOR_ELEMENT_DATA_TYPE_FLOAT; };
private:
const char* provider_;
};
//Slice array of floats or doubles between [from, to) and save to output
struct SliceCustomOpKernel {
SliceCustomOpKernel(Ort::CustomOpApi ort, const OrtKernelInfo* /*info*/, void* compute_stream)
: ort_(ort), compute_stream_(compute_stream) {
}
void Compute(OrtKernelContext* context);
private:
Ort::CustomOpApi ort_;
void* compute_stream_;
};
struct SliceCustomOp : Ort::CustomOpBase<SliceCustomOp, SliceCustomOpKernel> {
explicit SliceCustomOp(const char* provider, void* compute_stream) : provider_(provider), compute_stream_(compute_stream) {}
void* CreateKernel(Ort::CustomOpApi api, const OrtKernelInfo* info) const {
return new SliceCustomOpKernel(api, info, compute_stream_);
};
const char* GetName() const { return "Slice"; };
const char* GetExecutionProviderType() const { return provider_; };
size_t GetInputTypeCount() const { return 3; };
ONNXTensorElementDataType GetInputType(size_t index) const {
if (index == 0)
return ONNX_TENSOR_ELEMENT_DATA_TYPE_UNDEFINED; // input array of float or double
else if (index == 1)
return ONNX_TENSOR_ELEMENT_DATA_TYPE_INT64; // slice from
// index 2 (keep compiler happy on Linux)
return ONNX_TENSOR_ELEMENT_DATA_TYPE_INT64; // slice to
};
size_t GetOutputTypeCount() const { return 1; };
ONNXTensorElementDataType GetOutputType(size_t /*index*/) const {
return ONNX_TENSOR_ELEMENT_DATA_TYPE_UNDEFINED;
}
private:
const char* provider_;
void* compute_stream_;
};

View file

@ -22,6 +22,7 @@
#include "test_allocator.h"
#include "test_fixture.h"
#include "utils.h"
#include "custom_op_utils.h"
#ifdef _WIN32
#include <Windows.h>
@ -87,14 +88,17 @@ static void TestInference(Ort::Env& env, const std::basic_string<ORTCHAR_T>& mod
OrtCustomOpDomain* custom_op_domain_ptr,
const char* custom_op_library_filename,
void** library_handle = nullptr,
bool test_session_creation_only = false) {
bool test_session_creation_only = false,
void* cuda_compute_stream = nullptr) {
Ort::SessionOptions session_options;
if (provider_type == 1) {
#ifdef USE_CUDA
Ort::ThrowOnError(OrtSessionOptionsAppendExecutionProvider_CUDA(session_options, 0));
std::cout << "Running simple inference with cuda provider" << std::endl;
auto cuda_options = CreateDefaultOrtCudaProviderOptionsWithCustomStream(cuda_compute_stream);
session_options.AppendExecutionProvider_CUDA(cuda_options);
#else
ORT_UNUSED_PARAMETER(cuda_compute_stream);
return;
#endif
} else if (provider_type == 2) {
@ -241,9 +245,11 @@ TEST(CApiTest, custom_op_handler) {
std::vector<float> expected_values_y = {2.0f, 4.0f, 6.0f, 8.0f, 10.0f, 12.0f};
#ifdef USE_CUDA
MyCustomOp custom_op{onnxruntime::kCudaExecutionProvider};
cudaStream_t compute_stream = nullptr;
cudaStreamCreateWithFlags(&compute_stream, cudaStreamNonBlocking);
MyCustomOp custom_op{onnxruntime::kCudaExecutionProvider, compute_stream};
#else
MyCustomOp custom_op{onnxruntime::kCpuExecutionProvider};
MyCustomOp custom_op{onnxruntime::kCpuExecutionProvider, nullptr};
#endif
Ort::CustomOpDomain custom_op_domain("");
@ -251,111 +257,14 @@ TEST(CApiTest, custom_op_handler) {
#ifdef USE_CUDA
TestInference<float>(*ort_env, CUSTOM_OP_MODEL_URI, inputs, "Y", expected_dims_y, expected_values_y, 1,
custom_op_domain, nullptr, nullptr);
custom_op_domain, nullptr, nullptr, false, compute_stream);
cudaStreamDestroy(compute_stream);
#else
TestInference<float>(*ort_env, CUSTOM_OP_MODEL_URI, inputs, "Y", expected_dims_y, expected_values_y, 0,
custom_op_domain, nullptr);
#endif
}
template <typename T>
void cuda_slice(const T*, int64_t, int64_t, T*);
template <typename T>
void custom_slice(const T* X, int64_t from, int64_t to, T* Y) {
#ifdef USE_CUDA
cuda_slice(X, from, to, Y);
#else
for (auto i = from; i < to; i++) {
Y[i - from] = X[i];
}
#endif
}
//Slice array of floats or doubles between [from, to) and save to output
struct SliceCustomOpKernel {
SliceCustomOpKernel(Ort::CustomOpApi ort, const OrtKernelInfo* /*info*/) : ort_(ort) {
}
void Compute(OrtKernelContext* context) {
// Setup inputs and outputs
const OrtValue* input_X = ort_.KernelContext_GetInput(context, 0);
const OrtValue* input_from = ort_.KernelContext_GetInput(context, 1);
const OrtValue* input_to = ort_.KernelContext_GetInput(context, 2);
OrtTensorTypeAndShapeInfo* input_X_info = ort_.GetTensorTypeAndShape(input_X);
ONNXTensorElementDataType input_X_type = ort_.GetTensorElementType(input_X_info);
ort_.ReleaseTensorTypeAndShapeInfo(input_X_info);
#if USE_CUDA
int64_t slice_from = 0;
int64_t slice_to = 0;
cudaMemcpy(&slice_from, ort_.GetTensorData<int64_t>(input_from), sizeof(int64_t), cudaMemcpyDeviceToHost);
cudaMemcpy(&slice_to, ort_.GetTensorData<int64_t>(input_to), sizeof(int64_t), cudaMemcpyDeviceToHost);
#else
int64_t slice_from = *ort_.GetTensorData<int64_t>(input_from);
int64_t slice_to = *ort_.GetTensorData<int64_t>(input_to);
#endif
std::vector<int64_t> output_dims = {slice_to - slice_from};
OrtValue* output = ort_.KernelContext_GetOutput(context, 0, output_dims.data(), output_dims.size());
// do slice
switch (input_X_type) {
case ONNX_TENSOR_ELEMENT_DATA_TYPE_FLOAT:
custom_slice(ort_.GetTensorData<float>(input_X), slice_from, slice_to,
ort_.GetTensorMutableData<float>(output));
break;
case ONNX_TENSOR_ELEMENT_DATA_TYPE_DOUBLE:
custom_slice(ort_.GetTensorData<double>(input_X), slice_from, slice_to,
ort_.GetTensorMutableData<double>(output));
break;
default:
ORT_THROW("Unsupported input type: ", input_X_type);
}
} // Compute
private:
Ort::CustomOpApi ort_;
};
struct SliceCustomOp : Ort::CustomOpBase<SliceCustomOp, SliceCustomOpKernel> {
explicit SliceCustomOp(const char* provider) : provider_(provider) {}
void* CreateKernel(Ort::CustomOpApi api, const OrtKernelInfo* info) const {
return new SliceCustomOpKernel(api, info);
};
const char* GetName() const { return "Slice"; };
const char* GetExecutionProviderType() const { return provider_; };
size_t GetInputTypeCount() const { return 3; };
ONNXTensorElementDataType GetInputType(size_t index) const {
switch (index) {
case 0:
return ONNX_TENSOR_ELEMENT_DATA_TYPE_UNDEFINED; // input array of float or double
break;
case 1:
return ONNX_TENSOR_ELEMENT_DATA_TYPE_INT64; // slice from
break;
case 2:
return ONNX_TENSOR_ELEMENT_DATA_TYPE_INT64; // slice to
break;
default:
ORT_THROW("Invalid input index: ", index);
}
};
size_t GetOutputTypeCount() const { return 1; };
ONNXTensorElementDataType GetOutputType(size_t index) const {
switch (index) {
case 0:
return ONNX_TENSOR_ELEMENT_DATA_TYPE_UNDEFINED;
break;
default:
ORT_THROW("Invalid output index: ", index);
}
}
private:
const char* provider_;
};
//test custom op which accepts float and double as inputs
TEST(CApiTest, varied_input_custom_op_handler) {
std::vector<Input> inputs(2);
@ -369,9 +278,11 @@ TEST(CApiTest, varied_input_custom_op_handler) {
std::vector<float> expected_values_z = {10.0f};
#ifdef USE_CUDA
SliceCustomOp slice_custom_op{onnxruntime::kCudaExecutionProvider};
cudaStream_t compute_stream = nullptr;
cudaStreamCreateWithFlags(&compute_stream, cudaStreamNonBlocking);
SliceCustomOp slice_custom_op{onnxruntime::kCudaExecutionProvider, compute_stream};
#else
SliceCustomOp slice_custom_op{onnxruntime::kCpuExecutionProvider};
SliceCustomOp slice_custom_op{onnxruntime::kCpuExecutionProvider, nullptr};
#endif
Ort::CustomOpDomain custom_op_domain("abc");
@ -379,7 +290,8 @@ TEST(CApiTest, varied_input_custom_op_handler) {
#ifdef USE_CUDA
TestInference<float>(*ort_env, VARIED_INPUT_CUSTOM_OP_MODEL_URI, inputs, "Z",
expected_dims_z, expected_values_z, 1, custom_op_domain, nullptr, nullptr);
expected_dims_z, expected_values_z, 1, custom_op_domain, nullptr, nullptr, false, compute_stream);
cudaStreamDestroy(compute_stream);
#else
TestInference<float>(*ort_env, VARIED_INPUT_CUSTOM_OP_MODEL_URI, inputs, "Z",
expected_dims_z, expected_values_z, 0, custom_op_domain, nullptr);
@ -388,9 +300,11 @@ TEST(CApiTest, varied_input_custom_op_handler) {
TEST(CApiTest, multiple_varied_input_custom_op_handler) {
#ifdef USE_CUDA
MyCustomOpMultipleDynamicInputs custom_op{onnxruntime::kCudaExecutionProvider};
cudaStream_t compute_stream = nullptr;
cudaStreamCreateWithFlags(&compute_stream, cudaStreamNonBlocking);
MyCustomOpMultipleDynamicInputs custom_op{onnxruntime::kCudaExecutionProvider, compute_stream};
#else
MyCustomOpMultipleDynamicInputs custom_op{onnxruntime::kCpuExecutionProvider};
MyCustomOpMultipleDynamicInputs custom_op{onnxruntime::kCpuExecutionProvider, nullptr};
#endif
Ort::CustomOpDomain custom_op_domain("");
@ -399,7 +313,8 @@ TEST(CApiTest, multiple_varied_input_custom_op_handler) {
Ort::SessionOptions session_options;
#ifdef USE_CUDA
Ort::ThrowOnError(OrtSessionOptionsAppendExecutionProvider_CUDA(session_options, 0));
auto cuda_options = CreateDefaultOrtCudaProviderOptionsWithCustomStream(compute_stream);
session_options.AppendExecutionProvider_CUDA(cuda_options);
#endif
session_options.Add(custom_op_domain);
@ -444,6 +359,10 @@ TEST(CApiTest, multiple_varied_input_custom_op_handler) {
for (size_t i = 0; i != total_len; ++i) {
ASSERT_EQ(values_y[i], f[i]);
}
#ifdef USE_CUDA
cudaStreamDestroy(compute_stream);
#endif
}
TEST(CApiTest, optional_input_output_custom_op_handler) {
@ -589,8 +508,9 @@ TEST(CApiTest, RegisterCustomOpForCPUAndCUDA) {
std::vector<int64_t> expected_dims_y = {3, 2};
std::vector<float> expected_values_y = {2.0f, 4.0f, 6.0f, 8.0f, 10.0f, 12.0f};
MyCustomOp custom_op_cpu{onnxruntime::kCpuExecutionProvider};
MyCustomOp custom_op_cuda{onnxruntime::kCudaExecutionProvider};
MyCustomOp custom_op_cpu{onnxruntime::kCpuExecutionProvider, nullptr};
// We are going to test session creation only - hence it is not a problem to use the default stream as the compute stream for the custom op
MyCustomOp custom_op_cuda{onnxruntime::kCudaExecutionProvider, nullptr};
Ort::CustomOpDomain custom_op_domain("");
custom_op_domain.Add(&custom_op_cpu);
custom_op_domain.Add(&custom_op_cuda);

View file

@ -7,12 +7,14 @@
// custom ops are only supported in a minimal build if explicitly enabled
#if !defined(ORT_MINIMAL_BUILD) || defined(ORT_MINIMAL_BUILD_CUSTOM_OPS)
#include "core/common/common.h"
#include "core/common/make_unique.h"
#include "core/graph/constants.h"
#include "core/session/onnxruntime_cxx_api.h"
#include "test_allocator.h"
#include "utils.h"
#include "custom_op_utils.h"
#include "gtest/gtest.h"
@ -21,15 +23,16 @@ extern std::unique_ptr<Ort::Env> ort_env;
static void TestInference(Ort::Env& env, const std::basic_string<ORTCHAR_T>& model_uri,
const std::vector<Input>& inputs, const char* output_name,
const std::vector<int64_t>& expected_dims_y, const std::vector<float>& expected_values_y,
Ort::CustomOpDomain& custom_op_domain) {
Ort::CustomOpDomain& custom_op_domain, void* cuda_compute_stream = nullptr) {
Ort::SessionOptions session_options;
session_options.Add(custom_op_domain);
#ifdef USE_CUDA
OrtCUDAProviderOptions cuda_options{};
auto cuda_options = CreateDefaultOrtCudaProviderOptionsWithCustomStream(cuda_compute_stream);
session_options.AppendExecutionProvider_CUDA(cuda_options);
#else
ORT_UNUSED_PARAMETER(cuda_compute_stream);
#endif
Ort::Session session(env, model_uri.c_str(), session_options);
MockedOrtAllocator allocator;
@ -72,9 +75,14 @@ TEST(OrtFormatCustomOpTests, ConvertOnnxModelToOrt) {
const std::basic_string<ORTCHAR_T> ort_file = ORT_TSTR("testdata/foo_1.onnx.test_output.ort");
#ifdef USE_CUDA
MyCustomOp custom_op{onnxruntime::kCudaExecutionProvider};
// We need to launch our custom op in the same compute stream as the one we will be
// passing to ORT via Session options to use for the entire session (i.e.) CUDA ORT kernels
// will now use the same stream too
cudaStream_t compute_stream = nullptr;
cudaStreamCreateWithFlags(&compute_stream, cudaStreamNonBlocking);
MyCustomOp custom_op{onnxruntime::kCudaExecutionProvider, compute_stream};
#else
MyCustomOp custom_op{onnxruntime::kCpuExecutionProvider};
MyCustomOp custom_op{onnxruntime::kCpuExecutionProvider, nullptr};
#endif
Ort::CustomOpDomain custom_op_domain("");
custom_op_domain.Add(&custom_op);
@ -105,7 +113,12 @@ TEST(OrtFormatCustomOpTests, ConvertOnnxModelToOrt) {
std::vector<int64_t> expected_dims_y = {3, 2};
std::vector<float> expected_values_y = {2.0f, 4.0f, 6.0f, 8.0f, 10.0f, 12.0f};
TestInference(*ort_env, ort_file, inputs, "Y", expected_dims_y, expected_values_y, custom_op_domain);
#ifdef USE_CUDA
TestInference(*ort_env, ort_file, inputs, "Y", expected_dims_y, expected_values_y, custom_op_domain, compute_stream);
cudaStreamDestroy(compute_stream);
#else
TestInference(*ort_env, ort_file, inputs, "Y", expected_dims_y, expected_values_y, custom_op_domain, nullptr);
#endif
}
#endif // if !defined(ORT_MINIMAL_BUILD)
@ -115,7 +128,7 @@ TEST(OrtFormatCustomOpTests, ConvertOnnxModelToOrt) {
TEST(OrtFormatCustomOpTests, LoadOrtModel) {
const std::basic_string<ORTCHAR_T> ort_file = ORT_TSTR("testdata/foo_1.onnx.ort");
MyCustomOp custom_op{onnxruntime::kCpuExecutionProvider};
MyCustomOp custom_op{onnxruntime::kCpuExecutionProvider, nullptr};
Ort::CustomOpDomain custom_op_domain("");
custom_op_domain.Add(&custom_op);

View file

@ -2,117 +2,16 @@
// Licensed under the MIT License.
#include "utils.h"
#include <limits>
#ifdef USE_CUDA
#include <cuda_runtime.h>
template <typename T1, typename T2, typename T3>
void cuda_add(int64_t, T3*, const T1*, const T2*);
#endif
void MyCustomKernel::Compute(OrtKernelContext* context) {
// Setup inputs
const OrtValue* input_X = ort_.KernelContext_GetInput(context, 0);
const OrtValue* input_Y = ort_.KernelContext_GetInput(context, 1);
const float* X = ort_.GetTensorData<float>(input_X);
const float* Y = ort_.GetTensorData<float>(input_Y);
// Setup output
OrtTensorDimensions dimensions(ort_, input_X);
OrtValue* output = ort_.KernelContext_GetOutput(context, 0, dimensions.data(), dimensions.size());
float* out = ort_.GetTensorMutableData<float>(output);
OrtTensorTypeAndShapeInfo* output_info = ort_.GetTensorTypeAndShape(output);
int64_t size = ort_.GetTensorShapeElementCount(output_info);
ort_.ReleaseTensorTypeAndShapeInfo(output_info);
// Do computation
#ifdef USE_CUDA
cuda_add(size, out, X, Y);
cudaStreamSynchronize(nullptr);
#else
for (int64_t i = 0; i < size; i++) {
out[i] = X[i] + Y[i];
}
#endif
}
void MyCustomKernelMultipleDynamicInputs::Compute(OrtKernelContext* context) {
// Setup inputs
const OrtValue* input_X = ort_.KernelContext_GetInput(context, 0);
const OrtValue* input_Y = ort_.KernelContext_GetInput(context, 1);
// Even though this kernel backs an operator where-in both inputs can be any type and need not be homogeneous
// as a proof-of-concept, support the case where-in the first input is of float type and the second input
// is of double type. Users need to extend this logic to handle any arbitrary type should the need arise.
const float* X = ort_.GetTensorData<float>(input_X);
const double* Y = ort_.GetTensorData<double>(input_Y);
// Setup output
OrtTensorDimensions dimensions(ort_, input_X);
OrtValue* output = ort_.KernelContext_GetOutput(context, 0, dimensions.data(), dimensions.size());
float* out = ort_.GetTensorMutableData<float>(output);
OrtTensorTypeAndShapeInfo* output_info = ort_.GetTensorTypeAndShape(output);
int64_t size = ort_.GetTensorShapeElementCount(output_info);
ort_.ReleaseTensorTypeAndShapeInfo(output_info);
// Do computation
#ifdef USE_CUDA
cuda_add(size, out, X, Y);
cudaStreamSynchronize(nullptr);
#else
for (int64_t i = 0; i < size; i++) {
out[i] = static_cast<float>(X[i] + Y[i]);
}
#endif
}
void MyCustomKernelWithOptionalInput::Compute(OrtKernelContext* context) {
// Setup inputs
const OrtValue* input_X1 = ort_.KernelContext_GetInput(context, 0);
const OrtValue* input_X2 = ort_.KernelContext_GetInput(context, 1);
const OrtValue* input_X3 = ort_.KernelContext_GetInput(context, 2);
const float* X1 = ort_.GetTensorData<float>(input_X1);
// The second input may or may not be present
const float* X2 = (input_X2 != nullptr) ? ort_.GetTensorData<float>(input_X2) : nullptr;
const float* X3 = ort_.GetTensorData<float>(input_X3);
// Setup output
int64_t output_dim_value = 1;
OrtValue* output = ort_.KernelContext_GetOutput(context, 0, &output_dim_value, 1);
float* out = ort_.GetTensorMutableData<float>(output);
// Only CPU EP is supported in this kernel
for (int64_t i = 0; i < output_dim_value; i++) {
out[i] = X1[i] + (X2 != nullptr ? X2[i] : 0) + X3[i];
}
}
void MyCustomKernelWithAttributes::Compute(OrtKernelContext* context) {
// Setup inputs
const OrtValue* input_X = ort_.KernelContext_GetInput(context, 0);
const float* X = ort_.GetTensorData<float>(input_X);
// Setup output
OrtTensorDimensions dimensions(ort_, input_X);
OrtValue* output = ort_.KernelContext_GetOutput(context, 0, dimensions.data(), dimensions.size());
float* out = ort_.GetTensorMutableData<float>(output);
OrtTensorTypeAndShapeInfo* output_info = ort_.GetTensorTypeAndShape(output);
int64_t size = ort_.GetTensorShapeElementCount(output_info);
ort_.ReleaseTensorTypeAndShapeInfo(output_info);
// This kernel only supports CPU EP
if (string_arr_ == "add") { // Test that the string attribute parsing went correctly
for (int64_t i = 0; i < size; i++) {
out[i] = X[i] +
float_attr_ + static_cast<float>(int_attr_) +
floats_attr_[0] + floats_attr_[1] +
static_cast<float>(ints_attr_[0]) + static_cast<float>(ints_attr_[1]);
}
} else { // if the string attribute parsing had not gone correctly - it will trigger this path and fail the test due to result mis-match
for (int64_t i = 0; i < size; i++) {
out[i] = 0.f;
}
}
OrtCUDAProviderOptions CreateDefaultOrtCudaProviderOptionsWithCustomStream(void* cuda_compute_stream) {
OrtCUDAProviderOptions cuda_options{
0,
OrtCudnnConvAlgoSearch::EXHAUSTIVE,
std::numeric_limits<size_t>::max(),
0,
true,
cuda_compute_stream != nullptr ? 1 : 0,
cuda_compute_stream != nullptr ? cuda_compute_stream : nullptr};
return cuda_options;
}

View file

@ -1,158 +1,8 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
#pragma once
#include "core/session/onnxruntime_cxx_api.h"
#include <vector>
struct Input {
const char* name = nullptr;
std::vector<int64_t> dims;
std::vector<float> values;
};
struct OrtTensorDimensions : std::vector<int64_t> {
OrtTensorDimensions(Ort::CustomOpApi ort, const OrtValue* value) {
OrtTensorTypeAndShapeInfo* info = ort.GetTensorTypeAndShape(value);
std::vector<int64_t>::operator=(ort.GetTensorShape(info));
ort.ReleaseTensorTypeAndShapeInfo(info);
}
};
struct MyCustomKernel {
MyCustomKernel(Ort::CustomOpApi ort, const OrtKernelInfo* /*info*/) : ort_(ort) {
}
void Compute(OrtKernelContext* context);
private:
Ort::CustomOpApi ort_;
};
struct MyCustomOp : Ort::CustomOpBase<MyCustomOp, MyCustomKernel> {
explicit MyCustomOp(const char* provider) : provider_(provider) {}
void* CreateKernel(Ort::CustomOpApi api, const OrtKernelInfo* info) const { return new MyCustomKernel(api, info); };
const char* GetName() const { return "Foo"; };
const char* GetExecutionProviderType() const { return provider_; };
size_t GetInputTypeCount() const { return 2; };
ONNXTensorElementDataType GetInputType(size_t /*index*/) const {
// Both the inputs need to be necessarily of float type
return ONNX_TENSOR_ELEMENT_DATA_TYPE_FLOAT;
};
size_t GetOutputTypeCount() const { return 1; };
ONNXTensorElementDataType GetOutputType(size_t /*index*/) const { return ONNX_TENSOR_ELEMENT_DATA_TYPE_FLOAT; };
private:
const char* provider_;
};
struct MyCustomKernelMultipleDynamicInputs {
MyCustomKernelMultipleDynamicInputs(Ort::CustomOpApi ort, const OrtKernelInfo* /*info*/) : ort_(ort) {
}
void Compute(OrtKernelContext* context);
private:
Ort::CustomOpApi ort_;
};
struct MyCustomOpMultipleDynamicInputs : Ort::CustomOpBase<MyCustomOpMultipleDynamicInputs, MyCustomKernelMultipleDynamicInputs> {
explicit MyCustomOpMultipleDynamicInputs(const char* provider) : provider_(provider) {}
void* CreateKernel(Ort::CustomOpApi api, const OrtKernelInfo* info) const { return new MyCustomKernelMultipleDynamicInputs(api, info); };
const char* GetName() const { return "Foo"; };
const char* GetExecutionProviderType() const { return provider_; };
size_t GetInputTypeCount() const { return 2; };
ONNXTensorElementDataType GetInputType(size_t /*index*/) const {
// Both the inputs are dynamic typed (i.e.) they can be any type and need not be
// homogeneous
return ONNX_TENSOR_ELEMENT_DATA_TYPE_UNDEFINED;
};
size_t GetOutputTypeCount() const { return 1; };
ONNXTensorElementDataType GetOutputType(size_t /*index*/) const { return ONNX_TENSOR_ELEMENT_DATA_TYPE_FLOAT; };
private:
const char* provider_;
};
struct MyCustomKernelWithOptionalInput {
MyCustomKernelWithOptionalInput(Ort::CustomOpApi ort, const OrtKernelInfo* /*info*/) : ort_(ort) {
}
void Compute(OrtKernelContext* context);
private:
Ort::CustomOpApi ort_;
};
struct MyCustomOpWithOptionalInput : Ort::CustomOpBase<MyCustomOpWithOptionalInput, MyCustomKernelWithOptionalInput> {
explicit MyCustomOpWithOptionalInput(const char* provider) : provider_(provider) {}
void* CreateKernel(Ort::CustomOpApi api, const OrtKernelInfo* info) const { return new MyCustomKernelMultipleDynamicInputs(api, info); };
const char* GetName() const { return "FooBar"; };
const char* GetExecutionProviderType() const { return provider_; };
size_t GetInputTypeCount() const { return 3; };
ONNXTensorElementDataType GetInputType(size_t /*index*/) const { return ONNX_TENSOR_ELEMENT_DATA_TYPE_FLOAT; };
OrtCustomOpInputOutputCharacteristic GetInputCharacteristic(size_t index) const {
// The second input (index == 1) is optional
if (index == 1)
return OrtCustomOpInputOutputCharacteristic::INPUT_OUTPUT_OPTIONAL;
return OrtCustomOpInputOutputCharacteristic::INPUT_OUTPUT_REQUIRED;
}
size_t GetOutputTypeCount() const { return 1; };
ONNXTensorElementDataType GetOutputType(size_t /*index*/) const { return ONNX_TENSOR_ELEMENT_DATA_TYPE_FLOAT; };
OrtCustomOpInputOutputCharacteristic GetOutputCharacteristic(size_t /*index*/) const {
return OrtCustomOpInputOutputCharacteristic::INPUT_OUTPUT_REQUIRED;
}
private:
const char* provider_;
};
struct MyCustomKernelWithAttributes {
MyCustomKernelWithAttributes(Ort::CustomOpApi ort, const OrtKernelInfo* info) : ort_(ort) {
int_attr_ = ort_.KernelInfoGetAttribute<int64_t>(info, "int_attr");
float_attr_ = ort_.KernelInfoGetAttribute<float>(info, "float_attr");
ints_attr_ = ort_.KernelInfoGetAttribute<std::vector<int64_t>>(info, "ints_attr");
floats_attr_ = ort_.KernelInfoGetAttribute<std::vector<float>>(info, "floats_attr");
string_arr_ = ort_.KernelInfoGetAttribute<std::string>(info, "string_attr");
}
void Compute(OrtKernelContext* context);
private:
Ort::CustomOpApi ort_;
int64_t int_attr_;
float float_attr_;
std::vector<int64_t> ints_attr_;
std::vector<float> floats_attr_;
std::string string_arr_;
};
struct MyCustomOpWithAttributes : Ort::CustomOpBase<MyCustomOpWithAttributes, MyCustomKernelWithAttributes> {
explicit MyCustomOpWithAttributes(const char* provider) : provider_(provider) {}
void* CreateKernel(Ort::CustomOpApi api, const OrtKernelInfo* info) const { return new MyCustomKernelWithAttributes(api, info); };
const char* GetName() const { return "FooBar_Attr"; };
const char* GetExecutionProviderType() const { return provider_; };
size_t GetInputTypeCount() const { return 1; };
ONNXTensorElementDataType GetInputType(size_t /*index*/) const { return ONNX_TENSOR_ELEMENT_DATA_TYPE_FLOAT; };
size_t GetOutputTypeCount() const { return 1; };
ONNXTensorElementDataType GetOutputType(size_t /*index*/) const { return ONNX_TENSOR_ELEMENT_DATA_TYPE_FLOAT; };
private:
const char* provider_;
};
OrtCUDAProviderOptions CreateDefaultOrtCudaProviderOptionsWithCustomStream(void* cuda_compute_stream = nullptr);