1423 lines
54 KiB
C++
1423 lines
54 KiB
C++
// Copyright (c) 2025 PaddlePaddle Authors. All Rights Reserved.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
#include <torch/all.h>
|
|
#include <torch/library.h>
|
|
|
|
#include <sstream>
|
|
|
|
#include "gtest/gtest.h"
|
|
#include "test/cpp/utils/exception_test_utils.h"
|
|
|
|
at::Tensor mymuladd_cpu(at::Tensor a, const at::Tensor& b, double c) {
|
|
TORCH_CHECK(a.sizes() == b.sizes());
|
|
TORCH_CHECK(a.dtype() == at::kFloat);
|
|
TORCH_CHECK(b.dtype() == at::kFloat);
|
|
TORCH_INTERNAL_ASSERT(a.device().type() == at::DeviceType::CPU);
|
|
TORCH_INTERNAL_ASSERT(b.device().type() == at::DeviceType::CPU);
|
|
at::Tensor a_contig = a.contiguous();
|
|
at::Tensor b_contig = b.contiguous();
|
|
at::Tensor result = torch::empty(a_contig.sizes(), a_contig.options());
|
|
const float* a_ptr = a_contig.data_ptr<float>();
|
|
const float* b_ptr = b_contig.data_ptr<float>();
|
|
float* result_ptr = result.data_ptr<float>();
|
|
for (int64_t i = 0; i < result.numel(); i++) {
|
|
result_ptr[i] = a_ptr[i] * b_ptr[i] + c;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
template <typename T>
|
|
T generic_add(T a, T b) {
|
|
return a + b;
|
|
}
|
|
|
|
class TestClass : public torch::CustomClassHolder {
|
|
public:
|
|
int value;
|
|
std::string name;
|
|
|
|
TestClass() : value(0), name("default") {
|
|
std::cout << "TestClass::TestClass() - Default constructor" << std::endl;
|
|
}
|
|
|
|
TestClass(int v) : value(v), name("single_param") { // NOLINT
|
|
std::cout << "TestClass::TestClass(int) - Single parameter constructor"
|
|
<< std::endl;
|
|
}
|
|
|
|
TestClass(int v, const std::string& n) : value(v), name(n) {
|
|
std::cout
|
|
<< "TestClass::TestClass(int, string) - Double parameters constructor"
|
|
<< std::endl;
|
|
}
|
|
|
|
int getValue() const {
|
|
std::cout << "TestClass::getValue() - getter" << std::endl;
|
|
return value;
|
|
}
|
|
|
|
const std::string& getName() const {
|
|
std::cout << "TestClass::getName() - getter" << std::endl;
|
|
return name;
|
|
}
|
|
|
|
void setValue(int v) {
|
|
std::cout << "TestClass::setValue(int) - setter (int)" << std::endl;
|
|
value = v;
|
|
}
|
|
|
|
void setName(const std::string& n) {
|
|
std::cout << "TestClass::setName(string) - setter (string)" << std::endl;
|
|
name = n;
|
|
}
|
|
|
|
static int getDefaultValue() {
|
|
std::cout << "TestClass::getDefaultValue() - static method" << std::endl;
|
|
return 42;
|
|
}
|
|
|
|
static int addValues(int a, int b) {
|
|
std::cout << "TestClass::addValues(int, int) - static method" << std::endl;
|
|
return a + b;
|
|
}
|
|
};
|
|
|
|
torch::CppFunction MakeKwonlySchemaMethodForTestClass() {
|
|
torch::CppFunction method(
|
|
[](const torch::FunctionArgs& args) -> torch::IValue {
|
|
if (args.has_named_args()) {
|
|
throw std::runtime_error(
|
|
"Schema-normalized class method should not receive named args");
|
|
}
|
|
if (args.size() != 3) {
|
|
throw std::runtime_error("Expected 3 normalized arguments");
|
|
}
|
|
|
|
auto instance = args.get<torch::intrusive_ptr<TestClass>>(0);
|
|
const auto idx_repr = args.get_value(1).is_none()
|
|
? std::string("none")
|
|
: std::to_string(args.get<int64_t>(1));
|
|
return torch::IValue(instance->name + "|" + idx_repr + "|" +
|
|
args.get<std::string>(2));
|
|
});
|
|
// The self type is irrelevant here; this test only exercises kwarg
|
|
// forwarding and schema normalization on the instance-method overload.
|
|
method.bind_schema(torch::jit::parseSchema(
|
|
"kwonly_forwarding(Tensor self, *, int? idx=None, str mode=\"nearest\") "
|
|
"-> str"));
|
|
return method;
|
|
}
|
|
|
|
TORCH_LIBRARY(example_library, m) {
|
|
// Note that "float" in the schema corresponds to the C++ double type
|
|
// and the Python float type.
|
|
m.def("mymuladd(Tensor a, Tensor b, float c) -> Tensor");
|
|
m.class_<TestClass>("TestClass")
|
|
.def(torch::init<>())
|
|
.def(torch::init<int>())
|
|
.def(torch::init<int, std::string>())
|
|
.def("getValue", &TestClass::getValue)
|
|
.def("getName", &TestClass::getName)
|
|
.def("setValue", &TestClass::setValue)
|
|
.def("setName", &TestClass::setName)
|
|
.def_static("getDefaultValue", &TestClass::getDefaultValue)
|
|
.def_static("addValues", &TestClass::addValues);
|
|
}
|
|
|
|
TEST(test_torch_library, TestLibraryOperators) {
|
|
auto qualified_name = "example_library::mymuladd";
|
|
auto* op = torch::OperatorRegistry::instance().find_operator(qualified_name);
|
|
ASSERT_NE(op, nullptr);
|
|
auto impl_it = op->implementations.find(c10::DispatchKey::CPU);
|
|
ASSERT_NE(impl_it, op->implementations.end());
|
|
torch::FunctionArgs function_args;
|
|
function_args.add_arg(torch::IValue(at::ones({2, 2}, at::kFloat)));
|
|
function_args.add_arg(torch::IValue(at::ones({2, 2}, at::kFloat)));
|
|
function_args.add_arg(torch::IValue(2.0));
|
|
auto result = impl_it->second.call_with_args(function_args);
|
|
ASSERT_TRUE(result.get_value().is_tensor());
|
|
auto result_tensor = result.get_value().to_tensor();
|
|
}
|
|
|
|
TEST(test_torch_library, TestLibraryClasses) {
|
|
auto qualified_name = "example_library::TestClass";
|
|
const auto& class_registry = torch::ClassRegistry::instance();
|
|
bool has_class = class_registry.has_class(qualified_name);
|
|
ASSERT_TRUE(has_class);
|
|
torch::FunctionArgs constructor_args;
|
|
constructor_args.add_arg(torch::IValue(10));
|
|
constructor_args.add_arg(torch::IValue("example"));
|
|
|
|
// Call constructor
|
|
auto instance = class_registry.call_constructor_with_args(qualified_name,
|
|
constructor_args);
|
|
ASSERT_TRUE(instance.get_value().is_custom_class());
|
|
|
|
// Call getValue
|
|
auto get_value_result = class_registry.call_method_with_args(
|
|
qualified_name, "getValue", instance.get_value(), torch::FunctionArgs());
|
|
ASSERT_TRUE(get_value_result.get_value().is_int());
|
|
int value = get_value_result.get_value().to_int();
|
|
ASSERT_EQ(value, 10);
|
|
|
|
// Call setValue
|
|
torch::FunctionArgs set_value_args;
|
|
set_value_args.add_arg(torch::IValue(20));
|
|
class_registry.call_method_with_args(
|
|
qualified_name, "setValue", instance.get_value(), set_value_args);
|
|
ASSERT_EQ(instance.get_value().to_custom_class<TestClass>()->value, 20);
|
|
auto get_value_after_set = class_registry.call_method_with_args(
|
|
qualified_name, "getValue", instance.get_value(), torch::FunctionArgs());
|
|
ASSERT_EQ(get_value_after_set.get_value().to_int(), 20);
|
|
|
|
// Call getName
|
|
auto get_name_result = class_registry.call_method_with_args(
|
|
qualified_name, "getName", instance.get_value(), torch::FunctionArgs());
|
|
ASSERT_TRUE(get_name_result.get_value().is_string());
|
|
std::string name = get_name_result.get_value().to_string();
|
|
ASSERT_EQ(name, "example");
|
|
|
|
// Call setName
|
|
torch::FunctionArgs set_name_args;
|
|
set_name_args.add_arg(torch::IValue("new_example"));
|
|
class_registry.call_method_with_args(
|
|
qualified_name, "setName", instance.get_value(), set_name_args);
|
|
ASSERT_EQ(instance.get_value().to_custom_class<TestClass>()->name,
|
|
"new_example");
|
|
auto get_name_after_set = class_registry.call_method_with_args(
|
|
qualified_name, "getName", instance.get_value(), torch::FunctionArgs());
|
|
ASSERT_EQ(get_name_after_set.get_value().to_string(), "new_example");
|
|
|
|
// Call static method getDefaultValue
|
|
auto get_default_value_result = class_registry.call_static_method_with_args(
|
|
qualified_name, "getDefaultValue", torch::FunctionArgs());
|
|
ASSERT_TRUE(get_default_value_result.get_value().is_int());
|
|
int default_value = get_default_value_result.get_value().to_int();
|
|
ASSERT_EQ(default_value, 42);
|
|
|
|
// Call static method addValues
|
|
torch::FunctionArgs add_values_args;
|
|
add_values_args.add_arg(torch::IValue(5));
|
|
add_values_args.add_arg(torch::IValue(7));
|
|
auto add_values_result = class_registry.call_static_method_with_args(
|
|
qualified_name, "addValues", add_values_args);
|
|
ASSERT_TRUE(add_values_result.get_value().is_int());
|
|
int sum = add_values_result.get_value().to_int();
|
|
ASSERT_EQ(sum, 12);
|
|
}
|
|
|
|
TORCH_LIBRARY_IMPL(example_library, CPU, m) {
|
|
m.impl("mymuladd", &mymuladd_cpu);
|
|
}
|
|
|
|
TORCH_LIBRARY_FRAGMENT(example_library_fragment, m) {
|
|
m.def("int_add", &generic_add<int>);
|
|
}
|
|
|
|
TORCH_LIBRARY_FRAGMENT(example_library_fragment, m) {
|
|
m.def("string_concat", &generic_add<std::string>);
|
|
}
|
|
|
|
TEST(test_torch_library, TestFragmentOperators) {
|
|
auto qualified_name_int_add = "example_library_fragment::int_add";
|
|
auto* op_int_add =
|
|
torch::OperatorRegistry::instance().find_operator(qualified_name_int_add);
|
|
ASSERT_NE(op_int_add, nullptr);
|
|
auto impl_it_int_add =
|
|
op_int_add->implementations.find(c10::DispatchKey::CPU);
|
|
ASSERT_NE(impl_it_int_add, op_int_add->implementations.end());
|
|
torch::FunctionArgs function_args;
|
|
function_args.add_arg(torch::IValue(3));
|
|
function_args.add_arg(torch::IValue(4));
|
|
auto result = impl_it_int_add->second.call_with_args(function_args);
|
|
ASSERT_TRUE(result.get_value().is_int());
|
|
int sum = result.get_value().to_int();
|
|
ASSERT_EQ(sum, 7);
|
|
|
|
auto qualified_name_string_concat = "example_library_fragment::string_concat";
|
|
auto* op_string_concat = torch::OperatorRegistry::instance().find_operator(
|
|
qualified_name_string_concat);
|
|
ASSERT_NE(op_string_concat, nullptr);
|
|
auto impl_it_string_concat =
|
|
op_string_concat->implementations.find(c10::DispatchKey::CPU);
|
|
ASSERT_NE(impl_it_string_concat, op_string_concat->implementations.end());
|
|
torch::FunctionArgs string_args;
|
|
string_args.add_arg(torch::IValue(std::string("Hello, ")));
|
|
string_args.add_arg(torch::IValue(std::string("World!")));
|
|
auto string_result =
|
|
impl_it_string_concat->second.call_with_args(string_args);
|
|
ASSERT_TRUE(string_result.get_value().is_string());
|
|
std::string concatenated_string = string_result.get_value().to_string();
|
|
ASSERT_EQ(concatenated_string, "Hello, World!");
|
|
}
|
|
|
|
int schema_only_add(int a, int b) { return a + b; }
|
|
|
|
int schema_and_impl_add(int a, int b) { return a + b; }
|
|
|
|
int name_only_add(int a, int b) { return a + b; }
|
|
|
|
int overload_name_add(int a, int b) { return a + b; }
|
|
|
|
int dispatch_probe_cpu(int x) { return x + 1; }
|
|
|
|
int dispatch_probe_cuda(int x) { return x + 2; }
|
|
|
|
int impl_block_schema_and_fn(int x) { return x * 2; }
|
|
|
|
TORCH_LIBRARY(example_library_with_mdef_cases, m) {
|
|
m.def("schema_only_add(int a, int b) -> int");
|
|
m.def("schema_and_impl_add(int a, int b) -> int", &schema_and_impl_add);
|
|
m.def("name_only_add", &name_only_add);
|
|
m.def("schema_only_no_impl(int x) -> int");
|
|
m.def("overload.name(int a, int b) -> int", &overload_name_add);
|
|
m.def("dispatch_probe(int x) -> int");
|
|
}
|
|
|
|
TORCH_LIBRARY_IMPL(example_library_with_mdef_cases, CPU, m) {
|
|
m.impl("schema_only_add", &schema_only_add);
|
|
m.impl("dispatch_probe", &dispatch_probe_cpu);
|
|
}
|
|
|
|
TORCH_LIBRARY_IMPL(example_library_with_mdef_cases, CUDA, m) {
|
|
m.impl("dispatch_probe", &dispatch_probe_cuda);
|
|
}
|
|
|
|
TORCH_LIBRARY_IMPL(example_library_mdef_impl_block, CPU, m) {
|
|
// def() in IMPL block is explicitly ignored.
|
|
m.def("impl_block_schema_only(int x) -> int");
|
|
m.def("impl_block_schema_and_fn(int x) -> int", &impl_block_schema_and_fn);
|
|
}
|
|
|
|
at::Tensor add_scalar_to_float_tensor(const at::Tensor& input, double value) {
|
|
at::Tensor in_contig = input.contiguous();
|
|
at::Tensor output = at::empty(in_contig.sizes(), in_contig.options());
|
|
const float* in_ptr = in_contig.data_ptr<float>();
|
|
float* out_ptr = output.data_ptr<float>();
|
|
for (int64_t idx = 0; idx < output.numel(); ++idx) {
|
|
out_ptr[idx] = in_ptr[idx] + static_cast<float>(value);
|
|
}
|
|
return output;
|
|
}
|
|
|
|
at::Tensor mdef_schema_matrix_basic_types(const at::Tensor& x,
|
|
int i,
|
|
double f,
|
|
bool b,
|
|
const std::string& s,
|
|
const std::string& d,
|
|
double n,
|
|
std::optional<int64_t> z) {
|
|
const double bias = static_cast<double>(i) + f + (b ? 1.0 : 0.0) +
|
|
static_cast<double>(s.size()) +
|
|
static_cast<double>(d.size()) + n +
|
|
static_cast<double>(z.value_or(0));
|
|
return add_scalar_to_float_tensor(x, bias);
|
|
}
|
|
|
|
double mdef_schema_matrix_number_aliases(double a, double b) { return a + b; }
|
|
|
|
std::tuple<at::Tensor, int64_t> mdef_schema_matrix_optional_types(
|
|
std::optional<int64_t> i,
|
|
std::optional<double> f,
|
|
std::optional<bool> b,
|
|
std::optional<std::string> s,
|
|
std::optional<at::Tensor> t) {
|
|
const int64_t score = i.value_or(0) + static_cast<int64_t>(f.value_or(0.0)) +
|
|
(b.value_or(false) ? 1 : 0) +
|
|
static_cast<int64_t>(s ? s->size() : 0);
|
|
at::Tensor base = t.has_value() ? *t : at::zeros({1}, at::kFloat);
|
|
return {add_scalar_to_float_tensor(base, static_cast<double>(score)), score};
|
|
}
|
|
|
|
std::tuple<at::Tensor, at::Tensor> mdef_schema_matrix_tuple_optional(
|
|
std::optional<std::tuple<at::Tensor, int64_t, double, bool, std::string>>
|
|
payload) {
|
|
if (!payload.has_value()) {
|
|
return {at::zeros({1}, at::kFloat), at::ones({1}, at::kFloat)};
|
|
}
|
|
const auto& [x, i, f, b, s] = *payload;
|
|
const double rhs = static_cast<double>(i) + f + (b ? 1.0 : 0.0) + s.size();
|
|
return {x, add_scalar_to_float_tensor(x, rhs)};
|
|
}
|
|
|
|
std::string mdef_schema_matrix_defaults_mix(int i,
|
|
double f,
|
|
bool b,
|
|
const std::string& quoted,
|
|
const std::string& ident) {
|
|
return std::to_string(i) + "|" +
|
|
std::to_string(static_cast<int64_t>(f * 10.0)) + "|" +
|
|
(b ? "1" : "0") + "|" + quoted + "|" + ident;
|
|
}
|
|
|
|
void mdef_schema_matrix_alias_and_kwonly(const at::Tensor& x,
|
|
std::optional<int64_t> idx,
|
|
const std::string& mode) {
|
|
if (idx.has_value()) {
|
|
(void)x[idx.value()];
|
|
}
|
|
(void)mode;
|
|
}
|
|
|
|
TORCH_LIBRARY(example_library_mdef_schema_matrix, m) {
|
|
m.def(
|
|
"basic_types(Tensor x, int i, float f, bool b, str s, Device d, Scalar "
|
|
"n, NoneType z=None) -> Tensor",
|
|
&mdef_schema_matrix_basic_types);
|
|
m.def("number_aliases(Scalar a, number b) -> Scalar",
|
|
&mdef_schema_matrix_number_aliases);
|
|
m.def(
|
|
"optional_types(int? i=None, float? f=None, bool? b=None, str? s=None, "
|
|
"Tensor? t=None) -> (Tensor, int)",
|
|
&mdef_schema_matrix_optional_types);
|
|
m.def(
|
|
"tuple_optional((Tensor, int, float, bool, str)? payload=None) -> "
|
|
"(Tensor, Tensor)",
|
|
&mdef_schema_matrix_tuple_optional);
|
|
m.def(
|
|
"defaults_mix(int i=3, float f=-2.5, bool b=true, str quoted=\"abc\", "
|
|
"str ident=cpu) -> str",
|
|
&mdef_schema_matrix_defaults_mix);
|
|
m.def(
|
|
"alias_and_kwonly(Tensor(a!) x, *, int? idx=None, str mode=\"nearest\") "
|
|
"-> ()",
|
|
&mdef_schema_matrix_alias_and_kwonly);
|
|
m.def("variadic_signature(Tensor x, ...) -> ...",
|
|
[](const torch::FunctionArgs& args) -> torch::IValue {
|
|
int64_t sum = 0;
|
|
for (size_t i = 1; i < args.size(); ++i) {
|
|
sum += args.get<int64_t>(i);
|
|
}
|
|
return torch::IValue(sum);
|
|
});
|
|
}
|
|
|
|
TEST(test_torch_library, TestMDefRegistrationPathsCallResult) {
|
|
struct CallCase {
|
|
const char* qualified_name;
|
|
std::vector<torch::IValue> args;
|
|
int64_t expected;
|
|
};
|
|
|
|
const std::vector<CallCase> cases = {
|
|
{"example_library_with_mdef_cases::schema_only_add",
|
|
{torch::IValue(11), torch::IValue(31)},
|
|
42},
|
|
{"example_library_with_mdef_cases::schema_and_impl_add",
|
|
{torch::IValue(19), torch::IValue(23)},
|
|
42},
|
|
{"example_library_with_mdef_cases::name_only_add",
|
|
{torch::IValue(20), torch::IValue(22)},
|
|
42},
|
|
// Dotted overload-style names should preserve suffix before '('.
|
|
{"example_library_with_mdef_cases::overload.name",
|
|
{torch::IValue(40), torch::IValue(2)},
|
|
42},
|
|
};
|
|
|
|
for (const auto& test_case : cases) {
|
|
SCOPED_TRACE(test_case.qualified_name);
|
|
|
|
auto* op = torch::OperatorRegistry::instance().find_operator(
|
|
test_case.qualified_name);
|
|
ASSERT_NE(op, nullptr);
|
|
|
|
auto impl_it = op->implementations.find(torch::DispatchKey::CPU);
|
|
ASSERT_NE(impl_it, op->implementations.end());
|
|
|
|
torch::FunctionArgs function_args;
|
|
for (const auto& arg : test_case.args) {
|
|
function_args.add_arg(arg);
|
|
}
|
|
|
|
auto result = impl_it->second.call_with_args(function_args);
|
|
ASSERT_TRUE(result.get_value().is_int());
|
|
EXPECT_EQ(result.get_value().to_int(), test_case.expected);
|
|
}
|
|
}
|
|
|
|
TEST(test_torch_library, TestMDefSchemaOnlyWithoutImplHasNoImplementation) {
|
|
auto qualified_name = "example_library_with_mdef_cases::schema_only_no_impl";
|
|
auto* op = torch::OperatorRegistry::instance().find_operator(qualified_name);
|
|
ASSERT_NE(op, nullptr);
|
|
EXPECT_TRUE(op->implementations.empty());
|
|
}
|
|
|
|
TEST(test_torch_library, TestMDefRegistersMultipleDispatchImplementations) {
|
|
auto qualified_name = "example_library_with_mdef_cases::dispatch_probe";
|
|
auto* op = torch::OperatorRegistry::instance().find_operator(qualified_name);
|
|
ASSERT_NE(op, nullptr);
|
|
|
|
auto cpu_it = op->implementations.find(torch::DispatchKey::CPU);
|
|
auto cuda_it = op->implementations.find(torch::DispatchKey::CUDA);
|
|
ASSERT_NE(cpu_it, op->implementations.end());
|
|
ASSERT_NE(cuda_it, op->implementations.end());
|
|
|
|
torch::FunctionArgs function_args;
|
|
function_args.add_arg(torch::IValue(41));
|
|
auto cpu_result = cpu_it->second.call_with_args(function_args);
|
|
auto cuda_result = cuda_it->second.call_with_args(function_args);
|
|
ASSERT_TRUE(cpu_result.get_value().is_int());
|
|
ASSERT_TRUE(cuda_result.get_value().is_int());
|
|
EXPECT_EQ(cpu_result.get_value().to_int(), 42);
|
|
EXPECT_EQ(cuda_result.get_value().to_int(), 43);
|
|
}
|
|
|
|
TEST(test_torch_library, TestMDefInImplBlockIsNoop) {
|
|
{
|
|
auto qualified_name =
|
|
"example_library_mdef_impl_block::impl_block_schema_only";
|
|
auto* op =
|
|
torch::OperatorRegistry::instance().find_operator(qualified_name);
|
|
EXPECT_EQ(op, nullptr);
|
|
}
|
|
|
|
{
|
|
auto qualified_name =
|
|
"example_library_mdef_impl_block::impl_block_schema_and_fn";
|
|
auto* op =
|
|
torch::OperatorRegistry::instance().find_operator(qualified_name);
|
|
EXPECT_EQ(op, nullptr);
|
|
}
|
|
}
|
|
|
|
TEST(test_torch_library, TestMDefSchemaMatrixBasicTypesCallResult) {
|
|
auto qualified_name = "example_library_mdef_schema_matrix::basic_types";
|
|
auto* op = torch::OperatorRegistry::instance().find_operator(qualified_name);
|
|
ASSERT_NE(op, nullptr);
|
|
auto impl_it = op->implementations.find(torch::DispatchKey::CPU);
|
|
ASSERT_NE(impl_it, op->implementations.end());
|
|
|
|
torch::FunctionArgs function_args;
|
|
function_args.add_arg(torch::IValue(at::ones({2, 2}, at::kFloat)));
|
|
function_args.add_arg(torch::IValue(1));
|
|
function_args.add_arg(torch::IValue(2.5));
|
|
function_args.add_arg(torch::IValue(true));
|
|
function_args.add_arg(torch::IValue(std::string("ab")));
|
|
function_args.add_arg(torch::IValue(std::string("cpu")));
|
|
function_args.add_arg(torch::IValue(3.5));
|
|
function_args.add_arg(torch::IValue(int64_t(4)));
|
|
auto result = impl_it->second.call_with_args(function_args);
|
|
ASSERT_TRUE(result.get_value().is_tensor());
|
|
auto out = result.get_value().to_tensor();
|
|
EXPECT_EQ(out.sizes(), at::IntArrayRef({2, 2}));
|
|
EXPECT_FLOAT_EQ(out[0][0].item<float>(), 18.0f);
|
|
}
|
|
|
|
TEST(test_torch_library, TestMDefSchemaMatrixNumberAliasesCallResult) {
|
|
auto qualified_name = "example_library_mdef_schema_matrix::number_aliases";
|
|
auto* op = torch::OperatorRegistry::instance().find_operator(qualified_name);
|
|
ASSERT_NE(op, nullptr);
|
|
auto impl_it = op->implementations.find(torch::DispatchKey::CPU);
|
|
ASSERT_NE(impl_it, op->implementations.end());
|
|
|
|
torch::FunctionArgs function_args;
|
|
function_args.add_arg(torch::IValue(19.5));
|
|
function_args.add_arg(torch::IValue(22.5));
|
|
auto result = impl_it->second.call_with_args(function_args);
|
|
ASSERT_TRUE(result.get_value().is_double());
|
|
EXPECT_DOUBLE_EQ(result.get_value().to_double(), 42.0);
|
|
}
|
|
|
|
TEST(test_torch_library, TestMDefSchemaMatrixOptionalAndTupleCallResult) {
|
|
{
|
|
auto qualified_name = "example_library_mdef_schema_matrix::optional_types";
|
|
auto* op =
|
|
torch::OperatorRegistry::instance().find_operator(qualified_name);
|
|
ASSERT_NE(op, nullptr);
|
|
auto impl_it = op->implementations.find(torch::DispatchKey::CPU);
|
|
ASSERT_NE(impl_it, op->implementations.end());
|
|
|
|
torch::FunctionArgs args_with_values;
|
|
args_with_values.add_arg(torch::IValue(int64_t(5)));
|
|
args_with_values.add_arg(torch::IValue(2.0));
|
|
args_with_values.add_arg(torch::IValue(true));
|
|
args_with_values.add_arg(torch::IValue(std::string("abc")));
|
|
args_with_values.add_arg(torch::IValue(at::ones({1}, at::kFloat)));
|
|
auto result = impl_it->second.call_with_args(args_with_values);
|
|
ASSERT_TRUE(result.get_value().is_tuple());
|
|
const auto tuple_val = result.get_value().to_tuple();
|
|
ASSERT_EQ(tuple_val.size(), 2UL);
|
|
EXPECT_FLOAT_EQ(tuple_val[0].to_tensor()[0].item<float>(), 12.0f);
|
|
EXPECT_EQ(tuple_val[1].to_int(), 11);
|
|
}
|
|
|
|
{
|
|
auto qualified_name = "example_library_mdef_schema_matrix::tuple_optional";
|
|
auto* op =
|
|
torch::OperatorRegistry::instance().find_operator(qualified_name);
|
|
ASSERT_NE(op, nullptr);
|
|
auto impl_it = op->implementations.find(torch::DispatchKey::CPU);
|
|
ASSERT_NE(impl_it, op->implementations.end());
|
|
|
|
torch::FunctionArgs args_with_payload;
|
|
args_with_payload.add_arg(torch::IValue(std::make_tuple(
|
|
at::ones({1}, at::kFloat), int64_t(2), 3.0, true, std::string("ab"))));
|
|
auto result = impl_it->second.call_with_args(args_with_payload);
|
|
ASSERT_TRUE(result.get_value().is_tuple());
|
|
const auto tuple_val = result.get_value().to_tuple();
|
|
ASSERT_EQ(tuple_val.size(), 2UL);
|
|
EXPECT_FLOAT_EQ(tuple_val[0].to_tensor()[0].item<float>(), 1.0f);
|
|
EXPECT_FLOAT_EQ(tuple_val[1].to_tensor()[0].item<float>(), 9.0f);
|
|
}
|
|
}
|
|
|
|
TEST(test_torch_library,
|
|
TestMDefSchemaMatrixDefaultsAliasAndVariadicCallResult) {
|
|
{
|
|
auto qualified_name = "example_library_mdef_schema_matrix::defaults_mix";
|
|
auto* op =
|
|
torch::OperatorRegistry::instance().find_operator(qualified_name);
|
|
ASSERT_NE(op, nullptr);
|
|
auto impl_it = op->implementations.find(torch::DispatchKey::CPU);
|
|
ASSERT_NE(impl_it, op->implementations.end());
|
|
|
|
torch::FunctionArgs function_args;
|
|
function_args.add_arg(torch::IValue(3));
|
|
function_args.add_arg(torch::IValue(-2.5));
|
|
function_args.add_arg(torch::IValue(true));
|
|
function_args.add_arg(torch::IValue(std::string("abc")));
|
|
function_args.add_arg(torch::IValue(std::string("cpu")));
|
|
auto result = impl_it->second.call_with_args(function_args);
|
|
ASSERT_TRUE(result.get_value().is_string());
|
|
EXPECT_EQ(result.get_value().to_string(), "3|-25|1|abc|cpu");
|
|
}
|
|
|
|
{
|
|
auto qualified_name =
|
|
"example_library_mdef_schema_matrix::variadic_signature";
|
|
auto* op =
|
|
torch::OperatorRegistry::instance().find_operator(qualified_name);
|
|
ASSERT_NE(op, nullptr);
|
|
auto impl_it = op->implementations.find(torch::DispatchKey::CPU);
|
|
ASSERT_NE(impl_it, op->implementations.end());
|
|
|
|
torch::FunctionArgs function_args;
|
|
function_args.add_arg(torch::IValue(at::zeros({1}, at::kFloat)));
|
|
function_args.add_arg(torch::IValue(10));
|
|
function_args.add_arg(torch::IValue(20));
|
|
function_args.add_arg(torch::IValue(12));
|
|
auto result = impl_it->second.call_with_args(function_args);
|
|
ASSERT_TRUE(result.get_value().is_int());
|
|
EXPECT_EQ(result.get_value().to_int(), 42);
|
|
}
|
|
}
|
|
|
|
TEST(test_torch_library, TestMDefKeywordOnlyCallBehavior) {
|
|
auto qualified_name = "example_library_mdef_schema_matrix::alias_and_kwonly";
|
|
auto* op = torch::OperatorRegistry::instance().find_operator(qualified_name);
|
|
ASSERT_NE(op, nullptr);
|
|
auto impl_it = op->implementations.find(torch::DispatchKey::CPU);
|
|
ASSERT_NE(impl_it, op->implementations.end());
|
|
|
|
{
|
|
torch::FunctionArgs args_with_optional_none;
|
|
args_with_optional_none.add_arg(torch::IValue(at::ones({4}, at::kFloat)));
|
|
args_with_optional_none.add_arg(torch::arg("idx") = torch::IValue());
|
|
args_with_optional_none.add_arg(torch::arg("mode") = "nearest");
|
|
auto result = impl_it->second.call_with_args(args_with_optional_none);
|
|
EXPECT_TRUE(result.get_value().is_none());
|
|
}
|
|
|
|
{
|
|
torch::FunctionArgs args_with_defaults;
|
|
args_with_defaults.add_arg(torch::IValue(at::ones({4}, at::kFloat)));
|
|
auto result = impl_it->second.call_with_args(args_with_defaults);
|
|
EXPECT_TRUE(result.get_value().is_none());
|
|
}
|
|
|
|
{
|
|
torch::FunctionArgs positional_kwonly_args;
|
|
positional_kwonly_args.add_arg(torch::IValue(at::ones({4}, at::kFloat)));
|
|
positional_kwonly_args.add_arg(torch::IValue(int64_t(2)));
|
|
EXPECT_ANY_THROW(
|
|
(void)impl_it->second.call_with_args(positional_kwonly_args));
|
|
}
|
|
}
|
|
|
|
TEST(test_torch_library, TestFunctionArgsRejectsDuplicateKeywordArgument) {
|
|
torch::FunctionArgs function_args;
|
|
function_args.add_arg(torch::arg("idx") = int64_t(1));
|
|
test::utils::ExpectThrowContains<std::runtime_error>(
|
|
[&]() { function_args.add_arg(torch::arg("idx") = int64_t(2)); },
|
|
"Duplicate keyword argument `idx`");
|
|
}
|
|
|
|
TEST(test_torch_library, TestFunctionArgsAdditionalBranches) {
|
|
torch::FunctionArgs args;
|
|
EXPECT_THROW(args.add_arg(torch::arg("missing")), std::runtime_error);
|
|
|
|
args.add_arg("cpu");
|
|
args.add_arg(torch::IValue(int64_t(7)));
|
|
args.add_arg(int64_t(3));
|
|
args.add_arg(torch::arg("mode") = "nearest");
|
|
args.add_arg(torch::arg("idx") = int64_t(2));
|
|
|
|
ASSERT_EQ(args.size(), 3UL);
|
|
ASSERT_EQ(args.named_size(), 2UL);
|
|
EXPECT_TRUE(args.has_named_args());
|
|
EXPECT_FALSE(args.empty());
|
|
EXPECT_EQ(args.get<std::string>(0), "cpu");
|
|
|
|
const int64_t& ref_value = args.get<const int64_t&>(1);
|
|
const int64_t const_value = args.get<const int64_t>(2);
|
|
EXPECT_EQ(ref_value, 7);
|
|
EXPECT_EQ(const_value, 3);
|
|
|
|
const auto args_text = args.to_string();
|
|
EXPECT_NE(args_text.find("kwargs={"), std::string::npos);
|
|
EXPECT_NE(args_text.find("mode"), std::string::npos);
|
|
EXPECT_NE(args_text.find("idx"), std::string::npos);
|
|
|
|
auto from_vector = torch::FunctionArgs::from_vector(
|
|
std::vector<torch::IValue>{torch::IValue(int64_t(11))});
|
|
EXPECT_EQ(from_vector.get<int64_t>(0), 11);
|
|
}
|
|
|
|
TEST(test_torch_library, TestFunctionArgsErrorBranches) {
|
|
torch::FunctionArgs args;
|
|
args.add_arg(torch::IValue(int64_t(1)));
|
|
|
|
EXPECT_THROW((void)args.get<std::string>(0), std::runtime_error);
|
|
EXPECT_THROW((void)args.get_value(1), std::out_of_range);
|
|
test::utils::ExpectThrowContains<std::runtime_error>(
|
|
[&]() { (void)args.to_tuple<int64_t, int64_t>(); },
|
|
"Argument count mismatch");
|
|
}
|
|
|
|
TEST(test_torch_library, TestFunctionResultErrorBranches) {
|
|
torch::FunctionResult empty_result;
|
|
EXPECT_FALSE(empty_result.has_value());
|
|
EXPECT_THROW((void)empty_result.get<int64_t>(), std::runtime_error);
|
|
|
|
torch::FunctionResult string_result(torch::IValue(std::string("abc")));
|
|
EXPECT_THROW((void)string_result.get<int64_t>(), std::runtime_error);
|
|
}
|
|
|
|
TEST(test_torch_library, TestCppFunctionWrapperAndUninitializedErrors) {
|
|
torch::CppFunction uninitialized;
|
|
EXPECT_FALSE(uninitialized.valid());
|
|
EXPECT_THROW((void)uninitialized.call(), std::runtime_error);
|
|
EXPECT_THROW((void)uninitialized.call(1), std::runtime_error);
|
|
EXPECT_THROW((void)uninitialized.call_with_args(torch::FunctionArgs()),
|
|
std::runtime_error);
|
|
|
|
std::function<torch::IValue(const torch::FunctionArgs&)> ctor_thrower =
|
|
[](const torch::FunctionArgs& args) -> torch::IValue {
|
|
(void)args;
|
|
throw std::runtime_error("boom_ctor");
|
|
};
|
|
torch::CppFunction ctor_wrapped(ctor_thrower);
|
|
test::utils::ExpectThrowContains<std::runtime_error>(
|
|
[&]() { (void)ctor_wrapped.call_with_args(torch::FunctionArgs()); },
|
|
"Constructor failed: boom_ctor");
|
|
|
|
auto throw_in_free_function = +[](int x) -> int {
|
|
(void)x;
|
|
throw std::runtime_error("boom_fn");
|
|
};
|
|
torch::CppFunction free_fn_wrapped(throw_in_free_function);
|
|
torch::FunctionArgs single_arg;
|
|
single_arg.add_arg(torch::IValue(int64_t(1)));
|
|
test::utils::ExpectThrowContains<std::runtime_error>(
|
|
[&]() { (void)free_fn_wrapped.call_with_args(single_arg); },
|
|
"Function call failed: boom_fn");
|
|
|
|
auto throwing_callable =
|
|
[](const torch::FunctionArgs& args) -> torch::IValue {
|
|
(void)args;
|
|
throw std::runtime_error("boom_lambda");
|
|
};
|
|
torch::CppFunction lambda_wrapped(throwing_callable);
|
|
test::utils::ExpectThrowContains<std::runtime_error>(
|
|
[&]() { (void)lambda_wrapped.call_with_args(torch::FunctionArgs()); },
|
|
"Lambda execution failed: boom_lambda");
|
|
}
|
|
|
|
TEST(test_torch_library, TestCppFunctionSchemaNormalizationErrorBranches) {
|
|
{
|
|
torch::CppFunction fn([](const torch::FunctionArgs& args) -> torch::IValue {
|
|
return torch::IValue(args.get<int64_t>(0) + args.get<int64_t>(1));
|
|
});
|
|
fn.bind_schema(torch::jit::parseSchema("normalize(int a, int b=1) -> int"));
|
|
|
|
torch::FunctionArgs too_many_positional;
|
|
too_many_positional.add_arg(torch::IValue(int64_t(1)));
|
|
too_many_positional.add_arg(torch::IValue(int64_t(2)));
|
|
too_many_positional.add_arg(torch::IValue(int64_t(3)));
|
|
test::utils::ExpectThrowContains<std::runtime_error>(
|
|
[&]() { (void)fn.call_with_args(too_many_positional); },
|
|
"Too many positional arguments");
|
|
}
|
|
|
|
{
|
|
torch::CppFunction fn([](const torch::FunctionArgs& args) -> torch::IValue {
|
|
return torch::IValue(args.get<int64_t>(0) + args.get<int64_t>(1));
|
|
});
|
|
fn.bind_schema(
|
|
torch::jit::parseSchema("normalize_kw(int a, *, int b=1) -> int"));
|
|
|
|
torch::FunctionArgs positional_kwonly;
|
|
positional_kwonly.add_arg(torch::IValue(int64_t(1)));
|
|
positional_kwonly.add_arg(torch::IValue(int64_t(2)));
|
|
test::utils::ExpectThrowContains<std::runtime_error>(
|
|
[&]() { (void)fn.call_with_args(positional_kwonly); }, "keyword-only");
|
|
|
|
torch::FunctionArgs unknown_kw;
|
|
unknown_kw.add_arg(torch::IValue(int64_t(1)));
|
|
unknown_kw.add_arg(torch::arg("unknown") = int64_t(2));
|
|
test::utils::ExpectThrowContains<std::runtime_error>(
|
|
[&]() { (void)fn.call_with_args(unknown_kw); },
|
|
"Unknown keyword argument `unknown`");
|
|
}
|
|
|
|
{
|
|
torch::CppFunction fn([](const torch::FunctionArgs& args) -> torch::IValue {
|
|
return torch::IValue(args.get<int64_t>(0) + args.get<int64_t>(1));
|
|
});
|
|
fn.bind_schema(
|
|
torch::jit::parseSchema("normalize_dup(int a, int b) -> int"));
|
|
|
|
torch::FunctionArgs duplicated;
|
|
duplicated.add_arg(torch::IValue(int64_t(1)));
|
|
duplicated.add_arg(torch::IValue(int64_t(2)));
|
|
duplicated.add_arg(torch::arg("b") = int64_t(3));
|
|
test::utils::ExpectThrowContains<std::runtime_error>(
|
|
[&]() { (void)fn.call_with_args(duplicated); }, "already provided");
|
|
}
|
|
|
|
{
|
|
torch::CppFunction fn([](const torch::FunctionArgs& args) -> torch::IValue {
|
|
return torch::IValue(args.get<int64_t>(0) + args.get<int64_t>(1));
|
|
});
|
|
fn.bind_schema(
|
|
torch::jit::parseSchema("normalize_missing(int a, int b) -> int"));
|
|
|
|
torch::FunctionArgs missing_required;
|
|
missing_required.add_arg(torch::IValue(int64_t(1)));
|
|
test::utils::ExpectThrowContains<std::runtime_error>(
|
|
[&]() { (void)fn.call_with_args(missing_required); },
|
|
"Missing required argument `b`");
|
|
}
|
|
}
|
|
|
|
TEST(test_torch_library, TestCppFunctionSchemaNormalizationVarargPassthrough) {
|
|
torch::CppFunction fn([](const torch::FunctionArgs& args) -> torch::IValue {
|
|
int64_t sum = 0;
|
|
for (size_t i = 0; i < args.size(); ++i) {
|
|
sum += args.get<int64_t>(i);
|
|
}
|
|
return torch::IValue(sum);
|
|
});
|
|
fn.bind_schema(
|
|
torch::jit::parseSchema("normalize_vararg(int a, ...) -> int"));
|
|
|
|
torch::FunctionArgs inputs;
|
|
inputs.add_arg(torch::IValue(int64_t(1)));
|
|
inputs.add_arg(torch::IValue(int64_t(2)));
|
|
inputs.add_arg(torch::IValue(int64_t(3)));
|
|
auto result = fn.call_with_args(inputs);
|
|
ASSERT_TRUE(result.get_value().is_int());
|
|
EXPECT_EQ(result.get_value().to_int(), 6);
|
|
}
|
|
|
|
TEST(test_torch_library, TestCppFunctionArityMismatchFromFunctionTraits) {
|
|
torch::CppFunction add_two_ints(&schema_only_add);
|
|
torch::FunctionArgs missing_one;
|
|
missing_one.add_arg(torch::IValue(int64_t(1)));
|
|
test::utils::ExpectThrowContains<std::runtime_error>(
|
|
[&]() { (void)add_two_ints.call_with_args(missing_one); },
|
|
"Function expects 2 arguments, got 1");
|
|
}
|
|
|
|
TEST(test_torch_library, TestClassMethodArityMismatchFromFunctionTraits) {
|
|
auto qualified_name = "example_library::TestClass";
|
|
const auto& class_registry = torch::ClassRegistry::instance();
|
|
|
|
torch::FunctionArgs constructor_args;
|
|
constructor_args.add_arg(torch::IValue(10));
|
|
constructor_args.add_arg(torch::IValue("example"));
|
|
auto instance = class_registry.call_constructor_with_args(qualified_name,
|
|
constructor_args);
|
|
|
|
test::utils::ExpectThrowContains<std::runtime_error>(
|
|
[&]() {
|
|
(void)class_registry.call_method_with_args(qualified_name,
|
|
"setValue",
|
|
instance.get_value(),
|
|
torch::FunctionArgs());
|
|
},
|
|
"Method expects 1 arguments");
|
|
}
|
|
|
|
TEST(test_torch_library,
|
|
TestClassMethodKwonlyArgsForwardedThroughInstanceOverload) {
|
|
auto qualified_name = "example_library::TestClass";
|
|
auto method_name = "kwonlyForwarding";
|
|
auto& class_registry = torch::ClassRegistry::instance();
|
|
|
|
class_registry.register_method(
|
|
qualified_name, method_name, MakeKwonlySchemaMethodForTestClass());
|
|
|
|
torch::FunctionArgs constructor_args;
|
|
constructor_args.add_arg(torch::IValue(10));
|
|
constructor_args.add_arg(torch::IValue("example"));
|
|
auto instance = class_registry.call_constructor_with_args(qualified_name,
|
|
constructor_args);
|
|
|
|
{
|
|
torch::FunctionArgs kwonly_args;
|
|
kwonly_args.add_arg(torch::arg("idx") = int64_t(7));
|
|
kwonly_args.add_arg(torch::arg("mode") = "linear");
|
|
|
|
auto result = class_registry.call_method_with_args(
|
|
qualified_name, method_name, instance.get_value(), kwonly_args);
|
|
ASSERT_TRUE(result.get_value().is_string());
|
|
EXPECT_EQ(result.get_value().to_string(), "example|7|linear");
|
|
}
|
|
|
|
{
|
|
torch::FunctionArgs positional_kwonly_args;
|
|
positional_kwonly_args.add_arg(torch::IValue(int64_t(7)));
|
|
|
|
test::utils::ExpectThrowContains<std::runtime_error>(
|
|
[&]() {
|
|
(void)class_registry.call_method_with_args(qualified_name,
|
|
method_name,
|
|
instance.get_value(),
|
|
positional_kwonly_args);
|
|
},
|
|
"keyword-only");
|
|
}
|
|
}
|
|
|
|
TEST(test_torch_library, TestMDefSchemaDefaultsAppliedByCallWithArgs) {
|
|
auto qualified_name = "example_library_mdef_schema_matrix::defaults_mix";
|
|
auto* op = torch::OperatorRegistry::instance().find_operator(qualified_name);
|
|
ASSERT_NE(op, nullptr);
|
|
auto impl_it = op->implementations.find(torch::DispatchKey::CPU);
|
|
ASSERT_NE(impl_it, op->implementations.end());
|
|
|
|
torch::FunctionArgs args_without_values;
|
|
auto result = impl_it->second.call_with_args(args_without_values);
|
|
ASSERT_TRUE(result.get_value().is_string());
|
|
EXPECT_EQ(result.get_value().to_string(), "3|-25|1|abc|cpu");
|
|
}
|
|
|
|
at::Tensor cast_with_scalar_type(at::Tensor input, c10::ScalarType dtype) {
|
|
return input.toType(dtype);
|
|
}
|
|
|
|
TORCH_LIBRARY(example_library_with_scalar_type_input, m) {
|
|
m.def("cast_with_scalar_type", &cast_with_scalar_type);
|
|
}
|
|
|
|
TEST(test_torch_library, TestScalarTypeInput) {
|
|
auto qualified_name =
|
|
"example_library_with_scalar_type_input::cast_with_scalar_type";
|
|
auto* op = torch::OperatorRegistry::instance().find_operator(qualified_name);
|
|
ASSERT_NE(op, nullptr);
|
|
auto impl_it = op->implementations.find(c10::DispatchKey::CPU);
|
|
ASSERT_NE(impl_it, op->implementations.end());
|
|
torch::FunctionArgs function_args;
|
|
function_args.add_arg(torch::IValue(at::ones({2, 2}, at::kFloat)));
|
|
function_args.add_arg(torch::IValue(at::kDouble));
|
|
auto result = impl_it->second.call_with_args(function_args);
|
|
ASSERT_TRUE(result.get_value().is_tensor());
|
|
auto result_tensor = result.get_value().to_tensor();
|
|
ASSERT_EQ(result_tensor.dtype(), at::kDouble);
|
|
}
|
|
|
|
TEST(test_torch_library, TestRegisterImplementationAtRuntime) {
|
|
auto qualified_name = "runtime_example::runtime_add";
|
|
auto& registry = torch::OperatorRegistry::instance();
|
|
|
|
registry.register_implementation(qualified_name,
|
|
c10::DispatchKey::CPU,
|
|
torch::CppFunction(&generic_add<int>));
|
|
|
|
auto* op = registry.find_operator(qualified_name);
|
|
ASSERT_NE(op, nullptr);
|
|
|
|
auto impl_it = op->implementations.find(c10::DispatchKey::CPU);
|
|
ASSERT_NE(impl_it, op->implementations.end());
|
|
|
|
torch::FunctionArgs function_args;
|
|
function_args.add_arg(torch::IValue(11));
|
|
function_args.add_arg(torch::IValue(31));
|
|
auto result = impl_it->second.call_with_args(function_args);
|
|
|
|
ASSERT_TRUE(result.get_value().is_int());
|
|
ASSERT_EQ(result.get_value().to_int(), 42);
|
|
}
|
|
|
|
TEST(test_torch_library, TestLibraryPrintInfoWithDispatchKey) {
|
|
torch::Library library(torch::Library::IMPL,
|
|
"runtime_library_info",
|
|
std::make_optional(c10::DispatchKey::CPU),
|
|
__FILE__,
|
|
__LINE__);
|
|
|
|
testing::internal::CaptureStdout();
|
|
library.print_info();
|
|
auto output = testing::internal::GetCapturedStdout();
|
|
|
|
ASSERT_NE(output.find("Library Info: IMPL"), std::string::npos);
|
|
ASSERT_NE(output.find("namespace=runtime_library_info"), std::string::npos);
|
|
ASSERT_NE(output.find("dispatch_key="), std::string::npos);
|
|
}
|
|
|
|
int fn_with_int_const(int const x) { return x + 1; }
|
|
|
|
TORCH_LIBRARY(example_library_with_int_const, m) {
|
|
m.def("fn_with_int_const", &fn_with_int_const);
|
|
}
|
|
|
|
TEST(test_torch_library, TestIntConst) {
|
|
auto qualified_name = "example_library_with_int_const::fn_with_int_const";
|
|
auto* op = torch::OperatorRegistry::instance().find_operator(qualified_name);
|
|
ASSERT_NE(op, nullptr);
|
|
auto impl_it = op->implementations.find(c10::DispatchKey::CPU);
|
|
ASSERT_NE(impl_it, op->implementations.end());
|
|
torch::FunctionArgs function_args;
|
|
function_args.add_arg(torch::IValue(3));
|
|
auto result = impl_it->second.call_with_args(function_args);
|
|
ASSERT_TRUE(result.get_value().is_int());
|
|
int value = result.get_value().to_int();
|
|
ASSERT_EQ(value, 4);
|
|
}
|
|
|
|
int fn_with_optional_input(torch::optional<int64_t> x) {
|
|
if (x.has_value()) {
|
|
return x.value() + 1;
|
|
} else {
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
TORCH_LIBRARY(example_library_with_optional_input, m) {
|
|
m.def("fn_with_optional_input", &fn_with_optional_input);
|
|
}
|
|
|
|
TEST(test_torch_library, TestOptionalInput) {
|
|
auto qualified_name =
|
|
"example_library_with_optional_input::fn_with_optional_input";
|
|
auto* op = torch::OperatorRegistry::instance().find_operator(qualified_name);
|
|
ASSERT_NE(op, nullptr);
|
|
auto impl_it = op->implementations.find(c10::DispatchKey::CPU);
|
|
ASSERT_NE(impl_it, op->implementations.end());
|
|
|
|
// Test with value
|
|
torch::FunctionArgs function_args_with_value;
|
|
function_args_with_value.add_arg(torch::IValue(int64_t(5)));
|
|
auto result_with_value =
|
|
impl_it->second.call_with_args(function_args_with_value);
|
|
ASSERT_TRUE(result_with_value.get_value().is_int());
|
|
int value_with_value = result_with_value.get_value().to_int();
|
|
ASSERT_EQ(value_with_value, 6);
|
|
|
|
// Test without value (nullopt)
|
|
torch::FunctionArgs function_args_without_value;
|
|
function_args_without_value.add_arg(torch::IValue());
|
|
auto result_without_value =
|
|
impl_it->second.call_with_args(function_args_without_value);
|
|
ASSERT_TRUE(result_without_value.get_value().is_int());
|
|
int value_without_value = result_without_value.get_value().to_int();
|
|
ASSERT_EQ(value_without_value, -1);
|
|
}
|
|
|
|
int fn_with_arrayref_input(c10::ArrayRef<int64_t> x) {
|
|
int sum = 0;
|
|
for (const auto& val : x) {
|
|
sum += val;
|
|
}
|
|
return sum;
|
|
}
|
|
|
|
TORCH_LIBRARY(example_library_with_arrayref_input, m) {
|
|
m.def("fn_with_arrayref_input", &fn_with_arrayref_input);
|
|
}
|
|
|
|
TEST(test_torch_library, TestArrayRefInput) {
|
|
auto qualified_name =
|
|
"example_library_with_arrayref_input::fn_with_arrayref_input";
|
|
auto* op = torch::OperatorRegistry::instance().find_operator(qualified_name);
|
|
ASSERT_NE(op, nullptr);
|
|
auto impl_it = op->implementations.find(c10::DispatchKey::CPU);
|
|
ASSERT_NE(impl_it, op->implementations.end());
|
|
|
|
torch::FunctionArgs function_args;
|
|
function_args.add_arg(torch::IValue(std::vector<int64_t>({1, 2, 3, 4})));
|
|
auto result = impl_it->second.call_with_args(function_args);
|
|
ASSERT_TRUE(result.get_value().is_int());
|
|
int value = result.get_value().to_int();
|
|
ASSERT_EQ(value, 10);
|
|
}
|
|
|
|
int fn_with_mix_optional_arrayref_input(
|
|
c10::optional<c10::ArrayRef<int64_t>> x) {
|
|
if (x.has_value()) {
|
|
int sum = 0;
|
|
for (const auto& val : x.value()) {
|
|
sum += val;
|
|
}
|
|
return sum;
|
|
} else {
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
TORCH_LIBRARY(example_library_with_mix_optional_arrayref_input, m) {
|
|
m.def("fn_with_mix_optional_arrayref_input",
|
|
&fn_with_mix_optional_arrayref_input);
|
|
}
|
|
|
|
TEST(test_torch_library, TestMixOptionalArrayRefInput) {
|
|
auto qualified_name =
|
|
"example_library_with_mix_optional_arrayref_input::"
|
|
"fn_with_mix_optional_arrayref_input";
|
|
auto* op = torch::OperatorRegistry::instance().find_operator(qualified_name);
|
|
ASSERT_NE(op, nullptr);
|
|
auto impl_it = op->implementations.find(c10::DispatchKey::CPU);
|
|
ASSERT_NE(impl_it, op->implementations.end());
|
|
|
|
// Test with value
|
|
torch::FunctionArgs function_args_with_value;
|
|
function_args_with_value.add_arg(
|
|
torch::IValue(std::vector<int64_t>({1, 2, 3, 4})));
|
|
auto result_with_value =
|
|
impl_it->second.call_with_args(function_args_with_value);
|
|
ASSERT_TRUE(result_with_value.get_value().is_int());
|
|
int value_with_value = result_with_value.get_value().to_int();
|
|
ASSERT_EQ(value_with_value, 10);
|
|
|
|
// Test without value (nullopt)
|
|
torch::FunctionArgs function_args_without_value;
|
|
function_args_without_value.add_arg(torch::IValue());
|
|
auto result_without_value =
|
|
impl_it->second.call_with_args(function_args_without_value);
|
|
ASSERT_TRUE(result_without_value.get_value().is_int());
|
|
int value_without_value = result_without_value.get_value().to_int();
|
|
ASSERT_EQ(value_without_value, -1);
|
|
}
|
|
|
|
void fn_with_optional_tensor_const_ref_input(
|
|
torch::optional<at::Tensor> const& x) {}
|
|
|
|
TORCH_LIBRARY(example_library_with_optional_tensor_const_ref_input, m) {
|
|
m.def("fn_with_optional_tensor_const_ref_input",
|
|
&fn_with_optional_tensor_const_ref_input);
|
|
}
|
|
|
|
TEST(test_torch_library, TestOptionalTensorConstRefInput) {
|
|
auto qualified_name =
|
|
"example_library_with_optional_tensor_const_ref_input::"
|
|
"fn_with_optional_tensor_const_ref_input";
|
|
auto* op = torch::OperatorRegistry::instance().find_operator(qualified_name);
|
|
ASSERT_NE(op, nullptr);
|
|
auto impl_it = op->implementations.find(c10::DispatchKey::CPU);
|
|
ASSERT_NE(impl_it, op->implementations.end());
|
|
|
|
// Test with value
|
|
torch::FunctionArgs function_args_with_value;
|
|
function_args_with_value.add_arg(torch::IValue(at::ones({2, 2}, at::kFloat)));
|
|
impl_it->second.call_with_args(function_args_with_value);
|
|
|
|
// Test without value (nullopt)
|
|
torch::FunctionArgs function_args_without_value;
|
|
function_args_without_value.add_arg(torch::IValue());
|
|
impl_it->second.call_with_args(function_args_without_value);
|
|
}
|
|
|
|
// Function that returns a list of two tensors (instead of tuple)
|
|
std::vector<at::Tensor> return_tensor_list(const at::Tensor& input, int dim) {
|
|
// Simply create two tensors of different sizes as demonstration
|
|
auto first_part = at::ones({2}, input.options());
|
|
auto second_part = at::ones({2}, input.options());
|
|
|
|
return {first_part, second_part};
|
|
}
|
|
|
|
// Function that actually returns std::tuple<Tensor, Tensor>
|
|
std::tuple<at::Tensor, at::Tensor> return_tensor_tuple(const at::Tensor& input,
|
|
int dim) {
|
|
// Create two tensors and return as tuple
|
|
auto first_part = at::ones({2}, input.options());
|
|
auto second_part =
|
|
at::ones({3}, input.options()); // Different size to verify
|
|
|
|
return std::make_tuple(first_part, second_part);
|
|
}
|
|
|
|
// Function that actually returns std::tuple<Tensor, Tensor>
|
|
std::tuple<at::Tensor, at::Tensor, at::Tensor> return_tensor_tuple_3(
|
|
const at::Tensor& input, int dim) {
|
|
// Create two tensors and return as tuple
|
|
auto first_part = at::ones({2}, input.options());
|
|
auto second_part =
|
|
at::ones({3}, input.options()); // Different size to verify
|
|
auto third_part = at::ones({4}, input.options());
|
|
|
|
return std::make_tuple(first_part, second_part, third_part);
|
|
}
|
|
|
|
TORCH_LIBRARY(example_library_with_tuple_return, m) {
|
|
m.def("split_tensor_list", &return_tensor_list);
|
|
m.def("split_tensor_tuple", &return_tensor_tuple);
|
|
m.def("split_tensor_tuple_3", &return_tensor_tuple_3);
|
|
}
|
|
|
|
TEST(test_torch_library, TestTupleReturn) {
|
|
// Test vector<Tensor> return (list)
|
|
auto qualified_name_list =
|
|
"example_library_with_tuple_return::split_tensor_list";
|
|
auto* op_list =
|
|
torch::OperatorRegistry::instance().find_operator(qualified_name_list);
|
|
ASSERT_NE(op_list, nullptr);
|
|
auto impl_it_list = op_list->implementations.find(c10::DispatchKey::CPU);
|
|
ASSERT_NE(impl_it_list, op_list->implementations.end());
|
|
|
|
// Create a test tensor [0, 1, 2, 3] with shape [4]
|
|
std::vector<float> data = {0.0f, 1.0f, 2.0f, 3.0f};
|
|
auto input_tensor = at::from_blob(data.data(), {4}, at::kFloat).clone();
|
|
|
|
torch::FunctionArgs function_args_list;
|
|
function_args_list.add_arg(torch::IValue(input_tensor));
|
|
function_args_list.add_arg(torch::IValue(0)); // split along dimension 0
|
|
|
|
auto result_list = impl_it_list->second.call_with_args(function_args_list);
|
|
|
|
// Verify the result is a GenericList (vector of tensors)
|
|
ASSERT_TRUE(result_list.get_value().is_list());
|
|
|
|
auto list_val = result_list.get_value().to_list();
|
|
ASSERT_EQ(list_val.size(), 2);
|
|
|
|
// Check first tensor should have size [2]
|
|
auto first_tensor_list = list_val[0].to_tensor();
|
|
ASSERT_EQ(first_tensor_list.size(0), 2);
|
|
|
|
// Check second tensor should have size [2]
|
|
auto second_tensor_list = list_val[1].to_tensor();
|
|
ASSERT_EQ(second_tensor_list.size(0), 2);
|
|
|
|
// Test std::tuple<Tensor, Tensor> return (tuple)
|
|
auto qualified_name_tuple =
|
|
"example_library_with_tuple_return::split_tensor_tuple";
|
|
auto* op_tuple =
|
|
torch::OperatorRegistry::instance().find_operator(qualified_name_tuple);
|
|
ASSERT_NE(op_tuple, nullptr);
|
|
auto impl_it_tuple = op_tuple->implementations.find(c10::DispatchKey::CPU);
|
|
ASSERT_NE(impl_it_tuple, op_tuple->implementations.end());
|
|
|
|
torch::FunctionArgs function_args_tuple;
|
|
function_args_tuple.add_arg(torch::IValue(input_tensor));
|
|
function_args_tuple.add_arg(torch::IValue(0)); // split along dimension 0
|
|
|
|
auto result_tuple = impl_it_tuple->second.call_with_args(function_args_tuple);
|
|
|
|
// Verify the result is a tuple
|
|
ASSERT_TRUE(result_tuple.get_value().is_tuple());
|
|
|
|
auto tuple_val = result_tuple.get_value().to_tuple();
|
|
ASSERT_EQ(tuple_val.size(), 2);
|
|
|
|
// Check first tensor should have size [2]
|
|
auto first_tensor_tuple = tuple_val[0].to_tensor();
|
|
ASSERT_EQ(first_tensor_tuple.size(0), 2);
|
|
|
|
// Check second tensor should have size [3] (different from first)
|
|
auto second_tensor_tuple = tuple_val[1].to_tensor();
|
|
ASSERT_EQ(second_tensor_tuple.size(0), 3);
|
|
|
|
// Test std::tuple<Tensor, Tensor, Tensor> return (tuple)
|
|
auto qualified_name_tuple_3 =
|
|
"example_library_with_tuple_return::split_tensor_tuple_3";
|
|
auto* op_tuple_3 =
|
|
torch::OperatorRegistry::instance().find_operator(qualified_name_tuple_3);
|
|
ASSERT_NE(op_tuple_3, nullptr);
|
|
auto impl_it_tuple_3 =
|
|
op_tuple_3->implementations.find(c10::DispatchKey::CPU);
|
|
ASSERT_NE(impl_it_tuple_3, op_tuple_3->implementations.end());
|
|
|
|
torch::FunctionArgs function_args_tuple_3;
|
|
function_args_tuple_3.add_arg(torch::IValue(input_tensor));
|
|
function_args_tuple_3.add_arg(torch::IValue(0)); // split along dimension 0
|
|
|
|
auto result_tuple_3 =
|
|
impl_it_tuple_3->second.call_with_args(function_args_tuple_3);
|
|
|
|
// Verify the result is a tuple
|
|
ASSERT_TRUE(result_tuple_3.get_value().is_tuple());
|
|
|
|
auto tuple_val_3 = result_tuple_3.get_value().to_tuple();
|
|
ASSERT_EQ(tuple_val_3.size(), 3);
|
|
|
|
// Check first tensor should have size [2]
|
|
auto first_tensor_tuple_3 = tuple_val_3[0].to_tensor();
|
|
ASSERT_EQ(first_tensor_tuple_3.size(0), 2);
|
|
|
|
// Check second tensor should have size [3] (different from first)
|
|
auto second_tensor_tuple_3 = tuple_val_3[1].to_tensor();
|
|
ASSERT_EQ(second_tensor_tuple_3.size(0), 3);
|
|
|
|
// Check third tensor should have size [4] (different from first and second)
|
|
auto third_tensor_tuple_3 = tuple_val_3[2].to_tensor();
|
|
ASSERT_EQ(third_tensor_tuple_3.size(0), 4);
|
|
}
|
|
|
|
// Test for const reference parameters fix
|
|
void fn_with_const_ref_param(const int& x, const std::string& str) {
|
|
// Simple function to test const reference parameter handling
|
|
}
|
|
|
|
TORCH_LIBRARY(example_library_const_ref_fix, m) {
|
|
m.def("fn_with_const_ref_param", &fn_with_const_ref_param);
|
|
}
|
|
|
|
TEST(test_torch_library, TestConstRefParameterFix) {
|
|
auto qualified_name =
|
|
"example_library_const_ref_fix::fn_with_const_ref_param";
|
|
auto* op = torch::OperatorRegistry::instance().find_operator(qualified_name);
|
|
ASSERT_NE(op, nullptr);
|
|
auto impl_it = op->implementations.find(c10::DispatchKey::CPU);
|
|
ASSERT_NE(impl_it, op->implementations.end());
|
|
|
|
// Test with const reference parameters
|
|
torch::FunctionArgs function_args;
|
|
function_args.add_arg(torch::IValue(42));
|
|
function_args.add_arg(torch::IValue(std::string("test")));
|
|
|
|
// This should not throw compilation errors
|
|
auto result = impl_it->second.call_with_args(function_args);
|
|
ASSERT_TRUE(result.get_value().is_none()); // void function returns None
|
|
}
|
|
|
|
TEST(test_torch_library, TestClassRegistryHasNonExistentClass) {
|
|
auto qualified_name = "example_library::NonExistentClass";
|
|
const auto& class_registry = torch::ClassRegistry::instance();
|
|
bool has_class = class_registry.has_class(qualified_name);
|
|
ASSERT_FALSE(has_class);
|
|
}
|
|
|
|
TEST(test_torch_library, TestClassRegistryPrintAllClasses) {
|
|
const auto& class_registry = torch::ClassRegistry::instance();
|
|
class_registry.print_all_classes();
|
|
}
|
|
|
|
TEST(test_torch_library, TestOperatorRegistryHasNonExistentOperator) {
|
|
auto qualified_name = "example_library::non_existent_op";
|
|
const auto& operator_registry = torch::OperatorRegistry::instance();
|
|
bool has_operator = operator_registry.has_operator(qualified_name);
|
|
ASSERT_FALSE(has_operator);
|
|
}
|
|
|
|
TEST(test_torch_library, TestOperatorRegistryPrintAllOperators) {
|
|
const auto& operator_registry = torch::OperatorRegistry::instance();
|
|
operator_registry.print_all_operators();
|
|
}
|
|
|
|
TEST(test_torch_library, TestOperatorRegistryLateSchemaBindsExistingImpl) {
|
|
auto& operator_registry = torch::OperatorRegistry::instance();
|
|
const std::string qualified_name =
|
|
"example_library_registry_branch::late_schema_bind";
|
|
|
|
operator_registry.register_implementation(
|
|
qualified_name,
|
|
torch::DispatchKey::CPU,
|
|
torch::CppFunction([](const torch::FunctionArgs& args) -> torch::IValue {
|
|
return torch::IValue(args.get<int64_t>(0) + args.get<int64_t>(1));
|
|
}));
|
|
|
|
auto* op = operator_registry.find_operator(qualified_name);
|
|
ASSERT_NE(op, nullptr);
|
|
auto impl_it = op->implementations.find(torch::DispatchKey::CPU);
|
|
ASSERT_NE(impl_it, op->implementations.end());
|
|
|
|
torch::FunctionArgs one_arg;
|
|
one_arg.add_arg(torch::IValue(int64_t(5)));
|
|
EXPECT_ANY_THROW((void)impl_it->second.call_with_args(one_arg));
|
|
|
|
operator_registry.register_schema(qualified_name,
|
|
"late_schema_bind(int x, int y=3) -> int");
|
|
|
|
auto bound_result = impl_it->second.call_with_args(one_arg);
|
|
ASSERT_TRUE(bound_result.get_value().is_int());
|
|
EXPECT_EQ(bound_result.get_value().to_int(), 8);
|
|
}
|
|
|
|
TEST(test_torch_library, TestLibraryPrintInfo) {
|
|
torch::Library lib("example_library_test_print_info");
|
|
lib.print_info();
|
|
}
|
|
|
|
TEST(test_torch_library, TestIValueNone) {
|
|
torch::IValue ival = torch::IValue();
|
|
ASSERT_TRUE(ival.is_none());
|
|
ASSERT_EQ(ival.to_repr(), "None");
|
|
ASSERT_EQ(ival.type_string(), "None");
|
|
}
|
|
|
|
TEST(test_torch_library, TestIValueBool) {
|
|
torch::IValue ival = true;
|
|
ASSERT_TRUE(ival.is_bool());
|
|
ASSERT_EQ(ival.to_repr(), "true");
|
|
ASSERT_EQ(ival.type_string(), "Bool");
|
|
}
|
|
|
|
TEST(test_torch_library, TestIValueInt) {
|
|
torch::IValue ival = 42;
|
|
ASSERT_TRUE(ival.is_int());
|
|
ASSERT_EQ(ival.to_repr(), "42");
|
|
ASSERT_EQ(ival.type_string(), "Int");
|
|
}
|
|
|
|
TEST(test_torch_library, TestIValueDouble) {
|
|
torch::IValue ival = 3.14;
|
|
ASSERT_TRUE(ival.is_double());
|
|
ASSERT_TRUE(ival.to_repr().find("3.14") != std::string::npos);
|
|
ASSERT_EQ(ival.type_string(), "Double");
|
|
}
|
|
|
|
TEST(test_torch_library, TestIValueString) {
|
|
torch::IValue ival = std::string("hello");
|
|
ASSERT_TRUE(ival.is_string());
|
|
ASSERT_EQ(ival.to_repr(), "\"hello\"");
|
|
ASSERT_EQ(ival.type_string(), "String");
|
|
}
|
|
|
|
TEST(test_torch_library, TestIValueTensor) {
|
|
at::Tensor tensor = at::ones({2, 2}, at::kFloat);
|
|
torch::IValue ival = tensor;
|
|
ASSERT_TRUE(ival.is_tensor());
|
|
ASSERT_EQ(ival.type_string(), "Tensor");
|
|
}
|
|
|
|
TEST(test_torch_library, TestIValueList) {
|
|
std::vector<torch::IValue> vec = {1, 2, 3};
|
|
torch::IValue ival = torch::IValue(vec);
|
|
ASSERT_TRUE(ival.is_list());
|
|
ASSERT_EQ(ival.to_repr(), "[1, 2, 3]");
|
|
ASSERT_EQ(ival.type_string(), "List");
|
|
}
|
|
|
|
TEST(test_torch_library, TestIValueTuple) {
|
|
torch::IValue ival = torch::IValue(std::make_tuple(1, true, "three"));
|
|
ASSERT_TRUE(ival.is_tuple());
|
|
ASSERT_EQ(ival.to_repr(), "(1, true, \"three\")");
|
|
ASSERT_EQ(ival.type_string(), "Tuple");
|
|
}
|