blob: 9662f4f5f6a90371a914281a1c466ef9cd084cc0 [file] [log] [blame]
/* Copyright 2018 The TensorFlow 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.
==============================================================================*/
#if GOOGLE_CUDA || TENSORFLOW_USE_ROCM
#define EIGEN_USE_GPU
#endif
#include "tensorflow/c/kernels.h"
#include <stddef.h>
#include <stdint.h>
#include <string.h>
#include <memory>
#include <string>
#include <utility>
#include "absl/container/inlined_vector.h"
#include "absl/strings/str_format.h"
#include "third_party/eigen3/unsupported/Eigen/CXX11/Tensor"
#include "tensorflow/c/c_api.h"
#include "tensorflow/c/tf_datatype.h"
#include "tensorflow/c/tf_status.h"
#include "tensorflow/c/tf_tensor.h"
#include "tensorflow/core/common_runtime/device.h"
#include "tensorflow/core/common_runtime/device_factory.h"
#include "tensorflow/core/framework/allocator.h"
#include "tensorflow/core/framework/attr_value.pb.h"
#include "tensorflow/core/framework/device_base.h"
#include "tensorflow/core/framework/kernel_def.pb.h"
#include "tensorflow/core/framework/node_def.pb.h"
#include "tensorflow/core/framework/node_def_builder.h"
#include "tensorflow/core/framework/op.h"
#include "tensorflow/core/framework/op_kernel.h"
#include "tensorflow/core/framework/tensor.h"
#include "tensorflow/core/framework/tensor_types.h"
#include "tensorflow/core/framework/types.h"
#include "tensorflow/core/framework/types.pb.h"
#include "tensorflow/core/kernels/ops_testutil.h"
#include "tensorflow/core/lib/core/status_test_util.h"
#include "tensorflow/core/platform/env.h"
#include "tensorflow/core/platform/status.h"
#include "tensorflow/core/platform/test.h"
#include "tensorflow/core/platform/types.h"
struct MyCustomKernel {
bool created;
bool compute_called;
};
static bool delete_called = false;
static bool async_kernel_done = false;
static void* MyCreateFunc(TF_OpKernelConstruction* ctx) {
struct MyCustomKernel* s = new struct MyCustomKernel;
s->created = true;
s->compute_called = false;
// Exercise attribute reads.
TF_DataType type;
TF_Status* status = TF_NewStatus();
TF_OpKernelConstruction_GetAttrType(ctx, "SomeDataTypeAttr", &type, status);
EXPECT_EQ(TF_OK, TF_GetCode(status));
EXPECT_EQ(TF_FLOAT, type);
TF_DeleteStatus(status);
// Exercise kernel NodeDef name read
TF_StringView name_string_view = TF_OpKernelConstruction_GetName(ctx);
std::string node_name = "SomeNodeName";
std::string candidate_node_name =
std::string(name_string_view.data, name_string_view.len);
EXPECT_EQ(node_name, candidate_node_name);
return s;
}
static void MyComputeFunc(void* kernel, TF_OpKernelContext* ctx) {
struct MyCustomKernel* s = static_cast<struct MyCustomKernel*>(kernel);
s->compute_called = true;
if (ctx != nullptr) {
EXPECT_EQ(43, TF_StepId(ctx));
}
}
static void MyAsyncComputeFunc(void* kernel, TF_OpKernelContext* ctx,
TF_AsyncOpKernelDoneCallback* done) {
struct MyCustomKernel* s = static_cast<struct MyCustomKernel*>(kernel);
TF_RunAsyncOpKernelDoneCallback(done);
s->compute_called = true;
if (ctx != nullptr) {
EXPECT_EQ(43, TF_StepId(ctx));
}
}
static void MyDeleteFunc(void* kernel) {
struct MyCustomKernel* s = static_cast<struct MyCustomKernel*>(kernel);
EXPECT_TRUE(s->created);
EXPECT_TRUE(s->compute_called);
delete_called = true;
delete s;
}
namespace tensorflow {
Status TF_TensorToTensor(const TF_Tensor* src, Tensor* dst);
static std::unique_ptr<OpKernel> GetFakeKernel(const char* device_name,
const char* op_name,
const char* node_name,
Status* status) {
NodeDef def;
def.set_op(op_name);
def.set_name(node_name);
def.set_device(device_name);
def.add_input("input1");
def.add_input("input2");
AttrValue v;
v.set_type(DataType::DT_FLOAT);
(*def.mutable_attr())["SomeDataTypeAttr"] = v;
return CreateOpKernel(DeviceType(device_name), nullptr, nullptr, def, 1,
status);
}
static std::unique_ptr<OpKernel> GetFakeKernel2(const char* device_name,
const char* op_name,
const char* node_name,
Status* status) {
NodeDef def;
def.set_op(op_name);
def.set_name(node_name);
def.set_device(device_name);
def.add_input("input1");
def.add_input("input2");
def.add_input("input3");
def.add_input("input3");
def.add_input("input3");
AttrValue v0;
v0.set_type(DataType::DT_INT32);
v0.set_i(3);
(*def.mutable_attr())["NumInput3"] = v0;
AttrValue v1;
v1.set_type(DataType::DT_FLOAT);
(*def.mutable_attr())["SomeDataTypeAttr"] = v1;
return CreateOpKernel(DeviceType(device_name), nullptr, nullptr, def, 1,
status);
}
// Tests registration of a single C kernel and checks that calls through the
// C/C++ boundary are being made.
TEST(TestKernel, TestRegisterKernelBuilder) {
const char* node_name = "SomeNodeName";
const char* op_name = "FooOp";
const char* device_name = "FakeDeviceName1";
REGISTER_OP(op_name)
.Input("input1: double")
.Input("input2: uint8")
.Output("output1: uint8")
.Attr("SomeDataTypeAttr: type");
TF_KernelBuilder* builder = TF_NewKernelBuilder(
op_name, device_name, &MyCreateFunc, &MyComputeFunc, &MyDeleteFunc);
{
TF_Status* status = TF_NewStatus();
TF_RegisterKernelBuilder(node_name, builder, status);
EXPECT_EQ(TF_OK, TF_GetCode(status));
TF_Buffer* buf = TF_GetRegisteredKernelsForOp(op_name, status);
EXPECT_EQ(TF_OK, TF_GetCode(status));
KernelList list;
list.ParseFromArray(buf->data, buf->length);
ASSERT_EQ(1, list.kernel_size());
ASSERT_EQ(device_name, list.kernel(0).device_type());
TF_DeleteBuffer(buf);
TF_DeleteStatus(status);
}
{
Status status;
std::unique_ptr<OpKernel> kernel =
GetFakeKernel(device_name, op_name, node_name, &status);
TF_EXPECT_OK(status);
ASSERT_NE(nullptr, kernel.get());
kernel->Compute(nullptr);
}
ASSERT_TRUE(delete_called);
}
TEST(TestKernel, TF_RegisterKernelBuilderWithKernelDef) {
const char* node_name = "SomeNodeName";
const char* op_name = "FooOp1";
const char* device_name = "FakeDeviceName2";
REGISTER_OP(op_name)
.Input("input1: double")
.Input("input2: uint8")
.Output("output1: uint8")
.Attr("SomeDataTypeAttr: type");
TF_KernelBuilder* builder = TF_NewKernelBuilder(
op_name, device_name, &MyCreateFunc, &MyComputeFunc, &MyDeleteFunc);
KernelDef kernel_def;
kernel_def.set_op(op_name);
kernel_def.set_device_type(device_name);
std::string kernel_def_str = kernel_def.SerializePartialAsString();
{
TF_Status* status = TF_NewStatus();
TF_RegisterKernelBuilderWithKernelDef(kernel_def_str.data(), node_name,
builder, status);
EXPECT_EQ(TF_OK, TF_GetCode(status));
TF_Buffer* buf = TF_GetRegisteredKernelsForOp(op_name, status);
EXPECT_EQ(TF_OK, TF_GetCode(status));
KernelList list;
list.ParseFromArray(buf->data, buf->length);
ASSERT_EQ(1, list.kernel_size());
ASSERT_EQ(device_name, list.kernel(0).device_type());
TF_DeleteBuffer(buf);
TF_DeleteStatus(status);
}
{
Status status;
std::unique_ptr<OpKernel> kernel =
GetFakeKernel(device_name, op_name, node_name, &status);
TF_EXPECT_OK(status);
ASSERT_NE(nullptr, kernel.get());
kernel->Compute(nullptr);
}
ASSERT_TRUE(delete_called);
}
// Tests registration of a single C async kernel and checks that calls through
// the C/C++ boundary are being made.
TEST(TestKernel, TestRegisterAsyncKernelBuilder) {
const char* node_name = "SomeNodeName";
const char* op_name = "AsyncFooOp";
const char* device_name = "FakeDeviceName1";
REGISTER_OP(op_name)
.Input("input1: double")
.Input("input2: uint8")
.Output("output1: uint8")
.Attr("SomeDataTypeAttr: type");
TF_KernelBuilder* builder = TF_NewAsyncKernelBuilder(
op_name, device_name, &MyCreateFunc, &MyAsyncComputeFunc, &MyDeleteFunc);
{
TF_Status* status = TF_NewStatus();
TF_RegisterKernelBuilder(node_name, builder, status);
EXPECT_EQ(TF_OK, TF_GetCode(status));
TF_Buffer* buf = TF_GetRegisteredKernelsForOp(op_name, status);
EXPECT_EQ(TF_OK, TF_GetCode(status));
KernelList list;
list.ParseFromArray(buf->data, buf->length);
ASSERT_EQ(1, list.kernel_size());
ASSERT_EQ(device_name, list.kernel(0).device_type());
TF_DeleteBuffer(buf);
TF_DeleteStatus(status);
}
{
Status status;
std::unique_ptr<OpKernel> kernel =
GetFakeKernel(device_name, op_name, node_name, &status);
TF_EXPECT_OK(status);
ASSERT_NE(nullptr, kernel.get());
auto done = []() { async_kernel_done = true; };
down_cast<AsyncOpKernel*>(kernel.get())->ComputeAsync(nullptr, done);
}
ASSERT_TRUE(async_kernel_done);
ASSERT_TRUE(delete_called);
}
// REGISTER_OP for TF_OpKernelConstruction_GetAttr* test cases.
// Registers two ops, each with a single attribute called 'Attr'.
// The attribute in one op will have a type 'type', the other
// will have list(type).
#define ATTR_TEST_REGISTER_OP(name, type) \
REGISTER_OP("TestKernelAttr" #name) \
.Attr("Attr: " #type) \
.SetShapeFn(tensorflow::shape_inference::UnknownShape); \
REGISTER_OP("TestKernelAttr" #name "List") \
.Attr("Attr: list(" #type ")") \
.SetShapeFn(tensorflow::shape_inference::UnknownShape)
ATTR_TEST_REGISTER_OP(String, string);
ATTR_TEST_REGISTER_OP(Int, int);
ATTR_TEST_REGISTER_OP(Float, float);
ATTR_TEST_REGISTER_OP(Bool, bool);
ATTR_TEST_REGISTER_OP(Type, type);
ATTR_TEST_REGISTER_OP(Tensor, tensor);
#undef ATTR_TEST_REGISTER_OP
// Helper macros for the TF_OpKernelConstruction_GetAttr* tests.
#define EXPECT_TF_SIZE(attr_name, expected_list_size, expected_total_size) \
do { \
int32_t list_size, total_size; \
TF_OpKernelConstruction_GetAttrSize(ctx, attr_name, &list_size, \
&total_size, status); \
EXPECT_EQ(TF_OK, TF_GetCode(status)) << TF_Message(status); \
EXPECT_EQ(expected_list_size, list_size); \
EXPECT_EQ(expected_total_size, total_size); \
} while (0)
typedef void* (*MyCreateFuncWithAttr)(TF_OpKernelConstruction*);
class TestKernelAttr : public ::testing::Test {
public:
TestKernelAttr() {}
~TestKernelAttr() override {}
std::unique_ptr<OpKernel> GetFakeKernelWithAttr(const char* op_name,
AttrValue v, Status* status) {
NodeDef def;
def.set_op(op_name);
def.set_name("FakeNode");
def.set_device("FakeDevice");
(*def.mutable_attr())["Attr"] = v;
return CreateOpKernel(DeviceType("FakeDevice"), nullptr, nullptr, def, 1,
status);
}
void CreateAndCallKernelWithAttr(MyCreateFuncWithAttr MyCreateFuncAttr,
const char* op_name, AttrValue& v) {
TF_KernelBuilder* builder = TF_NewKernelBuilder(
op_name, "FakeDevice", MyCreateFuncAttr, &MyComputeFunc, &MyDeleteFunc);
{
TF_Status* status = TF_NewStatus();
TF_RegisterKernelBuilder("FakeNode", builder, status);
EXPECT_EQ(TF_OK, TF_GetCode(status));
TF_DeleteStatus(status);
}
Status status;
std::unique_ptr<OpKernel> kernel =
GetFakeKernelWithAttr(op_name, v, &status);
TF_EXPECT_OK(status);
ASSERT_NE(nullptr, kernel.get());
kernel->Compute(nullptr);
ASSERT_TRUE(delete_called);
}
};
TEST_F(TestKernelAttr, GetNodeDef) {
auto my_create_func = [](TF_OpKernelConstruction* ctx) {
struct MyCustomKernel* s = new struct MyCustomKernel;
s->created = true;
s->compute_called = false;
TF_Status* status = TF_NewStatus();
TF_Buffer* node_def_buf = TF_OpKernelConstruction_GetNodeDef(ctx, status);
EXPECT_EQ(TF_OK, TF_GetCode(status)) << TF_Message(status);
NodeDef node_def;
node_def.ParseFromArray(node_def_buf->data, node_def_buf->length);
EXPECT_EQ(node_def.op(), "TestKernelAttrGetNodeDef");
EXPECT_EQ(node_def.name(), "FakeNode");
EXPECT_EQ(node_def.device(), "FakeDevice");
EXPECT_EQ(node_def.attr_size(), 1);
const ::tensorflow::AttrValue& value = node_def.attr().at("Attr");
EXPECT_TRUE(value.value_case() == ::tensorflow::AttrValue::ValueCase::kI);
EXPECT_EQ(value.i(), 1234);
TF_DeleteBuffer(node_def_buf);
TF_DeleteStatus(status);
return static_cast<void*>(s);
};
REGISTER_OP("TestKernelAttrGetNodeDef")
.Attr("Attr: int")
.SetShapeFn(tensorflow::shape_inference::UnknownShape);
AttrValue v;
v.set_i(1234);
CreateAndCallKernelWithAttr(my_create_func, "TestKernelAttrGetNodeDef", v);
}
TEST_F(TestKernelAttr, String) {
auto my_create_func = [](TF_OpKernelConstruction* ctx) {
struct MyCustomKernel* s = new struct MyCustomKernel;
s->created = true;
s->compute_called = false;
std::unique_ptr<char[]> val(new char[5]);
TF_Status* status = TF_NewStatus();
EXPECT_TF_SIZE(/*attr_name*/ "Attr", /*expected_list_size*/ -1,
/*expected_total_size*/ 5);
TF_OpKernelConstruction_GetAttrString(ctx, "Attr", val.get(),
/*max_length*/ 5, status);
EXPECT_EQ(TF_OK, TF_GetCode(status)) << TF_Message(status);
EXPECT_EQ("bunny", string(static_cast<const char*>(val.get()), 5));
TF_DeleteStatus(status);
return static_cast<void*>(s);
};
AttrValue v;
v.set_s("bunny");
CreateAndCallKernelWithAttr(my_create_func, "TestKernelAttrString", v);
}
TEST_F(TestKernelAttr, StringList) {
auto my_create_func = [](TF_OpKernelConstruction* ctx) {
struct MyCustomKernel* s = new struct MyCustomKernel;
s->created = true;
s->compute_called = false;
std::vector<string> list = {"bugs", "bunny", "duck"};
int list_total_size = 0;
for (const auto& s : list) {
list_total_size += s.size();
}
TF_Status* status = TF_NewStatus();
std::unique_ptr<char*[]> values(new char*[list.size()]);
std::unique_ptr<size_t[]> lens(new size_t[list.size()]);
std::unique_ptr<char[]> storage(new char[list_total_size]);
EXPECT_TF_SIZE(/*attr_name*/ "Attr", /*expected_list_size*/ list.size(),
/*expected_total_size*/ list_total_size);
TF_OpKernelConstruction_GetAttrStringList(
ctx, "Attr", values.get(), lens.get(), list.size(), storage.get(),
list_total_size, status);
EXPECT_EQ(TF_OK, TF_GetCode(status)) << TF_Message(status);
for (size_t i = 0; i < list.size(); ++i) {
EXPECT_EQ(list[i].size(), lens[i]) << i;
EXPECT_EQ(list[i], string(static_cast<const char*>(values[i]), lens[i]))
<< i;
}
TF_DeleteStatus(status);
return static_cast<void*>(s);
};
AttrValue v;
std::string attr_in[] = {"bugs", "bunny", "duck"};
SetAttrValue(gtl::ArraySlice<std::string>(attr_in, 3), &v);
CreateAndCallKernelWithAttr(my_create_func, "TestKernelAttrStringList", v);
}
TEST_F(TestKernelAttr, Tensor) {
struct TensorProtoHelpers {
public:
static ::tensorflow::TensorProto GenerateTensorProto() {
::tensorflow::TensorProto tensor_proto;
tensor_proto.mutable_tensor_shape()->add_dim()->set_size(2);
tensor_proto.mutable_tensor_shape()->add_dim()->set_size(3);
tensor_proto.set_dtype(DT_INT32);
tensor_proto.add_int_val(1);
tensor_proto.add_int_val(2);
tensor_proto.add_int_val(3);
tensor_proto.add_int_val(4);
tensor_proto.add_int_val(5);
tensor_proto.add_int_val(6);
return tensor_proto;
}
};
auto my_create_func = [](TF_OpKernelConstruction* ctx) {
struct MyCustomKernel* s = new struct MyCustomKernel;
s->created = true;
s->compute_called = false;
TF_Tensor* val;
TF_Status* status = TF_NewStatus();
EXPECT_TF_SIZE(/*attr_name*/ "Attr", /*expected_list_size*/ -1,
/*expected_total_size*/ -1);
TF_OpKernelConstruction_GetAttrTensor(ctx, "Attr", &val, status);
EXPECT_EQ(TF_OK, TF_GetCode(status)) << TF_Message(status);
::tensorflow::Tensor expected_tensor;
EXPECT_TRUE(
expected_tensor.FromProto(TensorProtoHelpers::GenerateTensorProto()));
::tensorflow::Tensor actual_tensor;
EXPECT_TRUE(TF_TensorToTensor(val, &actual_tensor).ok());
EXPECT_EQ(actual_tensor.tensor_data(), expected_tensor.tensor_data());
EXPECT_EQ(actual_tensor.shape(), expected_tensor.shape());
EXPECT_EQ(actual_tensor.dtype(), expected_tensor.dtype());
TF_DeleteStatus(status);
TF_DeleteTensor(val);
return static_cast<void*>(s);
};
AttrValue v;
::tensorflow::TensorProto* tensor_proto = v.mutable_tensor();
*tensor_proto = TensorProtoHelpers::GenerateTensorProto();
CreateAndCallKernelWithAttr(my_create_func, "TestKernelAttrTensor", v);
}
TEST_F(TestKernelAttr, TensorList) {
struct TensorProtoHelpers {
public:
static ::tensorflow::TensorProto GenerateTensorProto1() {
::tensorflow::TensorProto tensor_proto;
tensor_proto.mutable_tensor_shape()->add_dim()->set_size(2);
tensor_proto.mutable_tensor_shape()->add_dim()->set_size(2);
tensor_proto.set_dtype(DT_INT32);
tensor_proto.add_int_val(1);
tensor_proto.add_int_val(2);
tensor_proto.add_int_val(3);
tensor_proto.add_int_val(4);
return tensor_proto;
}
static ::tensorflow::TensorProto GenerateTensorProto2() {
::tensorflow::TensorProto tensor_proto;
tensor_proto.mutable_tensor_shape()->add_dim()->set_size(2);
tensor_proto.mutable_tensor_shape()->add_dim()->set_size(3);
tensor_proto.set_dtype(DT_FLOAT);
tensor_proto.add_float_val(5.0f);
tensor_proto.add_float_val(6.0f);
tensor_proto.add_float_val(7.0f);
tensor_proto.add_float_val(8.0f);
tensor_proto.add_float_val(9.0f);
tensor_proto.add_float_val(10.0f);
return tensor_proto;
}
};
auto my_create_func = [](TF_OpKernelConstruction* ctx) {
struct MyCustomKernel* s = new struct MyCustomKernel;
s->created = true;
s->compute_called = false;
const size_t list_size = 2;
TF_Tensor* values[list_size];
TF_Status* status = TF_NewStatus();
EXPECT_TF_SIZE(/*attr_name*/ "Attr", /*expected_list_size*/ list_size,
/*expected_total_size*/ -1);
TF_OpKernelConstruction_GetAttrTensorList(ctx, "Attr", values, list_size,
status);
EXPECT_EQ(TF_OK, TF_GetCode(status)) << TF_Message(status);
::tensorflow::Tensor expected_tensor1;
EXPECT_TRUE(
expected_tensor1.FromProto(TensorProtoHelpers::GenerateTensorProto1()));
::tensorflow::Tensor actual_tensor1;
EXPECT_TRUE(TF_TensorToTensor(values[0], &actual_tensor1).ok());
EXPECT_EQ(actual_tensor1.tensor_data(), expected_tensor1.tensor_data());
EXPECT_EQ(actual_tensor1.shape(), expected_tensor1.shape());
EXPECT_EQ(actual_tensor1.dtype(), expected_tensor1.dtype());
::tensorflow::Tensor expected_tensor2;
EXPECT_TRUE(
expected_tensor2.FromProto(TensorProtoHelpers::GenerateTensorProto2()));
::tensorflow::Tensor actual_tensor2;
EXPECT_TRUE(TF_TensorToTensor(values[1], &actual_tensor2).ok());
EXPECT_EQ(actual_tensor2.tensor_data(), expected_tensor2.tensor_data());
EXPECT_EQ(actual_tensor2.shape(), expected_tensor2.shape());
EXPECT_EQ(actual_tensor2.dtype(), expected_tensor2.dtype());
TF_DeleteStatus(status);
TF_DeleteTensor(values[0]);
TF_DeleteTensor(values[1]);
return static_cast<void*>(s);
};
AttrValue v;
::tensorflow::TensorProto* tensor_proto1 = v.mutable_list()->add_tensor();
*tensor_proto1 = TensorProtoHelpers::GenerateTensorProto1();
::tensorflow::TensorProto* tensor_proto2 = v.mutable_list()->add_tensor();
*tensor_proto2 = TensorProtoHelpers::GenerateTensorProto2();
CreateAndCallKernelWithAttr(my_create_func, "TestKernelAttrTensorList", v);
}
TEST_F(TestKernelAttr, Int) {
auto my_create_func = [](TF_OpKernelConstruction* ctx) {
struct MyCustomKernel* s = new struct MyCustomKernel;
s->created = true;
s->compute_called = false;
int64_t val;
TF_Status* status = TF_NewStatus();
EXPECT_TF_SIZE(/*attr_name*/ "Attr", /*expected_list_size*/ -1,
/*expected_total_size*/ -1);
TF_OpKernelConstruction_GetAttrInt64(ctx, "Attr", &val, status);
EXPECT_EQ(TF_OK, TF_GetCode(status)) << TF_Message(status);
EXPECT_EQ(1234, val);
TF_DeleteStatus(status);
return static_cast<void*>(s);
};
AttrValue v;
v.set_i(1234);
CreateAndCallKernelWithAttr(my_create_func, "TestKernelAttrInt", v);
}
TEST_F(TestKernelAttr, IntList) {
auto my_create_func = [](TF_OpKernelConstruction* ctx) {
struct MyCustomKernel* s = new struct MyCustomKernel;
s->created = true;
s->compute_called = false;
const int64_t list[] = {1, 2, 3, 4};
const size_t list_size = TF_ARRAYSIZE(list);
int64_t values[list_size];
TF_Status* status = TF_NewStatus();
EXPECT_TF_SIZE(/*attr_name*/ "Attr", /*expected_list_size*/ list_size,
/*expected_total_size*/ -1);
TF_OpKernelConstruction_GetAttrInt64List(ctx, "Attr", values, list_size,
status);
EXPECT_EQ(TF_OK, TF_GetCode(status)) << TF_Message(status);
EXPECT_TRUE(
std::equal(std::begin(list), std::end(list), std::begin(values)));
TF_DeleteStatus(status);
return static_cast<void*>(s);
};
AttrValue v;
int64_t attr_in[] = {1, 2, 3, 4};
SetAttrValue(gtl::ArraySlice<int64_t>(attr_in, 4), &v);
CreateAndCallKernelWithAttr(my_create_func, "TestKernelAttrIntList", v);
}
TEST_F(TestKernelAttr, Float) {
auto my_create_func = [](TF_OpKernelConstruction* ctx) {
struct MyCustomKernel* s = new struct MyCustomKernel;
s->created = true;
s->compute_called = false;
float val;
TF_Status* status = TF_NewStatus();
EXPECT_TF_SIZE(/*attr_name*/ "Attr", /*expected_list_size*/ -1,
/*expected_total_size*/ -1);
TF_OpKernelConstruction_GetAttrFloat(ctx, "Attr", &val, status);
EXPECT_EQ(TF_OK, TF_GetCode(status)) << TF_Message(status);
EXPECT_FLOAT_EQ(2.718, val);
TF_DeleteStatus(status);
return static_cast<void*>(s);
};
AttrValue v;
v.set_f(2.718);
CreateAndCallKernelWithAttr(my_create_func, "TestKernelAttrFloat", v);
}
TEST_F(TestKernelAttr, FloatList) {
auto my_create_func = [](TF_OpKernelConstruction* ctx) {
struct MyCustomKernel* s = new struct MyCustomKernel;
s->created = true;
s->compute_called = false;
const float list[] = {1.414, 2.718, 3.1415};
const size_t list_size = TF_ARRAYSIZE(list);
float values[list_size];
TF_Status* status = TF_NewStatus();
EXPECT_TF_SIZE(/*attr_name*/ "Attr", /*expected_list_size*/ list_size,
/*expected_total_size*/ -1);
TF_OpKernelConstruction_GetAttrFloatList(ctx, "Attr", values, list_size,
status);
EXPECT_EQ(TF_OK, TF_GetCode(status)) << TF_Message(status);
EXPECT_TRUE(
std::equal(std::begin(list), std::end(list), std::begin(values)));
TF_DeleteStatus(status);
return static_cast<void*>(s);
};
AttrValue v;
float attr_in[] = {1.414, 2.718, 3.1415};
SetAttrValue(gtl::ArraySlice<float>(attr_in, 3), &v);
CreateAndCallKernelWithAttr(my_create_func, "TestKernelAttrFloatList", v);
}
TEST_F(TestKernelAttr, Bool) {
auto my_create_func = [](TF_OpKernelConstruction* ctx) {
struct MyCustomKernel* s = new struct MyCustomKernel;
s->created = true;
s->compute_called = false;
unsigned char val;
TF_Status* status = TF_NewStatus();
EXPECT_TF_SIZE(/*attr_name*/ "Attr", /*expected_list_size*/ -1,
/*expected_total_size*/ -1);
TF_OpKernelConstruction_GetAttrBool(ctx, "Attr", &val, status);
EXPECT_EQ(TF_OK, TF_GetCode(status)) << TF_Message(status);
EXPECT_EQ(1, val);
TF_DeleteStatus(status);
return static_cast<void*>(s);
};
AttrValue v;
v.set_b(true);
CreateAndCallKernelWithAttr(my_create_func, "TestKernelAttrBool", v);
}
TEST_F(TestKernelAttr, BoolList) {
auto my_create_func = [](TF_OpKernelConstruction* ctx) {
struct MyCustomKernel* s = new struct MyCustomKernel;
s->created = true;
s->compute_called = false;
const unsigned char list[] = {1, 0, 1, 0};
const size_t list_size = TF_ARRAYSIZE(list);
unsigned char values[list_size];
TF_Status* status = TF_NewStatus();
EXPECT_TF_SIZE(/*attr_name*/ "Attr", /*expected_list_size*/ list_size,
/*expected_total_size*/ -1);
TF_OpKernelConstruction_GetAttrBoolList(ctx, "Attr", values, list_size,
status);
EXPECT_EQ(TF_OK, TF_GetCode(status)) << TF_Message(status);
EXPECT_TRUE(
std::equal(std::begin(list), std::end(list), std::begin(values)));
TF_DeleteStatus(status);
return static_cast<void*>(s);
};
AttrValue v;
bool attr_in[] = {true, false, true, false};
SetAttrValue(gtl::ArraySlice<bool>(attr_in, 4), &v);
CreateAndCallKernelWithAttr(my_create_func, "TestKernelAttrBoolList", v);
}
TEST_F(TestKernelAttr, Type) {
auto my_create_func = [](TF_OpKernelConstruction* ctx) {
struct MyCustomKernel* s = new struct MyCustomKernel;
s->created = true;
s->compute_called = false;
TF_DataType val;
TF_Status* status = TF_NewStatus();
EXPECT_TF_SIZE(/*attr_name*/ "Attr", /*expected_list_size*/ -1,
/*expected_total_size*/ -1);
TF_OpKernelConstruction_GetAttrType(ctx, "Attr", &val, status);
EXPECT_EQ(TF_OK, TF_GetCode(status)) << TF_Message(status);
EXPECT_EQ(TF_FLOAT, val);
TF_DeleteStatus(status);
return static_cast<void*>(s);
};
AttrValue v;
v.set_type(DT_FLOAT);
CreateAndCallKernelWithAttr(my_create_func, "TestKernelAttrType", v);
}
TEST_F(TestKernelAttr, TypeList) {
auto my_create_func = [](TF_OpKernelConstruction* ctx) {
struct MyCustomKernel* s = new struct MyCustomKernel;
s->created = true;
s->compute_called = false;
const TF_DataType list[] = {TF_FLOAT, TF_DOUBLE, TF_HALF, TF_COMPLEX128};
const size_t list_size = TF_ARRAYSIZE(list);
TF_DataType values[list_size];
TF_Status* status = TF_NewStatus();
EXPECT_TF_SIZE(/*attr_name*/ "Attr", /*expected_list_size*/ list_size,
/*expected_total_size*/ -1);
TF_OpKernelConstruction_GetAttrTypeList(ctx, "Attr", values, list_size,
status);
EXPECT_EQ(TF_OK, TF_GetCode(status)) << TF_Message(status);
EXPECT_TRUE(
std::equal(std::begin(list), std::end(list), std::begin(values)));
TF_DeleteStatus(status);
return static_cast<void*>(s);
};
AttrValue v;
DataType attr_in[] = {DT_FLOAT, DT_DOUBLE, DT_HALF, DT_COMPLEX128};
SetAttrValue(gtl::ArraySlice<DataType>(attr_in, 4), &v);
CreateAndCallKernelWithAttr(my_create_func, "TestKernelAttrTypeList", v);
}
#undef EXPECT_TF_SIZE
class DummyDevice : public DeviceBase {
public:
explicit DummyDevice(Env* env) : DeviceBase(env) {}
Allocator* GetAllocator(AllocatorAttributes /*attr*/) override {
return cpu_allocator();
}
};
TEST(TestKernel, TestInputAndOutputCount) {
const char* node_name = "InputOutputCounterKernel";
const char* op_name = "BarOp";
const char* device_name = "FakeDeviceName2";
REGISTER_OP(op_name)
.Input("input1: double")
.Input("input2: uint8")
.Output("output1: uint8")
.Attr("SomeDataTypeAttr: type");
static int num_inputs = 0;
static int num_outputs = 0;
// A kernel whose Compute function has a side-effect of updating num_inputs
// and num_outputs. Various functions on TF_OpKernelContext are also
// exercised.
auto my_compute_func = [](void* kernel, TF_OpKernelContext* ctx) {
num_inputs = TF_NumInputs(ctx);
num_outputs = TF_NumOutputs(ctx);
TF_Tensor* input = nullptr;
TF_Status* s = TF_NewStatus();
TF_GetInput(ctx, 0, &input, s);
EXPECT_EQ(TF_OK, TF_GetCode(s)) << "Failed to get input: " << TF_Message(s);
EXPECT_EQ(123, *static_cast<tensorflow::uint8*>(TF_TensorData(input)));
TF_GetInput(ctx, -1, &input, s);
EXPECT_EQ(TF_OUT_OF_RANGE, TF_GetCode(s));
TF_GetInput(ctx, 3, &input, s);
EXPECT_EQ(TF_OUT_OF_RANGE, TF_GetCode(s));
// Copy the input tensor to output.
TF_SetOutput(ctx, 0, input, s);
EXPECT_EQ(TF_OK, TF_GetCode(s));
TF_SetOutput(ctx, 24, input, s);
EXPECT_EQ(TF_OUT_OF_RANGE, TF_GetCode(s));
EXPECT_EQ(TF_UINT8, TF_ExpectedOutputDataType(ctx, 0));
EXPECT_DEATH({ TF_ExpectedOutputDataType(ctx, 1); },
"Check failed: i < cc_ctx->num_outputs");
EXPECT_DEATH({ TF_ExpectedOutputDataType(ctx, -1); },
"Check failed: i >= 0");
TF_DeleteStatus(s);
if (input != nullptr) {
TF_DeleteTensor(input);
}
};
TF_KernelBuilder* builder = TF_NewKernelBuilder(op_name, device_name, nullptr,
my_compute_func, nullptr);
{
TF_Status* status = TF_NewStatus();
TF_RegisterKernelBuilder(node_name, builder, status);
EXPECT_EQ(TF_OK, TF_GetCode(status));
TF_DeleteStatus(status);
}
{
OpKernelContext::Params p;
DummyDevice dummy_device(nullptr);
p.device = &dummy_device;
p.step_id = 43;
Tensor t(tensorflow::uint8(123));
gtl::InlinedVector<TensorValue, 4> inputs;
// Simulate 2 inputs
inputs.emplace_back(&t);
inputs.emplace_back();
p.inputs = inputs;
Status status;
std::unique_ptr<OpKernel> kernel =
GetFakeKernel(device_name, op_name, node_name, &status);
TF_EXPECT_OK(status);
ASSERT_NE(nullptr, kernel.get());
p.op_kernel = kernel.get();
OpKernelContext ctx(&p);
kernel->Compute(&ctx);
ASSERT_EQ(2, num_inputs);
ASSERT_EQ(1, num_outputs);
ASSERT_EQ(123, ctx.mutable_output(0)->scalar<tensorflow::uint8>()());
}
}
TEST(TestKernel, DeleteKernelBuilderIsOkOnNull) {
TF_DeleteKernelBuilder(nullptr);
}
std::string ExpectedString(const char* type) {
const auto format_str = R"str(kernel {
op: "TypeOp%s"
device_type: "FakeDeviceName1"
constraint {
name: "T"
allowed_values {
list {
type: %s
}
}
}
}
)str";
return absl::StrFormat(format_str, type, type);
}
#define TEST_KERNEL_TYPE_CONSTRAINT(tf_type, dtype) \
TEST(TestKernel, TestTypeConstraint##tf_type) { \
const char* node_name = "SomeNodeName"; \
const char* op_name = "TypeOp" #dtype; \
const char* device_name = "FakeDeviceName1"; \
\
REGISTER_OP(op_name) \
.Input("input1: double") \
.Input("input2: uint8") \
.Output("output1: uint8") \
.Attr("T: type"); \
\
TF_KernelBuilder* builder = TF_NewKernelBuilder( \
op_name, device_name, &MyCreateFunc, &MyComputeFunc, &MyDeleteFunc); \
TF_Status* status = TF_NewStatus(); \
TF_KernelBuilder_TypeConstraint(builder, "T", TF_DataType::tf_type, \
status); \
EXPECT_EQ(TF_OK, TF_GetCode(status)); \
TF_RegisterKernelBuilder(node_name, builder, status); \
EXPECT_EQ(TF_OK, TF_GetCode(status)); \
\
TF_Buffer* buf = TF_GetRegisteredKernelsForOp(op_name, status); \
EXPECT_EQ(TF_OK, TF_GetCode(status)); \
KernelList list; \
list.ParseFromArray(buf->data, buf->length); \
KernelList expected_proto; \
protobuf::TextFormat::ParseFromString(ExpectedString(#dtype), \
&expected_proto); \
ASSERT_EQ(expected_proto.DebugString(), list.DebugString()); \
\
TF_DeleteBuffer(buf); \
TF_DeleteStatus(status); \
TF_DeleteKernelBuilder(builder); \
ASSERT_TRUE(delete_called); \
}
TEST_KERNEL_TYPE_CONSTRAINT(TF_HALF, DT_HALF);
TEST_KERNEL_TYPE_CONSTRAINT(TF_BFLOAT16, DT_BFLOAT16);
TEST_KERNEL_TYPE_CONSTRAINT(TF_FLOAT, DT_FLOAT);
TEST_KERNEL_TYPE_CONSTRAINT(TF_DOUBLE, DT_DOUBLE);
TEST_KERNEL_TYPE_CONSTRAINT(TF_UINT64, DT_UINT64);
TEST_KERNEL_TYPE_CONSTRAINT(TF_UINT32, DT_UINT32);
TEST_KERNEL_TYPE_CONSTRAINT(TF_UINT16, DT_UINT16);
TEST_KERNEL_TYPE_CONSTRAINT(TF_UINT8, DT_UINT8);
TEST_KERNEL_TYPE_CONSTRAINT(TF_INT8, DT_INT8);
TEST_KERNEL_TYPE_CONSTRAINT(TF_INT32, DT_INT32);
TEST_KERNEL_TYPE_CONSTRAINT(TF_COMPLEX64, DT_COMPLEX64);
TEST_KERNEL_TYPE_CONSTRAINT(TF_COMPLEX128, DT_COMPLEX128);
TEST_KERNEL_TYPE_CONSTRAINT(TF_QINT8, DT_QINT8);
TEST_KERNEL_TYPE_CONSTRAINT(TF_QUINT8, DT_QUINT8);
TEST_KERNEL_TYPE_CONSTRAINT(TF_QINT32, DT_QINT32);
TEST_KERNEL_TYPE_CONSTRAINT(TF_QINT16, DT_QINT16);
TEST_KERNEL_TYPE_CONSTRAINT(TF_QUINT16, DT_QUINT16);
TEST(TestKernel, TestHostMemory) {
const char* node_name = "SomeNodeName";
const char* op_name = "HostMemoryOp";
const char* device_name = "FakeDeviceName1";
REGISTER_OP(op_name)
.Input("input1: double")
.Input("input2: uint8")
.Output("output1: uint8")
.Output("output2: uint8")
.Attr("T: type");
auto my_compute_func = [](void* kernel, TF_OpKernelContext* ctx) {
MyComputeFunc(kernel, ctx);
TF_Status* status = TF_NewStatus();
TF_SetStatus(status, TF_OK, "");
EXPECT_EQ(false, TF_IsHostMemoryInput(ctx, 0, status));
EXPECT_EQ(TF_OK, TF_GetCode(status));
TF_SetStatus(status, TF_OK, "");
EXPECT_EQ(true, TF_IsHostMemoryInput(ctx, 1, status));
EXPECT_EQ(TF_OK, TF_GetCode(status));
TF_SetStatus(status, TF_OK, "");
EXPECT_EQ(true, TF_IsHostMemoryOutput(ctx, 0, status));
EXPECT_EQ(TF_OK, TF_GetCode(status));
TF_SetStatus(status, TF_OK, "");
EXPECT_EQ(false, TF_IsHostMemoryOutput(ctx, 1, status));
EXPECT_EQ(TF_OK, TF_GetCode(status));
TF_SetStatus(status, TF_OK, "");
TF_IsHostMemoryInput(ctx, -1, status);
EXPECT_EQ(TF_OUT_OF_RANGE, TF_GetCode(status));
TF_SetStatus(status, TF_OK, "");
TF_IsHostMemoryInput(ctx, 2, status);
EXPECT_EQ(TF_OUT_OF_RANGE, TF_GetCode(status));
TF_SetStatus(status, TF_OK, "");
TF_IsHostMemoryOutput(ctx, -1, status);
EXPECT_EQ(TF_OUT_OF_RANGE, TF_GetCode(status));
TF_SetStatus(status, TF_OK, "");
TF_IsHostMemoryOutput(ctx, 2, status);
EXPECT_EQ(TF_OUT_OF_RANGE, TF_GetCode(status));
TF_DeleteStatus(status);
};
TF_KernelBuilder* builder = TF_NewKernelBuilder(
op_name, device_name, &MyCreateFunc, my_compute_func, &MyDeleteFunc);
TF_KernelBuilder_HostMemory(builder, "input2");
TF_KernelBuilder_HostMemory(builder, "output1");
TF_Status* status = TF_NewStatus();
TF_RegisterKernelBuilder(node_name, builder, status);
EXPECT_EQ(TF_OK, TF_GetCode(status));
TF_Buffer* buf = TF_GetRegisteredKernelsForOp(op_name, status);
EXPECT_EQ(TF_OK, TF_GetCode(status));
KernelList list;
list.ParseFromArray(buf->data, buf->length);
KernelList expected_proto;
protobuf::TextFormat::ParseFromString(
R"str(kernel {
op: "HostMemoryOp"
device_type: "FakeDeviceName1"
host_memory_arg: "input2"
host_memory_arg: "output1"
}
)str",
&expected_proto);
ASSERT_EQ(list.DebugString(), expected_proto.DebugString());
TF_DeleteBuffer(buf);
TF_DeleteStatus(status);
TF_DeleteKernelBuilder(builder);
ASSERT_TRUE(delete_called);
}
class DeviceKernelOpTest : public OpsTestBase {
protected:
void SetupOp(const char* op_name, const char* node_name,
void (*compute_func)(void*, TF_OpKernelContext*)) {
TF_KernelBuilder* builder = TF_NewKernelBuilder(
op_name, device_name_, nullptr, compute_func, nullptr);
TF_Status* status = TF_NewStatus();
TF_RegisterKernelBuilder(node_name, builder, status);
EXPECT_EQ(TF_OK, TF_GetCode(status));
TF_DeleteStatus(status);
#if GOOGLE_CUDA || TENSORFLOW_USE_ROCM
std::unique_ptr<Device> device(
DeviceFactory::NewDevice(device_name_, {}, "/job:a/replica:0/task:0"));
OpsTestBase::SetDevice(DEVICE_GPU, std::move(device));
#endif
TF_ASSERT_OK(NodeDefBuilder(op_name, op_name).Finalize(node_def()));
TF_ASSERT_OK(InitOp());
}
#if GOOGLE_CUDA || TENSORFLOW_USE_ROCM
const char* device_name_ = tensorflow::DEVICE_GPU;
#else
const char* device_name_ = tensorflow::DEVICE_CPU;
#endif
};
// Validates that the tensor has shape and type corresponding to
// dims and dtype.
void validate_tensor(TF_Tensor* tensor, int64_t* dims, int64_t num_dims,
TF_DataType dtype);
// Copies data of length tensor_size_bytes from values to tensor.
template <typename T>
void set_tensor_data(TF_Tensor* tensor, T* values, size_t tensor_size_bytes,
TF_OpKernelContext* ctx);
REGISTER_OP("StreamOp").Output("output1: float");
TEST_F(DeviceKernelOpTest, TestStream) {
auto my_compute_func = [](void* kernel, TF_OpKernelContext* ctx) {
TF_Status* s = TF_NewStatus();
SP_Stream stream = TF_GetStream(ctx, s);
// Stream is always null if device is not a pluggable device. More test
// cases will be added when pluggable device mechanism is supported.
EXPECT_EQ(stream, nullptr);
EXPECT_NE(TF_OK, TF_GetCode(s));
TF_DeleteStatus(s);
};
SetupOp("StreamOp", "StreamOp", my_compute_func);
TF_ASSERT_OK(RunOpKernel());
}
REGISTER_OP("AllocateOutputOp1").Output("output1: float");
TEST_F(DeviceKernelOpTest, TestAllocateOutputSizeOne) {
auto my_compute_func = [](void* kernel, TF_OpKernelContext* ctx) {
// Allocate output
TF_Status* s = TF_NewStatus();
int64_t dim = 1;
size_t tensor_size_bytes = TF_DataTypeSize(TF_FLOAT);
TF_Tensor* output = TF_AllocateOutput(
/*context=*/ctx, /*index=*/0, /*dtype=*/TF_FLOAT, /*dims=*/&dim,
/*num_dims=*/1, /*len=*/tensor_size_bytes, s);
validate_tensor(output, &dim, 1, TF_FLOAT);
// Set output to 3
float values[1] = {3.0f};
set_tensor_data<float>(output, values, tensor_size_bytes, ctx);
TF_DeleteStatus(s);
TF_DeleteTensor(output);
};
SetupOp("AllocateOutputOp1", "AllocateOutput1", my_compute_func);
TF_ASSERT_OK(RunOpKernel());
Tensor* output = GetOutput(0);
EXPECT_EQ("Tensor<type: float shape: [1] values: 3>",
output->DebugString(100));
}
REGISTER_OP("AllocateOutputOp0").Output("output1: float");
TEST_F(DeviceKernelOpTest, TestAllocateEmptyOutput) {
auto my_compute_func = [](void* kernel, TF_OpKernelContext* ctx) {
TF_Status* s = TF_NewStatus();
// Allocate empty output
int64_t dim = 0;
TF_Tensor* output = TF_AllocateOutput(
/*context=*/ctx, /*index=*/0, /*dtype=*/TF_FLOAT, /*dims=*/&dim,
/*num_dims=*/1, /*len=*/0, s);
EXPECT_EQ(TF_OK, TF_GetCode(s));
validate_tensor(output, &dim, 1, TF_FLOAT);
TF_DeleteStatus(s);
TF_DeleteTensor(output);
};
SetupOp("AllocateOutputOp0", "AllocateOutput0", my_compute_func);
TF_ASSERT_OK(RunOpKernel());
Tensor* output = GetOutput(0);
EXPECT_EQ("Tensor<type: float shape: [0] values: >",
output->DebugString(100));
}
REGISTER_OP("AllocateOutputOp2x3").Output("output1: float");
TEST_F(DeviceKernelOpTest, TestAllocateOutputSize2x3) {
auto my_compute_func = [](void* kernel, TF_OpKernelContext* ctx) {
TF_Status* s = TF_NewStatus();
// Allocate 2x3 output
int64_t dim[2] = {2, 3};
size_t tensor_size_bytes = TF_DataTypeSize(TF_FLOAT) * 6;
TF_Tensor* output = TF_AllocateOutput(
/*context=*/ctx, /*index=*/0, /*dtype=*/TF_FLOAT, /*dims=*/dim,
/*num_dims=*/2, /*len=*/tensor_size_bytes, s);
EXPECT_EQ(TF_OK, TF_GetCode(s));
validate_tensor(output, dim, 2, TF_FLOAT);
// Set output to [1 2 3 4 5 6]
float values[6] = {1, 2, 3, 4, 5, 6};
set_tensor_data<float>(output, values, tensor_size_bytes, ctx);
TF_DeleteStatus(s);
TF_DeleteTensor(output);
};
SetupOp("AllocateOutputOp2x3", "AllocateOutput2x3", my_compute_func);
TF_ASSERT_OK(RunOpKernel());
Tensor* output = GetOutput(0);
EXPECT_EQ("Tensor<type: float shape: [2,3] values: [1 2 3][4 5 6]>",
output->DebugString(100));
}
REGISTER_OP("AllocateTempOp1").Output("output1: float");
TEST_F(DeviceKernelOpTest, TestAllocateTempSizeOne) {
auto my_compute_func = [](void* kernel, TF_OpKernelContext* ctx) {
// Allocate scalar TF_Tensor
TF_Status* s = TF_NewStatus();
int64_t dim = 1;
TF_AllocatorAttributes alloc_attrs;
alloc_attrs.struct_size = TF_ALLOCATOR_ATTRIBUTES_STRUCT_SIZE;
#if GOOGLE_CUDA || TENSORFLOW_USE_ROCM
alloc_attrs.on_host = 0;
#else
alloc_attrs.on_host = 1;
#endif
TF_Tensor* output = TF_AllocateTemp(
/*context=*/ctx, /*dtype=*/TF_FLOAT, /*dims=*/&dim,
/*num_dims=*/1, /*allocator_attributes*/ &alloc_attrs, s);
size_t tensor_size_bytes = TF_DataTypeSize(TF_FLOAT);
EXPECT_EQ(TF_OK, TF_GetCode(s));
validate_tensor(output, &dim, 1, TF_FLOAT);
// Set TF_Tensor value to 3
float values[1] = {3.0f};
set_tensor_data<float>(output, values, tensor_size_bytes, ctx);
TF_SetOutput(ctx, 0, output, s);
TF_DeleteStatus(s);
TF_DeleteTensor(output);
};
SetupOp("AllocateTempOp1", "AllocateTemp1", my_compute_func);
TF_ASSERT_OK(RunOpKernel());
Tensor* output = GetOutput(0);
EXPECT_EQ("Tensor<type: float shape: [1] values: 3>",
output->DebugString(100));
}
REGISTER_OP("AllocateTempOp0").Output("output1: float");
TEST_F(DeviceKernelOpTest, TestAllocateTempEmpty) {
auto my_compute_func = [](void* kernel, TF_OpKernelContext* ctx) {
TF_Status* s = TF_NewStatus();
// Allocate empty TF_Tensor
int64_t dim = 0;
TF_AllocatorAttributes alloc_attrs;
alloc_attrs.struct_size = TF_ALLOCATOR_ATTRIBUTES_STRUCT_SIZE;
#if GOOGLE_CUDA || TENSORFLOW_USE_ROCM
alloc_attrs.on_host = 0;
#else
alloc_attrs.on_host = 1;
#endif
TF_Tensor* output = TF_AllocateTemp(
/*context=*/ctx, /*dtype=*/TF_FLOAT, /*dims=*/&dim,
/*num_dims=*/1, /*allocator_attributes*/ &alloc_attrs, s);
EXPECT_EQ(TF_OK, TF_GetCode(s));
validate_tensor(output, &dim, 1, TF_FLOAT);
TF_SetOutput(ctx, 0, output, s);
TF_DeleteStatus(s);
TF_DeleteTensor(output);
};
SetupOp("AllocateTempOp0", "AllocateTemp0", my_compute_func);
TF_ASSERT_OK(RunOpKernel());
Tensor* output = GetOutput(0);
EXPECT_EQ("Tensor<type: float shape: [0] values: >",
output->DebugString(100));
}
REGISTER_OP("AllocateTempOp2x3").Output("output1: float");
TEST_F(DeviceKernelOpTest, TestAllocateTempSize2x3) {
auto my_compute_func = [](void* kernel, TF_OpKernelContext* ctx) {
TF_Status* s = TF_NewStatus();
size_t tensor_size_bytes = 6 * TF_DataTypeSize(TF_FLOAT);
// Allocate 2x3 TF_Tensor
int64_t dim[2] = {2, 3};
TF_AllocatorAttributes alloc_attrs;
alloc_attrs.struct_size = TF_ALLOCATOR_ATTRIBUTES_STRUCT_SIZE;
#if GOOGLE_CUDA || TENSORFLOW_USE_ROCM
alloc_attrs.on_host = 0;
#else
alloc_attrs.on_host = 1;
#endif
TF_Tensor* output = TF_AllocateTemp(
/*context=*/ctx, /*dtype=*/TF_FLOAT, /*dims=*/dim,
/*num_dims=*/2, /*allocator_attributes*/ &alloc_attrs, s);
EXPECT_EQ(TF_OK, TF_GetCode(s));
validate_tensor(output, dim, 2, TF_FLOAT);
// Set TF_Tensor values to [1 2 3 4 5 6]
float values[6] = {1, 2, 3, 4, 5, 6};
set_tensor_data<float>(output, values, tensor_size_bytes, ctx);
TF_SetOutput(ctx, 0, output, s);
TF_DeleteStatus(s);
TF_DeleteTensor(output);
};
SetupOp("AllocateTempOp2x3", "AllocateTempOp2x3", my_compute_func);
TF_ASSERT_OK(RunOpKernel());
Tensor* output = GetOutput(0);
EXPECT_EQ("Tensor<type: float shape: [2,3] values: [1 2 3][4 5 6]>",
output->DebugString(100));
}
REGISTER_OP("DoNothingOp")
.Input("input1: float")
.Input("input2: float")
.Attr("NumInput3: int >= 0")
.Input("input3: NumInput3 * float")
.Output("output1: float")
.Attr("SomeDataTypeAttr: type");
TEST_F(DeviceKernelOpTest, TestGetKernelInfo) {
auto my_compute_func = [](void* kernel, TF_OpKernelContext* ctx) {
TF_Status* s = TF_NewStatus();
int64_t dim[1] = {1};
TF_AllocatorAttributes alloc_attrs;
alloc_attrs.struct_size = TF_ALLOCATOR_ATTRIBUTES_STRUCT_SIZE;
#if GOOGLE_CUDA || TENSORFLOW_USE_ROCM
alloc_attrs.on_host = 0;
#else
alloc_attrs.on_host = 1;
#endif
// Test if the C API returns expected strings.
TF_StringView sv = TF_GetOpKernelName(ctx);
EXPECT_STREQ(sv.data, "TestGetKernelInfoNode");
sv = TF_GetOpKernelRequestedInput(ctx, 0);
EXPECT_STREQ(sv.data, "input1");
sv = TF_GetOpKernelRequestedInput(ctx, 1);
EXPECT_STREQ(sv.data, "input2");
TF_InputRange_Args args;
args.status = s;
TF_InputRange(ctx, "input3", &args);
EXPECT_EQ(TF_OK, TF_GetCode(s));
EXPECT_EQ(args.start, 2);
EXPECT_EQ(args.stop, 5);
TF_Tensor* output = TF_AllocateTemp(
/*context=*/ctx, /*dtype=*/TF_FLOAT, /*dims=*/dim,
/*num_dims=*/1, /*allocator_attributes*/ &alloc_attrs, s);
TF_SetOutput(ctx, 0, output, s);
TF_DeleteStatus(s);
TF_DeleteTensor(output);
};
const char* node_name = "TestGetKernelInfoNode";
const char* op_name = "DoNothingOp";
const char* device_name = "FakeDeviceName";
TF_KernelBuilder* builder = TF_NewKernelBuilder(op_name, device_name, nullptr,
my_compute_func, nullptr);
TF_Status* status = TF_NewStatus();
TF_RegisterKernelBuilder(node_name, builder, status);
EXPECT_EQ(TF_OK, TF_GetCode(status));
TF_DeleteStatus(status);
{
OpKernelContext::Params p;
DummyDevice dummy_device(nullptr);
p.device = &dummy_device;
AllocatorAttributes alloc_attrs;
p.output_attr_array = &alloc_attrs;
gtl::InlinedVector<TensorValue, 4> inputs;
Tensor t0(1.0f);
Tensor t1(2.0f);
Tensor t2_0(2.0f);
Tensor t2_1(2.1f);
Tensor t2_2(2.2f);
inputs.emplace_back(&t0);
inputs.emplace_back(&t1);
inputs.emplace_back(&t2_0);
inputs.emplace_back(&t2_1);
inputs.emplace_back(&t2_2);
Status status;
std::unique_ptr<OpKernel> kernel =
GetFakeKernel2(device_name, op_name, node_name, &status);
TF_EXPECT_OK(status);
ASSERT_NE(nullptr, kernel.get());
p.op_kernel = kernel.get();
p.inputs = inputs;
OpKernelContext ctx(&p);
kernel->Compute(&ctx);
}
}
TEST_F(DeviceKernelOpTest, TestForwardInputOrAllocateOutput) {
const char* node_name = "TestForwardInputOrAllocateOutputKernel";
const char* op_name = "BazOp";
const char* device_name = "FakeDeviceName";
REGISTER_OP(op_name)
.Input("input1: float")
.Input("input2: float")
.Output("output1: float")
.Attr("SomeDataTypeAttr: type");
// A kernel whose Compute function that forwards a scalar input to output
auto my_compute_func = [](void* kernel, TF_OpKernelContext* ctx) {
TF_Status* s = TF_NewStatus();
int candidate_input_indices[1] = {0};
int forwarded_input;
int64_t output_dims[1] = {};
TF_Tensor* output = TF_ForwardInputOrAllocateOutput(
/*context=*/ctx, candidate_input_indices,
/*num_candidate_input_indices=*/1,
/*output_index=*/0, output_dims, /*output_num_dims=*/0,
&forwarded_input, /*status=*/s);
EXPECT_EQ(TF_OK, TF_GetCode(s));
EXPECT_EQ(forwarded_input, 0);
EXPECT_EQ(TF_FLOAT, TF_TensorType(output));
EXPECT_EQ(0, TF_NumDims(output));
TF_DeleteStatus(s);
TF_DeleteTensor(output);
};
TF_KernelBuilder* builder = TF_NewKernelBuilder(op_name, device_name, nullptr,
my_compute_func, nullptr);
{
TF_Status* status = TF_NewStatus();
TF_RegisterKernelBuilder(node_name, builder, status);
EXPECT_EQ(TF_OK, TF_GetCode(status));
TF_DeleteStatus(status);
}
{
OpKernelContext::Params p;
DummyDevice dummy_device(nullptr);
p.device = &dummy_device;
AllocatorAttributes alloc_attrs;
p.output_attr_array = &alloc_attrs;
Tensor t(123.0f);
gtl::InlinedVector<TensorValue, 4> inputs;
// GetFakeKernel requires a NodeDef with two inputs
inputs.emplace_back(&t);
inputs.emplace_back();
p.inputs = inputs;
Status status;
std::unique_ptr<OpKernel> kernel =
GetFakeKernel(device_name, op_name, node_name, &status);
TF_EXPECT_OK(status);
ASSERT_NE(nullptr, kernel.get());
p.op_kernel = kernel.get();
OpKernelContext ctx(&p);
kernel->Compute(&ctx);
ASSERT_EQ(123, ctx.mutable_output(0)->scalar<float>()());
}
}
void validate_tensor(TF_Tensor* tensor, int64_t* dims, int64_t num_dims,
TF_DataType dtype) {
EXPECT_EQ(TF_FLOAT, TF_TensorType(tensor));
EXPECT_EQ(num_dims, TF_NumDims(tensor));
for (int i = 0; i < num_dims; ++i) {
EXPECT_EQ(dims[i], TF_Dim(tensor, i));
}
}
template <typename T>
void set_tensor_data(TF_Tensor* tensor, T* values, size_t tensor_size_bytes,
TF_OpKernelContext* ctx) {
T* data = reinterpret_cast<T*>(TF_TensorData(tensor));
#if GOOGLE_CUDA || TENSORFLOW_USE_ROCM
OpKernelContext* cc_ctx = reinterpret_cast<OpKernelContext*>(ctx);
cc_ctx->eigen_gpu_device().memcpyHostToDevice(data, values,
tensor_size_bytes);
#else
memcpy(data, values, tensor_size_bytes);
#endif
}
} // namespace tensorflow