2542 lines
92 KiB
C++
2542 lines
92 KiB
C++
/* Copyright 2015 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.
|
|
==============================================================================*/
|
|
|
|
#include "tensorflow/c/c_api.h"
|
|
|
|
#include <algorithm>
|
|
#include <cstddef>
|
|
#include <iterator>
|
|
#include <memory>
|
|
#include <vector>
|
|
|
|
#include "tensorflow/c/c_test_util.h"
|
|
#include "tensorflow/c/tf_status.h"
|
|
#include "tensorflow/cc/saved_model/signature_constants.h"
|
|
#include "tensorflow/cc/saved_model/tag_constants.h"
|
|
#include "tensorflow/core/example/example.pb.h"
|
|
#include "tensorflow/core/example/feature.pb.h"
|
|
#include "tensorflow/core/framework/api_def.pb.h"
|
|
#include "tensorflow/core/framework/common_shape_fns.h"
|
|
#include "tensorflow/core/framework/graph.pb_text.h"
|
|
#include "tensorflow/core/framework/kernel_def.pb.h"
|
|
#include "tensorflow/core/framework/node_def.pb_text.h"
|
|
#include "tensorflow/core/framework/node_def_util.h"
|
|
#include "tensorflow/core/framework/op.h"
|
|
#include "tensorflow/core/framework/op_def.pb.h"
|
|
#include "tensorflow/core/framework/op_kernel.h"
|
|
#include "tensorflow/core/framework/partial_tensor_shape.h"
|
|
#include "tensorflow/core/framework/tensor.h"
|
|
#include "tensorflow/core/framework/tensor_shape.pb.h"
|
|
#include "tensorflow/core/framework/types.pb.h"
|
|
#include "tensorflow/core/graph/tensor_id.h"
|
|
#include "tensorflow/core/lib/core/error_codes.pb.h"
|
|
#include "tensorflow/core/lib/core/status_test_util.h"
|
|
#include "tensorflow/core/lib/io/path.h"
|
|
#include "tensorflow/core/lib/strings/str_util.h"
|
|
#include "tensorflow/core/lib/strings/strcat.h"
|
|
#include "tensorflow/core/platform/test.h"
|
|
#include "tensorflow/core/protobuf/meta_graph.pb.h"
|
|
#include "tensorflow/core/util/equal_graph_def.h"
|
|
|
|
namespace tensorflow {
|
|
TF_Tensor* TF_TensorFromTensor(const Tensor& src, TF_Status* status);
|
|
Status TF_TensorToTensor(const TF_Tensor* src, Tensor* dst);
|
|
|
|
namespace {
|
|
|
|
static void ExpectHasSubstr(StringPiece s, StringPiece expected) {
|
|
EXPECT_TRUE(absl::StrContains(s, expected))
|
|
<< "'" << s << "' does not contain '" << expected << "'";
|
|
}
|
|
|
|
// Returns the GPU device name if there is one (with arbitrary tie breaking if
|
|
// there are more than one), or "" otherwise.
|
|
string GPUDeviceName(TF_Session* session) {
|
|
std::unique_ptr<TF_Status, decltype(&TF_DeleteStatus)> status(
|
|
TF_NewStatus(), TF_DeleteStatus);
|
|
TF_Status* s = status.get();
|
|
std::unique_ptr<TF_DeviceList, decltype(&TF_DeleteDeviceList)> list(
|
|
TF_SessionListDevices(session, s), TF_DeleteDeviceList);
|
|
TF_DeviceList* device_list = list.get();
|
|
|
|
CHECK_EQ(TF_OK, TF_GetCode(s)) << TF_Message(s);
|
|
|
|
const int num_devices = TF_DeviceListCount(device_list);
|
|
LOG(INFO) << "There are " << num_devices << " devices.";
|
|
for (int i = 0; i < num_devices; ++i) {
|
|
const char* device_name = TF_DeviceListName(device_list, i, s);
|
|
CHECK_EQ(TF_OK, TF_GetCode(s)) << TF_Message(s);
|
|
const char* device_type = TF_DeviceListType(device_list, i, s);
|
|
CHECK_EQ(TF_OK, TF_GetCode(s)) << TF_Message(s);
|
|
LOG(INFO) << "Device " << i << " has name " << device_name << ", type "
|
|
<< device_type;
|
|
if (string(device_type) == DEVICE_GPU) {
|
|
return device_name;
|
|
}
|
|
}
|
|
// No GPU device found.
|
|
return "";
|
|
}
|
|
|
|
string GPUDeviceName() {
|
|
std::unique_ptr<TF_Status, decltype(&TF_DeleteStatus)> status(
|
|
TF_NewStatus(), TF_DeleteStatus);
|
|
TF_Status* s = status.get();
|
|
std::unique_ptr<TF_Graph, decltype(&TF_DeleteGraph)> graph(TF_NewGraph(),
|
|
TF_DeleteGraph);
|
|
|
|
TF_SessionOptions* opts = TF_NewSessionOptions();
|
|
TF_Session* sess = TF_NewSession(graph.get(), opts, s);
|
|
TF_DeleteSessionOptions(opts);
|
|
|
|
const string gpu_device_name = GPUDeviceName(sess);
|
|
TF_DeleteSession(sess, s);
|
|
CHECK_EQ(TF_OK, TF_GetCode(s)) << TF_Message(s);
|
|
return gpu_device_name;
|
|
}
|
|
|
|
TEST(CAPI, Version) { EXPECT_STRNE("", TF_Version()); }
|
|
|
|
TEST(CAPI, Status) {
|
|
TF_Status* s = TF_NewStatus();
|
|
EXPECT_EQ(TF_OK, TF_GetCode(s));
|
|
EXPECT_EQ(string(), TF_Message(s));
|
|
TF_SetStatus(s, TF_CANCELLED, "cancel");
|
|
EXPECT_EQ(TF_CANCELLED, TF_GetCode(s));
|
|
EXPECT_EQ(string("cancel"), TF_Message(s));
|
|
TF_DeleteStatus(s);
|
|
}
|
|
|
|
void Deallocator(void* data, size_t, void* arg) {
|
|
tensorflow::cpu_allocator()->DeallocateRaw(data);
|
|
*reinterpret_cast<bool*>(arg) = true;
|
|
}
|
|
|
|
TEST(CAPI, Tensor) {
|
|
const int num_bytes = 6 * sizeof(float);
|
|
float* values =
|
|
reinterpret_cast<float*>(tensorflow::cpu_allocator()->AllocateRaw(
|
|
EIGEN_MAX_ALIGN_BYTES, num_bytes));
|
|
int64_t dims[] = {2, 3};
|
|
bool deallocator_called = false;
|
|
TF_Tensor* t = TF_NewTensor(TF_FLOAT, dims, 2, values, num_bytes,
|
|
&Deallocator, &deallocator_called);
|
|
EXPECT_FALSE(deallocator_called);
|
|
EXPECT_EQ(TF_FLOAT, TF_TensorType(t));
|
|
EXPECT_EQ(2, TF_NumDims(t));
|
|
EXPECT_EQ(dims[0], TF_Dim(t, 0));
|
|
EXPECT_EQ(dims[1], TF_Dim(t, 1));
|
|
EXPECT_EQ(num_bytes, TF_TensorByteSize(t));
|
|
EXPECT_EQ(static_cast<void*>(values), TF_TensorData(t));
|
|
TF_DeleteTensor(t);
|
|
EXPECT_TRUE(deallocator_called);
|
|
}
|
|
|
|
void NoOpDeallocator(void* data, size_t, void*) {}
|
|
|
|
TEST(CAPI, MalformedTensor) {
|
|
// See https://github.com/tensorflow/tensorflow/issues/7394
|
|
// num_dims = 0 implies a scalar, so should be backed by at least 4 bytes of
|
|
// data.
|
|
TF_Tensor* t =
|
|
TF_NewTensor(TF_FLOAT, nullptr, 0, nullptr, 0, &NoOpDeallocator, nullptr);
|
|
ASSERT_TRUE(t == nullptr);
|
|
}
|
|
|
|
TEST(CAPI, AllocateTensor) {
|
|
const int num_bytes = 6 * sizeof(float);
|
|
int64_t dims[] = {2, 3};
|
|
TF_Tensor* t = TF_AllocateTensor(TF_FLOAT, dims, 2, num_bytes);
|
|
EXPECT_EQ(TF_FLOAT, TF_TensorType(t));
|
|
EXPECT_EQ(2, TF_NumDims(t));
|
|
EXPECT_EQ(dims[0], TF_Dim(t, 0));
|
|
EXPECT_EQ(dims[1], TF_Dim(t, 1));
|
|
EXPECT_EQ(num_bytes, TF_TensorByteSize(t));
|
|
EXPECT_EQ(6, TF_TensorElementCount(t));
|
|
TF_DeleteTensor(t);
|
|
}
|
|
|
|
TEST(CAPI, MaybeMove) {
|
|
const int num_bytes = 6 * sizeof(float);
|
|
float* values =
|
|
reinterpret_cast<float*>(tensorflow::cpu_allocator()->AllocateRaw(
|
|
EIGEN_MAX_ALIGN_BYTES, num_bytes));
|
|
int64_t dims[] = {2, 3};
|
|
bool deallocator_called = false;
|
|
TF_Tensor* t = TF_NewTensor(TF_FLOAT, dims, 2, values, num_bytes,
|
|
&Deallocator, &deallocator_called);
|
|
|
|
TF_Tensor* o = TF_TensorMaybeMove(t);
|
|
ASSERT_TRUE(o == nullptr); // It is unsafe to move memory TF might not own.
|
|
TF_DeleteTensor(t);
|
|
EXPECT_TRUE(deallocator_called);
|
|
}
|
|
|
|
TEST(CAPI, LibraryLoadFunctions) {
|
|
// TODO(b/73318067): Fix linking for the GPU test generated by the
|
|
// tf_cuda_cc_test() bazel rule and remove the next line.
|
|
if (!GPUDeviceName().empty()) return;
|
|
|
|
#if !defined(TENSORFLOW_NO_SHARED_OBJECTS)
|
|
{
|
|
// Load the library.
|
|
TF_Status* status = TF_NewStatus();
|
|
TF_Library* lib =
|
|
TF_LoadLibrary("tensorflow/c/test_op1.so", status);
|
|
TF_Code code = TF_GetCode(status);
|
|
string status_msg(TF_Message(status));
|
|
TF_DeleteStatus(status);
|
|
ASSERT_EQ(TF_OK, code) << status_msg;
|
|
|
|
// Test op list.
|
|
TF_Buffer op_list_buf = TF_GetOpList(lib);
|
|
tensorflow::OpList op_list;
|
|
EXPECT_TRUE(op_list.ParseFromArray(op_list_buf.data, op_list_buf.length));
|
|
ASSERT_EQ(op_list.op_size(), 1);
|
|
EXPECT_EQ("TestCApi1", op_list.op(0).name());
|
|
TF_DeleteLibraryHandle(lib);
|
|
}
|
|
#endif // !defined(TENSORFLOW_NO_SHARED_OBJECTS)
|
|
{
|
|
TF_Buffer* op_list_buffer = TF_GetAllOpList();
|
|
tensorflow::OpList op_list;
|
|
op_list.ParseFromArray(op_list_buffer->data, op_list_buffer->length);
|
|
ASSERT_GE(op_list.op_size(), 1);
|
|
typedef tensorflow::protobuf::RepeatedPtrField<tensorflow::OpDef> OpDefs;
|
|
const OpDefs& ops = op_list.op();
|
|
bool found = std::find_if(ops.begin(), ops.end(),
|
|
[](const tensorflow::OpDef& op_def) {
|
|
return op_def.name() == "TestCApi";
|
|
}) != ops.end();
|
|
EXPECT_TRUE(found);
|
|
TF_DeleteBuffer(op_list_buffer);
|
|
}
|
|
}
|
|
|
|
void TestEncodeDecode(int line, const std::vector<string>& data) {
|
|
const tensorflow::int64 n = data.size();
|
|
TF_Status* status = TF_NewStatus();
|
|
for (const std::vector<tensorflow::int64>& dims :
|
|
std::vector<std::vector<tensorflow::int64>>{
|
|
{n}, {1, n}, {n, 1}, {n / 2, 2}}) {
|
|
// Create C++ Tensor
|
|
Tensor src(tensorflow::DT_STRING, TensorShape(dims));
|
|
for (tensorflow::int64 i = 0; i < src.NumElements(); ++i) {
|
|
src.flat<string>()(i) = data[i];
|
|
}
|
|
TF_Tensor* dst = TF_TensorFromTensor(src, status);
|
|
ASSERT_EQ(TF_OK, TF_GetCode(status)) << TF_Message(status);
|
|
|
|
// Convert back to a C++ Tensor and ensure we get expected output.
|
|
Tensor output;
|
|
ASSERT_EQ(Status::OK(), TF_TensorToTensor(dst, &output)) << line;
|
|
ASSERT_EQ(src.NumElements(), output.NumElements()) << line;
|
|
for (tensorflow::int64 i = 0; i < src.NumElements(); ++i) {
|
|
ASSERT_EQ(data[i], output.flat<string>()(i)) << line;
|
|
}
|
|
|
|
TF_DeleteTensor(dst);
|
|
}
|
|
TF_DeleteStatus(status);
|
|
}
|
|
|
|
TEST(CAPI, TensorEncodeDecodeStrings) {
|
|
TestEncodeDecode(__LINE__, {});
|
|
TestEncodeDecode(__LINE__, {"hello"});
|
|
TestEncodeDecode(__LINE__,
|
|
{"the", "quick", "brown", "fox", "jumped", "over"});
|
|
|
|
string big(1000, 'a');
|
|
TestEncodeDecode(__LINE__, {"small", big, "small2"});
|
|
}
|
|
|
|
TEST(CAPI, SessionOptions) {
|
|
TF_SessionOptions* opt = TF_NewSessionOptions();
|
|
TF_DeleteSessionOptions(opt);
|
|
}
|
|
|
|
TEST(CAPI, DeprecatedSession) {
|
|
TF_Status* s = TF_NewStatus();
|
|
TF_SessionOptions* opt = TF_NewSessionOptions();
|
|
TF_DeprecatedSession* session = TF_NewDeprecatedSession(opt, s);
|
|
TF_DeleteSessionOptions(opt);
|
|
ASSERT_EQ(TF_OK, TF_GetCode(s)) << TF_Message(s);
|
|
|
|
TF_Buffer* run_options = TF_NewBufferFromString("", 0);
|
|
TF_Buffer* run_metadata = TF_NewBuffer();
|
|
TF_Run(session, run_options, nullptr, nullptr, 0, nullptr, nullptr, 0,
|
|
nullptr, 0, run_metadata, s);
|
|
EXPECT_EQ(TF_INVALID_ARGUMENT, TF_GetCode(s)) << TF_Message(s);
|
|
EXPECT_EQ("Session was not created with a graph before Run()!",
|
|
string(TF_Message(s)));
|
|
TF_DeleteBuffer(run_metadata);
|
|
TF_DeleteBuffer(run_options);
|
|
|
|
TF_DeleteDeprecatedSession(session, s);
|
|
ASSERT_EQ(TF_OK, TF_GetCode(s)) << TF_Message(s);
|
|
|
|
TF_DeleteStatus(s);
|
|
}
|
|
|
|
TEST(CAPI, DataTypeEnum) {
|
|
EXPECT_EQ(TF_FLOAT, static_cast<TF_DataType>(tensorflow::DT_FLOAT));
|
|
EXPECT_EQ(TF_DOUBLE, static_cast<TF_DataType>(tensorflow::DT_DOUBLE));
|
|
EXPECT_EQ(TF_INT32, static_cast<TF_DataType>(tensorflow::DT_INT32));
|
|
EXPECT_EQ(TF_UINT8, static_cast<TF_DataType>(tensorflow::DT_UINT8));
|
|
EXPECT_EQ(TF_INT16, static_cast<TF_DataType>(tensorflow::DT_INT16));
|
|
EXPECT_EQ(TF_INT8, static_cast<TF_DataType>(tensorflow::DT_INT8));
|
|
EXPECT_EQ(TF_STRING, static_cast<TF_DataType>(tensorflow::DT_STRING));
|
|
EXPECT_EQ(TF_COMPLEX64, static_cast<TF_DataType>(tensorflow::DT_COMPLEX64));
|
|
EXPECT_EQ(TF_COMPLEX, TF_COMPLEX64);
|
|
EXPECT_EQ(TF_INT64, static_cast<TF_DataType>(tensorflow::DT_INT64));
|
|
EXPECT_EQ(TF_BOOL, static_cast<TF_DataType>(tensorflow::DT_BOOL));
|
|
EXPECT_EQ(TF_QINT8, static_cast<TF_DataType>(tensorflow::DT_QINT8));
|
|
EXPECT_EQ(TF_QUINT8, static_cast<TF_DataType>(tensorflow::DT_QUINT8));
|
|
EXPECT_EQ(TF_QINT32, static_cast<TF_DataType>(tensorflow::DT_QINT32));
|
|
EXPECT_EQ(TF_BFLOAT16, static_cast<TF_DataType>(tensorflow::DT_BFLOAT16));
|
|
EXPECT_EQ(TF_QINT16, static_cast<TF_DataType>(tensorflow::DT_QINT16));
|
|
EXPECT_EQ(TF_QUINT16, static_cast<TF_DataType>(tensorflow::DT_QUINT16));
|
|
EXPECT_EQ(TF_UINT16, static_cast<TF_DataType>(tensorflow::DT_UINT16));
|
|
EXPECT_EQ(TF_COMPLEX128, static_cast<TF_DataType>(tensorflow::DT_COMPLEX128));
|
|
EXPECT_EQ(TF_HALF, static_cast<TF_DataType>(tensorflow::DT_HALF));
|
|
EXPECT_EQ(TF_DataTypeSize(TF_DOUBLE),
|
|
tensorflow::DataTypeSize(tensorflow::DT_DOUBLE));
|
|
EXPECT_EQ(TF_DataTypeSize(TF_STRING),
|
|
tensorflow::DataTypeSize(tensorflow::DT_STRING));
|
|
// Test with invalid type; should always return 0 as documented
|
|
EXPECT_EQ(TF_DataTypeSize(static_cast<TF_DataType>(0)), 0);
|
|
}
|
|
|
|
TEST(CAPI, StatusEnum) {
|
|
EXPECT_EQ(TF_OK, static_cast<TF_Code>(tensorflow::error::OK));
|
|
EXPECT_EQ(TF_CANCELLED, static_cast<TF_Code>(tensorflow::error::CANCELLED));
|
|
EXPECT_EQ(TF_UNKNOWN, static_cast<TF_Code>(tensorflow::error::UNKNOWN));
|
|
EXPECT_EQ(TF_INVALID_ARGUMENT,
|
|
static_cast<TF_Code>(tensorflow::error::INVALID_ARGUMENT));
|
|
EXPECT_EQ(TF_DEADLINE_EXCEEDED,
|
|
static_cast<TF_Code>(tensorflow::error::DEADLINE_EXCEEDED));
|
|
EXPECT_EQ(TF_NOT_FOUND, static_cast<TF_Code>(tensorflow::error::NOT_FOUND));
|
|
EXPECT_EQ(TF_ALREADY_EXISTS,
|
|
static_cast<TF_Code>(tensorflow::error::ALREADY_EXISTS));
|
|
EXPECT_EQ(TF_PERMISSION_DENIED,
|
|
static_cast<TF_Code>(tensorflow::error::PERMISSION_DENIED));
|
|
EXPECT_EQ(TF_UNAUTHENTICATED,
|
|
static_cast<TF_Code>(tensorflow::error::UNAUTHENTICATED));
|
|
EXPECT_EQ(TF_RESOURCE_EXHAUSTED,
|
|
static_cast<TF_Code>(tensorflow::error::RESOURCE_EXHAUSTED));
|
|
EXPECT_EQ(TF_FAILED_PRECONDITION,
|
|
static_cast<TF_Code>(tensorflow::error::FAILED_PRECONDITION));
|
|
EXPECT_EQ(TF_ABORTED, static_cast<TF_Code>(tensorflow::error::ABORTED));
|
|
EXPECT_EQ(TF_OUT_OF_RANGE,
|
|
static_cast<TF_Code>(tensorflow::error::OUT_OF_RANGE));
|
|
EXPECT_EQ(TF_UNIMPLEMENTED,
|
|
static_cast<TF_Code>(tensorflow::error::UNIMPLEMENTED));
|
|
EXPECT_EQ(TF_INTERNAL, static_cast<TF_Code>(tensorflow::error::INTERNAL));
|
|
EXPECT_EQ(TF_UNAVAILABLE,
|
|
static_cast<TF_Code>(tensorflow::error::UNAVAILABLE));
|
|
EXPECT_EQ(TF_DATA_LOSS, static_cast<TF_Code>(tensorflow::error::DATA_LOSS));
|
|
}
|
|
|
|
TEST(CAPI, GetAllOpList) {
|
|
TF_Buffer* buf = TF_GetAllOpList();
|
|
tensorflow::OpList op_list;
|
|
EXPECT_TRUE(op_list.ParseFromArray(buf->data, buf->length));
|
|
EXPECT_GT(op_list.op_size(), 0);
|
|
TF_DeleteBuffer(buf);
|
|
}
|
|
|
|
TEST(CAPI, SetShape) {
|
|
TF_Status* s = TF_NewStatus();
|
|
TF_Graph* graph = TF_NewGraph();
|
|
|
|
TF_Operation* feed = Placeholder(graph, s);
|
|
ASSERT_EQ(TF_OK, TF_GetCode(s)) << TF_Message(s);
|
|
TF_Output feed_out_0 = TF_Output{feed, 0};
|
|
int num_dims;
|
|
|
|
// Fetch the shape, it should be completely unknown.
|
|
num_dims = TF_GraphGetTensorNumDims(graph, feed_out_0, s);
|
|
ASSERT_EQ(TF_OK, TF_GetCode(s)) << TF_Message(s);
|
|
EXPECT_EQ(-1, num_dims);
|
|
|
|
// Set the shape to be unknown, expect no change.
|
|
TF_GraphSetTensorShape(graph, feed_out_0, /*dims=*/nullptr, -1, s);
|
|
ASSERT_EQ(TF_OK, TF_GetCode(s)) << TF_Message(s);
|
|
num_dims = TF_GraphGetTensorNumDims(graph, feed_out_0, s);
|
|
ASSERT_EQ(TF_OK, TF_GetCode(s)) << TF_Message(s);
|
|
EXPECT_EQ(-1, num_dims);
|
|
|
|
// Set the shape to be 2 x Unknown
|
|
int64_t dims[] = {2, -1};
|
|
TF_GraphSetTensorShape(graph, feed_out_0, dims, 2, s);
|
|
ASSERT_EQ(TF_OK, TF_GetCode(s)) << TF_Message(s);
|
|
|
|
// Fetch the shape and validate it is 2 by -1.
|
|
num_dims = TF_GraphGetTensorNumDims(graph, feed_out_0, s);
|
|
ASSERT_EQ(TF_OK, TF_GetCode(s)) << TF_Message(s);
|
|
EXPECT_EQ(2, num_dims);
|
|
|
|
// Resize the dimension vector appropriately.
|
|
int64_t returned_dims[2];
|
|
TF_GraphGetTensorShape(graph, feed_out_0, returned_dims, num_dims, s);
|
|
ASSERT_EQ(TF_OK, TF_GetCode(s)) << TF_Message(s);
|
|
EXPECT_EQ(dims[0], returned_dims[0]);
|
|
EXPECT_EQ(dims[1], returned_dims[1]);
|
|
|
|
// Set to a new valid shape: [2, 3]
|
|
dims[1] = 3;
|
|
TF_GraphSetTensorShape(graph, feed_out_0, dims, 2, s);
|
|
EXPECT_EQ(TF_OK, TF_GetCode(s)) << TF_Message(s);
|
|
|
|
// Fetch and see that the new value is returned.
|
|
TF_GraphGetTensorShape(graph, feed_out_0, returned_dims, num_dims, s);
|
|
ASSERT_EQ(TF_OK, TF_GetCode(s)) << TF_Message(s);
|
|
EXPECT_EQ(dims[0], returned_dims[0]);
|
|
EXPECT_EQ(dims[1], returned_dims[1]);
|
|
|
|
// Try to set 'unknown' with unknown rank on the shape and see that
|
|
// it doesn't change.
|
|
TF_GraphSetTensorShape(graph, feed_out_0, /*dims=*/nullptr, -1, s);
|
|
EXPECT_EQ(TF_OK, TF_GetCode(s)) << TF_Message(s);
|
|
TF_GraphGetTensorShape(graph, feed_out_0, returned_dims, num_dims, s);
|
|
ASSERT_EQ(TF_OK, TF_GetCode(s)) << TF_Message(s);
|
|
EXPECT_EQ(2, num_dims);
|
|
EXPECT_EQ(2, returned_dims[0]);
|
|
EXPECT_EQ(3, returned_dims[1]);
|
|
|
|
// Try to set 'unknown' with same rank on the shape and see that
|
|
// it doesn't change.
|
|
dims[0] = -1;
|
|
dims[1] = -1;
|
|
TF_GraphSetTensorShape(graph, feed_out_0, dims, 2, s);
|
|
EXPECT_EQ(TF_OK, TF_GetCode(s)) << TF_Message(s);
|
|
// Fetch and see that the new value is returned.
|
|
TF_GraphGetTensorShape(graph, feed_out_0, returned_dims, num_dims, s);
|
|
ASSERT_EQ(TF_OK, TF_GetCode(s)) << TF_Message(s);
|
|
EXPECT_EQ(2, num_dims);
|
|
EXPECT_EQ(2, returned_dims[0]);
|
|
EXPECT_EQ(3, returned_dims[1]);
|
|
|
|
// Try to fetch a shape with the wrong num_dims
|
|
TF_GraphGetTensorShape(graph, feed_out_0, returned_dims, 5, s);
|
|
EXPECT_EQ(TF_INVALID_ARGUMENT, TF_GetCode(s)) << TF_Message(s);
|
|
|
|
// Try to set an invalid shape (cannot change 2x3 to a 2x5).
|
|
dims[1] = 5;
|
|
TF_GraphSetTensorShape(graph, feed_out_0, dims, 2, s);
|
|
EXPECT_EQ(TF_INVALID_ARGUMENT, TF_GetCode(s)) << TF_Message(s);
|
|
|
|
// Test for a scalar.
|
|
TF_Operation* three = ScalarConst(3, graph, s);
|
|
ASSERT_EQ(TF_OK, TF_GetCode(s)) << TF_Message(s);
|
|
TF_Output three_out_0 = TF_Output{three, 0};
|
|
|
|
num_dims = TF_GraphGetTensorNumDims(graph, three_out_0, s);
|
|
ASSERT_EQ(TF_OK, TF_GetCode(s)) << TF_Message(s);
|
|
EXPECT_EQ(0, num_dims);
|
|
TF_GraphGetTensorShape(graph, three_out_0, returned_dims, num_dims, s);
|
|
ASSERT_EQ(TF_OK, TF_GetCode(s)) << TF_Message(s);
|
|
|
|
// Clean up
|
|
TF_DeleteGraph(graph);
|
|
TF_DeleteStatus(s);
|
|
}
|
|
|
|
TEST(CAPI, Graph) {
|
|
TF_Status* s = TF_NewStatus();
|
|
TF_Graph* graph = TF_NewGraph();
|
|
|
|
// Make a placeholder operation.
|
|
TF_Operation* feed = Placeholder(graph, s);
|
|
ASSERT_EQ(TF_OK, TF_GetCode(s)) << TF_Message(s);
|
|
|
|
// Test TF_Operation*() query functions.
|
|
EXPECT_EQ(string("feed"), string(TF_OperationName(feed)));
|
|
EXPECT_EQ(string("Placeholder"), string(TF_OperationOpType(feed)));
|
|
EXPECT_EQ(string(""), string(TF_OperationDevice(feed)));
|
|
EXPECT_EQ(1, TF_OperationNumOutputs(feed));
|
|
EXPECT_EQ(TF_INT32, TF_OperationOutputType(TF_Output{feed, 0}));
|
|
EXPECT_EQ(1, TF_OperationOutputListLength(feed, "output", s));
|
|
ASSERT_EQ(TF_OK, TF_GetCode(s)) << TF_Message(s);
|
|
EXPECT_EQ(0, TF_OperationNumInputs(feed));
|
|
EXPECT_EQ(0, TF_OperationOutputNumConsumers(TF_Output{feed, 0}));
|
|
EXPECT_EQ(0, TF_OperationNumControlInputs(feed));
|
|
EXPECT_EQ(0, TF_OperationNumControlOutputs(feed));
|
|
|
|
tensorflow::AttrValue attr_value;
|
|
ASSERT_TRUE(GetAttrValue(feed, "dtype", &attr_value, s)) << TF_Message(s);
|
|
EXPECT_EQ(attr_value.type(), tensorflow::DT_INT32);
|
|
|
|
// Test not found errors in TF_Operation*() query functions.
|
|
EXPECT_EQ(-1, TF_OperationOutputListLength(feed, "bogus", s));
|
|
EXPECT_EQ(TF_INVALID_ARGUMENT, TF_GetCode(s));
|
|
|
|
ASSERT_FALSE(GetAttrValue(feed, "missing", &attr_value, s));
|
|
EXPECT_EQ(string("Operation 'feed' has no attr named 'missing'."),
|
|
string(TF_Message(s)));
|
|
|
|
// Make a constant oper with the scalar "3".
|
|
TF_Operation* three = ScalarConst(3, graph, s);
|
|
ASSERT_EQ(TF_OK, TF_GetCode(s)) << TF_Message(s);
|
|
|
|
// Add oper.
|
|
TF_Operation* add = Add(feed, three, graph, s);
|
|
ASSERT_EQ(TF_OK, TF_GetCode(s)) << TF_Message(s);
|
|
|
|
// Test TF_Operation*() query functions.
|
|
EXPECT_EQ(string("add"), string(TF_OperationName(add)));
|
|
EXPECT_EQ(string("AddN"), string(TF_OperationOpType(add)));
|
|
EXPECT_EQ(string(""), string(TF_OperationDevice(add)));
|
|
EXPECT_EQ(1, TF_OperationNumOutputs(add));
|
|
EXPECT_EQ(TF_INT32, TF_OperationOutputType(TF_Output{add, 0}));
|
|
EXPECT_EQ(1, TF_OperationOutputListLength(add, "sum", s));
|
|
ASSERT_EQ(TF_OK, TF_GetCode(s)) << TF_Message(s);
|
|
EXPECT_EQ(2, TF_OperationNumInputs(add));
|
|
EXPECT_EQ(2, TF_OperationInputListLength(add, "inputs", s));
|
|
ASSERT_EQ(TF_OK, TF_GetCode(s)) << TF_Message(s);
|
|
EXPECT_EQ(TF_INT32, TF_OperationInputType(TF_Input{add, 0}));
|
|
EXPECT_EQ(TF_INT32, TF_OperationInputType(TF_Input{add, 1}));
|
|
TF_Output add_in_0 = TF_OperationInput(TF_Input{add, 0});
|
|
EXPECT_EQ(feed, add_in_0.oper);
|
|
EXPECT_EQ(0, add_in_0.index);
|
|
TF_Output add_in_1 = TF_OperationInput(TF_Input{add, 1});
|
|
EXPECT_EQ(three, add_in_1.oper);
|
|
EXPECT_EQ(0, add_in_1.index);
|
|
EXPECT_EQ(0, TF_OperationOutputNumConsumers(TF_Output{add, 0}));
|
|
EXPECT_EQ(0, TF_OperationNumControlInputs(add));
|
|
EXPECT_EQ(0, TF_OperationNumControlOutputs(add));
|
|
|
|
ASSERT_TRUE(GetAttrValue(add, "T", &attr_value, s)) << TF_Message(s);
|
|
EXPECT_EQ(attr_value.type(), tensorflow::DT_INT32);
|
|
ASSERT_TRUE(GetAttrValue(add, "N", &attr_value, s)) << TF_Message(s);
|
|
EXPECT_EQ(attr_value.i(), 2);
|
|
|
|
// Placeholder oper now has a consumer.
|
|
ASSERT_EQ(1, TF_OperationOutputNumConsumers(TF_Output{feed, 0}));
|
|
TF_Input feed_port;
|
|
EXPECT_EQ(1, TF_OperationOutputConsumers(TF_Output{feed, 0}, &feed_port, 1));
|
|
EXPECT_EQ(add, feed_port.oper);
|
|
EXPECT_EQ(0, feed_port.index);
|
|
|
|
// The scalar const oper also has a consumer.
|
|
ASSERT_EQ(1, TF_OperationOutputNumConsumers(TF_Output{three, 0}));
|
|
TF_Input three_port;
|
|
EXPECT_EQ(1,
|
|
TF_OperationOutputConsumers(TF_Output{three, 0}, &three_port, 1));
|
|
EXPECT_EQ(add, three_port.oper);
|
|
EXPECT_EQ(1, three_port.index);
|
|
|
|
// Serialize to GraphDef.
|
|
GraphDef graph_def;
|
|
ASSERT_TRUE(GetGraphDef(graph, &graph_def));
|
|
|
|
// Validate GraphDef is what we expect.
|
|
bool found_placeholder = false;
|
|
bool found_scalar_const = false;
|
|
bool found_add = false;
|
|
for (const auto& n : graph_def.node()) {
|
|
if (IsPlaceholder(n)) {
|
|
EXPECT_FALSE(found_placeholder);
|
|
found_placeholder = true;
|
|
} else if (IsScalarConst(n, 3)) {
|
|
EXPECT_FALSE(found_scalar_const);
|
|
found_scalar_const = true;
|
|
} else if (IsAddN(n, 2)) {
|
|
EXPECT_FALSE(found_add);
|
|
found_add = true;
|
|
} else {
|
|
ADD_FAILURE() << "Unexpected NodeDef: " << ProtoDebugString(n);
|
|
}
|
|
}
|
|
EXPECT_TRUE(found_placeholder);
|
|
EXPECT_TRUE(found_scalar_const);
|
|
EXPECT_TRUE(found_add);
|
|
|
|
// Add another oper to the graph.
|
|
TF_Operation* neg = Neg(add, graph, s);
|
|
ASSERT_EQ(TF_OK, TF_GetCode(s)) << TF_Message(s);
|
|
|
|
// Serialize to NodeDef.
|
|
NodeDef node_def;
|
|
ASSERT_TRUE(GetNodeDef(neg, &node_def));
|
|
|
|
// Validate NodeDef is what we expect.
|
|
EXPECT_TRUE(IsNeg(node_def, "add"));
|
|
|
|
// Serialize to GraphDef.
|
|
GraphDef graph_def2;
|
|
ASSERT_TRUE(GetGraphDef(graph, &graph_def2));
|
|
|
|
// Compare with first GraphDef + added NodeDef.
|
|
NodeDef* added_node = graph_def.add_node();
|
|
*added_node = node_def;
|
|
EXPECT_EQ(ProtoDebugString(graph_def), ProtoDebugString(graph_def2));
|
|
|
|
// Look up some nodes by name.
|
|
TF_Operation* neg2 = TF_GraphOperationByName(graph, "neg");
|
|
EXPECT_TRUE(neg == neg2);
|
|
NodeDef node_def2;
|
|
ASSERT_TRUE(GetNodeDef(neg2, &node_def2));
|
|
EXPECT_EQ(ProtoDebugString(node_def), ProtoDebugString(node_def2));
|
|
|
|
TF_Operation* feed2 = TF_GraphOperationByName(graph, "feed");
|
|
EXPECT_TRUE(feed == feed2);
|
|
ASSERT_TRUE(GetNodeDef(feed, &node_def));
|
|
ASSERT_TRUE(GetNodeDef(feed2, &node_def2));
|
|
EXPECT_EQ(ProtoDebugString(node_def), ProtoDebugString(node_def2));
|
|
|
|
// Test iterating through the nodes of a graph.
|
|
found_placeholder = false;
|
|
found_scalar_const = false;
|
|
found_add = false;
|
|
bool found_neg = false;
|
|
size_t pos = 0;
|
|
TF_Operation* oper;
|
|
while ((oper = TF_GraphNextOperation(graph, &pos)) != nullptr) {
|
|
if (oper == feed) {
|
|
EXPECT_FALSE(found_placeholder);
|
|
found_placeholder = true;
|
|
} else if (oper == three) {
|
|
EXPECT_FALSE(found_scalar_const);
|
|
found_scalar_const = true;
|
|
} else if (oper == add) {
|
|
EXPECT_FALSE(found_add);
|
|
found_add = true;
|
|
} else if (oper == neg) {
|
|
EXPECT_FALSE(found_neg);
|
|
found_neg = true;
|
|
} else {
|
|
ASSERT_TRUE(GetNodeDef(oper, &node_def));
|
|
ADD_FAILURE() << "Unexpected Node: " << ProtoDebugString(node_def);
|
|
}
|
|
}
|
|
EXPECT_TRUE(found_placeholder);
|
|
EXPECT_TRUE(found_scalar_const);
|
|
EXPECT_TRUE(found_add);
|
|
EXPECT_TRUE(found_neg);
|
|
|
|
// Clean up
|
|
TF_DeleteGraph(graph);
|
|
TF_DeleteStatus(s);
|
|
}
|
|
|
|
/*
|
|
TODO(skyewm): this test currently DCHECKs, change to bad status
|
|
|
|
TEST(CAPI, InputFromDifferentGraphError) {
|
|
TF_Status* s = TF_NewStatus();
|
|
TF_Graph* g1 = TF_NewGraph();
|
|
TF_Graph* g2 = TF_NewGraph();
|
|
|
|
TF_Operation* feed = Placeholder(g1, s);
|
|
ASSERT_EQ(TF_OK, TF_GetCode(s)) << TF_Message(s);
|
|
|
|
// Attempt to create node in g2 with input from g1
|
|
Neg(feed, g2, s);
|
|
EXPECT_EQ(TF_INVALID_ARGUMENT, TF_GetCode(s));
|
|
EXPECT_STREQ("foo", TF_Message(s));
|
|
|
|
TF_DeleteGraph(g1);
|
|
TF_DeleteGraph(g2);
|
|
TF_DeleteStatus(s);
|
|
}
|
|
*/
|
|
|
|
TEST(CAPI, ImportGraphDef) {
|
|
TF_Status* s = TF_NewStatus();
|
|
TF_Graph* graph = TF_NewGraph();
|
|
|
|
// Create a simple graph.
|
|
Placeholder(graph, s);
|
|
ASSERT_EQ(TF_OK, TF_GetCode(s)) << TF_Message(s);
|
|
ASSERT_TRUE(TF_GraphOperationByName(graph, "feed") != nullptr);
|
|
TF_Operation* oper = ScalarConst(3, graph, s);
|
|
ASSERT_EQ(TF_OK, TF_GetCode(s)) << TF_Message(s);
|
|
ASSERT_TRUE(TF_GraphOperationByName(graph, "scalar") != nullptr);
|
|
Neg(oper, graph, s);
|
|
ASSERT_EQ(TF_OK, TF_GetCode(s)) << TF_Message(s);
|
|
ASSERT_TRUE(TF_GraphOperationByName(graph, "neg") != nullptr);
|
|
|
|
// Export to a GraphDef.
|
|
TF_Buffer* graph_def = TF_NewBuffer();
|
|
TF_GraphToGraphDef(graph, graph_def, s);
|
|
ASSERT_EQ(TF_OK, TF_GetCode(s)) << TF_Message(s);
|
|
|
|
// Import it, with a prefix, in a fresh graph.
|
|
TF_DeleteGraph(graph);
|
|
graph = TF_NewGraph();
|
|
TF_ImportGraphDefOptions* opts = TF_NewImportGraphDefOptions();
|
|
TF_ImportGraphDefOptionsSetPrefix(opts, "imported");
|
|
TF_GraphImportGraphDef(graph, graph_def, opts, s);
|
|
ASSERT_EQ(TF_OK, TF_GetCode(s)) << TF_Message(s);
|
|
|
|
TF_Operation* scalar = TF_GraphOperationByName(graph, "imported/scalar");
|
|
TF_Operation* feed = TF_GraphOperationByName(graph, "imported/feed");
|
|
TF_Operation* neg = TF_GraphOperationByName(graph, "imported/neg");
|
|
ASSERT_TRUE(scalar != nullptr);
|
|
ASSERT_TRUE(feed != nullptr);
|
|
ASSERT_TRUE(neg != nullptr);
|
|
|
|
// Test basic structure of the imported graph.
|
|
EXPECT_EQ(0, TF_OperationNumInputs(scalar));
|
|
EXPECT_EQ(0, TF_OperationNumInputs(feed));
|
|
ASSERT_EQ(1, TF_OperationNumInputs(neg));
|
|
TF_Output neg_input = TF_OperationInput({neg, 0});
|
|
EXPECT_EQ(scalar, neg_input.oper);
|
|
EXPECT_EQ(0, neg_input.index);
|
|
|
|
// Test that we can't see control edges involving the source and sink nodes.
|
|
TF_Operation* control_ops[100];
|
|
EXPECT_EQ(0, TF_OperationNumControlInputs(scalar));
|
|
EXPECT_EQ(0, TF_OperationGetControlInputs(scalar, control_ops, 100));
|
|
EXPECT_EQ(0, TF_OperationNumControlOutputs(scalar));
|
|
EXPECT_EQ(0, TF_OperationGetControlOutputs(scalar, control_ops, 100));
|
|
|
|
EXPECT_EQ(0, TF_OperationNumControlInputs(feed));
|
|
EXPECT_EQ(0, TF_OperationGetControlInputs(feed, control_ops, 100));
|
|
EXPECT_EQ(0, TF_OperationNumControlOutputs(feed));
|
|
EXPECT_EQ(0, TF_OperationGetControlOutputs(feed, control_ops, 100));
|
|
|
|
EXPECT_EQ(0, TF_OperationNumControlInputs(neg));
|
|
EXPECT_EQ(0, TF_OperationGetControlInputs(neg, control_ops, 100));
|
|
EXPECT_EQ(0, TF_OperationNumControlOutputs(neg));
|
|
EXPECT_EQ(0, TF_OperationGetControlOutputs(neg, control_ops, 100));
|
|
|
|
// Import it again, with an input mapping, return outputs, and a return
|
|
// operation, into the same graph.
|
|
TF_DeleteImportGraphDefOptions(opts);
|
|
opts = TF_NewImportGraphDefOptions();
|
|
TF_ImportGraphDefOptionsSetPrefix(opts, "imported2");
|
|
TF_ImportGraphDefOptionsAddInputMapping(opts, "scalar", 0, {scalar, 0});
|
|
TF_ImportGraphDefOptionsAddReturnOutput(opts, "feed", 0);
|
|
TF_ImportGraphDefOptionsAddReturnOutput(opts, "scalar", 0);
|
|
EXPECT_EQ(2, TF_ImportGraphDefOptionsNumReturnOutputs(opts));
|
|
TF_ImportGraphDefOptionsAddReturnOperation(opts, "scalar");
|
|
EXPECT_EQ(1, TF_ImportGraphDefOptionsNumReturnOperations(opts));
|
|
TF_ImportGraphDefResults* results =
|
|
TF_GraphImportGraphDefWithResults(graph, graph_def, opts, s);
|
|
ASSERT_EQ(TF_OK, TF_GetCode(s)) << TF_Message(s);
|
|
|
|
TF_Operation* scalar2 = TF_GraphOperationByName(graph, "imported2/scalar");
|
|
TF_Operation* feed2 = TF_GraphOperationByName(graph, "imported2/feed");
|
|
TF_Operation* neg2 = TF_GraphOperationByName(graph, "imported2/neg");
|
|
ASSERT_TRUE(scalar2 != nullptr);
|
|
ASSERT_TRUE(feed2 != nullptr);
|
|
ASSERT_TRUE(neg2 != nullptr);
|
|
|
|
// Check input mapping
|
|
neg_input = TF_OperationInput({neg, 0});
|
|
EXPECT_EQ(scalar, neg_input.oper);
|
|
EXPECT_EQ(0, neg_input.index);
|
|
|
|
// Check return outputs
|
|
TF_Output* return_outputs;
|
|
int num_return_outputs;
|
|
TF_ImportGraphDefResultsReturnOutputs(results, &num_return_outputs,
|
|
&return_outputs);
|
|
ASSERT_EQ(2, num_return_outputs);
|
|
EXPECT_EQ(feed2, return_outputs[0].oper);
|
|
EXPECT_EQ(0, return_outputs[0].index);
|
|
EXPECT_EQ(scalar, return_outputs[1].oper); // remapped
|
|
EXPECT_EQ(0, return_outputs[1].index);
|
|
|
|
// Check return operation
|
|
TF_Operation** return_opers;
|
|
int num_return_opers;
|
|
TF_ImportGraphDefResultsReturnOperations(results, &num_return_opers,
|
|
&return_opers);
|
|
ASSERT_EQ(1, num_return_opers);
|
|
EXPECT_EQ(scalar2, return_opers[0]); // not remapped
|
|
|
|
TF_DeleteImportGraphDefResults(results);
|
|
|
|
// Import again, with control dependencies, into the same graph.
|
|
TF_DeleteImportGraphDefOptions(opts);
|
|
opts = TF_NewImportGraphDefOptions();
|
|
TF_ImportGraphDefOptionsSetPrefix(opts, "imported3");
|
|
TF_ImportGraphDefOptionsAddControlDependency(opts, feed);
|
|
TF_ImportGraphDefOptionsAddControlDependency(opts, feed2);
|
|
TF_GraphImportGraphDef(graph, graph_def, opts, s);
|
|
ASSERT_EQ(TF_OK, TF_GetCode(s)) << TF_Message(s);
|
|
|
|
TF_Operation* scalar3 = TF_GraphOperationByName(graph, "imported3/scalar");
|
|
TF_Operation* feed3 = TF_GraphOperationByName(graph, "imported3/feed");
|
|
TF_Operation* neg3 = TF_GraphOperationByName(graph, "imported3/neg");
|
|
ASSERT_TRUE(scalar3 != nullptr);
|
|
ASSERT_TRUE(feed3 != nullptr);
|
|
ASSERT_TRUE(neg3 != nullptr);
|
|
|
|
// Check that newly-imported scalar and feed have control deps (neg3 will
|
|
// inherit them from input)
|
|
TF_Operation* control_inputs[100];
|
|
int num_control_inputs = TF_OperationGetControlInputs(
|
|
scalar3, control_inputs, TF_OperationNumControlInputs(scalar3));
|
|
ASSERT_EQ(2, num_control_inputs);
|
|
EXPECT_EQ(feed, control_inputs[0]);
|
|
EXPECT_EQ(feed2, control_inputs[1]);
|
|
|
|
num_control_inputs = TF_OperationGetControlInputs(
|
|
feed3, control_inputs, TF_OperationNumControlInputs(feed3));
|
|
ASSERT_EQ(2, num_control_inputs);
|
|
EXPECT_EQ(feed, control_inputs[0]);
|
|
EXPECT_EQ(feed2, control_inputs[1]);
|
|
|
|
// Export to a graph def so we can import a graph with control dependencies
|
|
TF_DeleteBuffer(graph_def);
|
|
graph_def = TF_NewBuffer();
|
|
TF_GraphToGraphDef(graph, graph_def, s);
|
|
ASSERT_EQ(TF_OK, TF_GetCode(s)) << TF_Message(s);
|
|
|
|
// Import again, with remapped control dependency, into the same graph
|
|
TF_DeleteImportGraphDefOptions(opts);
|
|
opts = TF_NewImportGraphDefOptions();
|
|
TF_ImportGraphDefOptionsSetPrefix(opts, "imported4");
|
|
TF_ImportGraphDefOptionsRemapControlDependency(opts, "imported/feed", feed);
|
|
TF_GraphImportGraphDef(graph, graph_def, opts, s);
|
|
ASSERT_EQ(TF_OK, TF_GetCode(s)) << TF_Message(s);
|
|
|
|
TF_Operation* scalar4 =
|
|
TF_GraphOperationByName(graph, "imported4/imported3/scalar");
|
|
TF_Operation* feed4 =
|
|
TF_GraphOperationByName(graph, "imported4/imported2/feed");
|
|
|
|
// Check that imported `imported3/scalar` has remapped control dep from
|
|
// original graph and imported control dep
|
|
num_control_inputs = TF_OperationGetControlInputs(
|
|
scalar4, control_inputs, TF_OperationNumControlInputs(scalar4));
|
|
ASSERT_EQ(2, num_control_inputs);
|
|
EXPECT_EQ(feed, control_inputs[0]);
|
|
EXPECT_EQ(feed4, control_inputs[1]);
|
|
|
|
TF_DeleteImportGraphDefOptions(opts);
|
|
TF_DeleteBuffer(graph_def);
|
|
|
|
// Can add nodes to the imported graph without trouble.
|
|
Add(feed, scalar, graph, s);
|
|
ASSERT_EQ(TF_OK, TF_GetCode(s)) << TF_Message(s);
|
|
|
|
TF_DeleteGraph(graph);
|
|
TF_DeleteStatus(s);
|
|
}
|
|
|
|
TEST(CAPI, ImportGraphDef_WithReturnOutputs) {
|
|
TF_Status* s = TF_NewStatus();
|
|
TF_Graph* graph = TF_NewGraph();
|
|
|
|
// Create a graph with two nodes: x and 3
|
|
Placeholder(graph, s);
|
|
ASSERT_EQ(TF_OK, TF_GetCode(s)) << TF_Message(s);
|
|
ASSERT_TRUE(TF_GraphOperationByName(graph, "feed") != nullptr);
|
|
TF_Operation* oper = ScalarConst(3, graph, s);
|
|
ASSERT_EQ(TF_OK, TF_GetCode(s)) << TF_Message(s);
|
|
ASSERT_TRUE(TF_GraphOperationByName(graph, "scalar") != nullptr);
|
|
Neg(oper, graph, s);
|
|
ASSERT_EQ(TF_OK, TF_GetCode(s)) << TF_Message(s);
|
|
ASSERT_TRUE(TF_GraphOperationByName(graph, "neg") != nullptr);
|
|
|
|
// Export to a GraphDef.
|
|
TF_Buffer* graph_def = TF_NewBuffer();
|
|
TF_GraphToGraphDef(graph, graph_def, s);
|
|
ASSERT_EQ(TF_OK, TF_GetCode(s)) << TF_Message(s);
|
|
|
|
// Import it in a fresh graph with return outputs.
|
|
TF_DeleteGraph(graph);
|
|
graph = TF_NewGraph();
|
|
TF_ImportGraphDefOptions* opts = TF_NewImportGraphDefOptions();
|
|
TF_ImportGraphDefOptionsAddReturnOutput(opts, "feed", 0);
|
|
TF_ImportGraphDefOptionsAddReturnOutput(opts, "scalar", 0);
|
|
EXPECT_EQ(2, TF_ImportGraphDefOptionsNumReturnOutputs(opts));
|
|
TF_Output return_outputs[2];
|
|
TF_GraphImportGraphDefWithReturnOutputs(graph, graph_def, opts,
|
|
return_outputs, 2, s);
|
|
ASSERT_EQ(TF_OK, TF_GetCode(s)) << TF_Message(s);
|
|
|
|
TF_Operation* scalar = TF_GraphOperationByName(graph, "scalar");
|
|
TF_Operation* feed = TF_GraphOperationByName(graph, "feed");
|
|
TF_Operation* neg = TF_GraphOperationByName(graph, "neg");
|
|
ASSERT_TRUE(scalar != nullptr);
|
|
ASSERT_TRUE(feed != nullptr);
|
|
ASSERT_TRUE(neg != nullptr);
|
|
|
|
// Check return outputs
|
|
EXPECT_EQ(feed, return_outputs[0].oper);
|
|
EXPECT_EQ(0, return_outputs[0].index);
|
|
EXPECT_EQ(scalar, return_outputs[1].oper);
|
|
EXPECT_EQ(0, return_outputs[1].index);
|
|
|
|
TF_DeleteImportGraphDefOptions(opts);
|
|
TF_DeleteBuffer(graph_def);
|
|
TF_DeleteGraph(graph);
|
|
TF_DeleteStatus(s);
|
|
}
|
|
|
|
TEST(CAPI, ImportGraphDef_MissingUnusedInputMappings) {
|
|
TF_Status* s = TF_NewStatus();
|
|
TF_Graph* graph = TF_NewGraph();
|
|
|
|
// Create a graph with two nodes: x and 3
|
|
Placeholder(graph, s);
|
|
ASSERT_EQ(TF_OK, TF_GetCode(s)) << TF_Message(s);
|
|
ASSERT_TRUE(TF_GraphOperationByName(graph, "feed") != nullptr);
|
|
TF_Operation* oper = ScalarConst(3, graph, s);
|
|
ASSERT_EQ(TF_OK, TF_GetCode(s)) << TF_Message(s);
|
|
ASSERT_TRUE(TF_GraphOperationByName(graph, "scalar") != nullptr);
|
|
Neg(oper, graph, s);
|
|
ASSERT_EQ(TF_OK, TF_GetCode(s)) << TF_Message(s);
|
|
ASSERT_TRUE(TF_GraphOperationByName(graph, "neg") != nullptr);
|
|
|
|
// Export to a GraphDef.
|
|
TF_Buffer* graph_def = TF_NewBuffer();
|
|
TF_GraphToGraphDef(graph, graph_def, s);
|
|
ASSERT_EQ(TF_OK, TF_GetCode(s)) << TF_Message(s);
|
|
|
|
// Import it in a fresh graph.
|
|
TF_DeleteGraph(graph);
|
|
graph = TF_NewGraph();
|
|
TF_ImportGraphDefOptions* opts = TF_NewImportGraphDefOptions();
|
|
TF_GraphImportGraphDef(graph, graph_def, opts, s);
|
|
ASSERT_EQ(TF_OK, TF_GetCode(s)) << TF_Message(s);
|
|
|
|
TF_Operation* scalar = TF_GraphOperationByName(graph, "scalar");
|
|
|
|
// Import it in a fresh graph with an unused input mapping.
|
|
TF_DeleteImportGraphDefOptions(opts);
|
|
opts = TF_NewImportGraphDefOptions();
|
|
TF_ImportGraphDefOptionsSetPrefix(opts, "imported");
|
|
TF_ImportGraphDefOptionsAddInputMapping(opts, "scalar", 0, {scalar, 0});
|
|
TF_ImportGraphDefOptionsAddInputMapping(opts, "fake", 0, {scalar, 0});
|
|
TF_ImportGraphDefResults* results =
|
|
TF_GraphImportGraphDefWithResults(graph, graph_def, opts, s);
|
|
ASSERT_EQ(TF_OK, TF_GetCode(s)) << TF_Message(s);
|
|
|
|
// Check unused input mappings
|
|
int num_unused_input_mappings;
|
|
const char** src_names;
|
|
int* src_indexes;
|
|
TF_ImportGraphDefResultsMissingUnusedInputMappings(
|
|
results, &num_unused_input_mappings, &src_names, &src_indexes);
|
|
ASSERT_EQ(1, num_unused_input_mappings);
|
|
EXPECT_EQ(string("fake"), string(src_names[0]));
|
|
EXPECT_EQ(0, src_indexes[0]);
|
|
|
|
TF_DeleteImportGraphDefResults(results);
|
|
TF_DeleteImportGraphDefOptions(opts);
|
|
TF_DeleteBuffer(graph_def);
|
|
TF_DeleteGraph(graph);
|
|
TF_DeleteStatus(s);
|
|
}
|
|
|
|
TEST(CAPI, Session) {
|
|
TF_Status* s = TF_NewStatus();
|
|
TF_Graph* graph = TF_NewGraph();
|
|
|
|
// Make a placeholder operation.
|
|
TF_Operation* feed = Placeholder(graph, s);
|
|
ASSERT_EQ(TF_OK, TF_GetCode(s)) << TF_Message(s);
|
|
|
|
// Make a constant operation with the scalar "2".
|
|
TF_Operation* two = ScalarConst(2, graph, s);
|
|
ASSERT_EQ(TF_OK, TF_GetCode(s)) << TF_Message(s);
|
|
|
|
// Add operation.
|
|
TF_Operation* add = Add(feed, two, graph, s);
|
|
ASSERT_EQ(TF_OK, TF_GetCode(s)) << TF_Message(s);
|
|
|
|
// Create a session for this graph.
|
|
CSession csession(graph, s);
|
|
ASSERT_EQ(TF_OK, TF_GetCode(s)) << TF_Message(s);
|
|
|
|
// Run the graph.
|
|
csession.SetInputs({{feed, Int32Tensor(3)}});
|
|
csession.SetOutputs({add});
|
|
csession.Run(s);
|
|
ASSERT_EQ(TF_OK, TF_GetCode(s)) << TF_Message(s);
|
|
TF_Tensor* out = csession.output_tensor(0);
|
|
ASSERT_TRUE(out != nullptr);
|
|
EXPECT_EQ(TF_INT32, TF_TensorType(out));
|
|
EXPECT_EQ(0, TF_NumDims(out)); // scalar
|
|
ASSERT_EQ(sizeof(int32), TF_TensorByteSize(out));
|
|
int32* output_contents = static_cast<int32*>(TF_TensorData(out));
|
|
EXPECT_EQ(3 + 2, *output_contents);
|
|
|
|
// Add another operation to the graph.
|
|
TF_Operation* neg = Neg(add, graph, s);
|
|
ASSERT_EQ(TF_OK, TF_GetCode(s)) << TF_Message(s);
|
|
|
|
// Run up to the new operation.
|
|
csession.SetInputs({{feed, Int32Tensor(7)}});
|
|
csession.SetOutputs({neg});
|
|
csession.Run(s);
|
|
ASSERT_EQ(TF_OK, TF_GetCode(s)) << TF_Message(s);
|
|
out = csession.output_tensor(0);
|
|
ASSERT_TRUE(out != nullptr);
|
|
EXPECT_EQ(TF_INT32, TF_TensorType(out));
|
|
EXPECT_EQ(0, TF_NumDims(out)); // scalar
|
|
ASSERT_EQ(sizeof(int32), TF_TensorByteSize(out));
|
|
output_contents = static_cast<int32*>(TF_TensorData(out));
|
|
EXPECT_EQ(-(7 + 2), *output_contents);
|
|
|
|
// Clean up
|
|
csession.CloseAndDelete(s);
|
|
ASSERT_EQ(TF_OK, TF_GetCode(s)) << TF_Message(s);
|
|
TF_DeleteGraph(graph);
|
|
TF_DeleteStatus(s);
|
|
}
|
|
|
|
// If `device` is non-empty, run Min op on that device.
|
|
// Otherwise run it on the default device (CPU).
|
|
void RunMinTest(const string& device, bool use_XLA) {
|
|
TF_Status* s = TF_NewStatus();
|
|
TF_Graph* graph = TF_NewGraph();
|
|
|
|
// Make a placeholder operation.
|
|
TF_Operation* feed = Placeholder(graph, s);
|
|
ASSERT_EQ(TF_OK, TF_GetCode(s)) << TF_Message(s);
|
|
|
|
// Make a constant operation with the scalar "0", for axis.
|
|
TF_Operation* one = ScalarConst(0, graph, s);
|
|
ASSERT_EQ(TF_OK, TF_GetCode(s)) << TF_Message(s);
|
|
|
|
// Create a session for this graph.
|
|
CSession csession(graph, s, use_XLA);
|
|
ASSERT_EQ(TF_OK, TF_GetCode(s)) << TF_Message(s);
|
|
|
|
if (!device.empty()) {
|
|
LOG(INFO) << "Setting op Min on device " << device;
|
|
}
|
|
TF_Operation* min = MinWithDevice(feed, one, graph, device, s);
|
|
ASSERT_EQ(TF_OK, TF_GetCode(s)) << TF_Message(s);
|
|
|
|
// Run the graph.
|
|
csession.SetInputs({{feed, Int32Tensor({3, 2, 5})}});
|
|
csession.SetOutputs({min});
|
|
csession.Run(s);
|
|
ASSERT_EQ(TF_OK, TF_GetCode(s)) << TF_Message(s);
|
|
TF_Tensor* out = csession.output_tensor(0);
|
|
ASSERT_TRUE(out != nullptr);
|
|
EXPECT_EQ(TF_INT32, TF_TensorType(out));
|
|
EXPECT_EQ(0, TF_NumDims(out)); // scalar
|
|
ASSERT_EQ(sizeof(int32), TF_TensorByteSize(out));
|
|
int32* output_contents = static_cast<int32*>(TF_TensorData(out));
|
|
EXPECT_EQ(2, *output_contents);
|
|
|
|
// Clean up
|
|
csession.CloseAndDelete(s);
|
|
ASSERT_EQ(TF_OK, TF_GetCode(s)) << TF_Message(s);
|
|
TF_DeleteGraph(graph);
|
|
TF_DeleteStatus(s);
|
|
}
|
|
|
|
TEST(CAPI, Session_Min_CPU) { RunMinTest(/*device=*/"", /*use_XLA=*/false); }
|
|
|
|
TEST(CAPI, Session_Min_XLA_CPU) { RunMinTest(/*device=*/"", /*use_XLA=*/true); }
|
|
|
|
TEST(CAPI, Session_Min_GPU) {
|
|
const string gpu_device = GPUDeviceName();
|
|
// Skip this test if no GPU is available.
|
|
if (gpu_device.empty()) return;
|
|
|
|
RunMinTest(gpu_device, /*use_XLA=*/false);
|
|
}
|
|
|
|
TEST(CAPI, Session_Min_XLA_GPU) {
|
|
const string gpu_device = GPUDeviceName();
|
|
// Skip this test if no GPU is available.
|
|
if (gpu_device.empty()) return;
|
|
|
|
RunMinTest(gpu_device, /*use_XLA=*/true);
|
|
}
|
|
|
|
TEST(CAPI, SessionPRun) {
|
|
TF_Status* s = TF_NewStatus();
|
|
TF_Graph* graph = TF_NewGraph();
|
|
|
|
// Construct the graph: A + 2 + B
|
|
TF_Operation* a = Placeholder(graph, s, "A");
|
|
ASSERT_EQ(TF_OK, TF_GetCode(s)) << TF_Message(s);
|
|
|
|
TF_Operation* b = Placeholder(graph, s, "B");
|
|
ASSERT_EQ(TF_OK, TF_GetCode(s)) << TF_Message(s);
|
|
|
|
TF_Operation* two = ScalarConst(2, graph, s);
|
|
ASSERT_EQ(TF_OK, TF_GetCode(s)) << TF_Message(s);
|
|
|
|
TF_Operation* plus2 = Add(a, two, graph, s, "plus2");
|
|
ASSERT_EQ(TF_OK, TF_GetCode(s)) << TF_Message(s);
|
|
|
|
TF_Operation* plusB = Add(plus2, b, graph, s, "plusB");
|
|
ASSERT_EQ(TF_OK, TF_GetCode(s)) << TF_Message(s);
|
|
|
|
// Setup a session and a partial run handle. The partial run will allow
|
|
// computation of A + 2 + B in two phases (calls to TF_SessionPRun):
|
|
// 1. Feed A and get (A+2)
|
|
// 2. Feed B and get (A+2)+B
|
|
TF_SessionOptions* opts = TF_NewSessionOptions();
|
|
TF_Session* sess = TF_NewSession(graph, opts, s);
|
|
TF_DeleteSessionOptions(opts);
|
|
|
|
TF_Output feeds[] = {TF_Output{a, 0}, TF_Output{b, 0}};
|
|
TF_Output fetches[] = {TF_Output{plus2, 0}, TF_Output{plusB, 0}};
|
|
|
|
const char* handle = nullptr;
|
|
TF_SessionPRunSetup(sess, feeds, TF_ARRAYSIZE(feeds), fetches,
|
|
TF_ARRAYSIZE(fetches), nullptr, 0, &handle, s);
|
|
ASSERT_EQ(TF_OK, TF_GetCode(s)) << TF_Message(s);
|
|
|
|
// Feed A and fetch A + 2.
|
|
TF_Output feeds1[] = {TF_Output{a, 0}};
|
|
TF_Output fetches1[] = {TF_Output{plus2, 0}};
|
|
TF_Tensor* feedValues1[] = {Int32Tensor(1)};
|
|
TF_Tensor* fetchValues1[1];
|
|
TF_SessionPRun(sess, handle, feeds1, feedValues1, 1, fetches1, fetchValues1,
|
|
1, nullptr, 0, s);
|
|
ASSERT_EQ(TF_OK, TF_GetCode(s)) << TF_Message(s);
|
|
EXPECT_EQ(3, *(static_cast<int32*>(TF_TensorData(fetchValues1[0]))));
|
|
TF_DeleteTensor(feedValues1[0]);
|
|
TF_DeleteTensor(fetchValues1[0]);
|
|
|
|
// Feed B and fetch (A + 2) + B.
|
|
TF_Output feeds2[] = {TF_Output{b, 0}};
|
|
TF_Output fetches2[] = {TF_Output{plusB, 0}};
|
|
TF_Tensor* feedValues2[] = {Int32Tensor(4)};
|
|
TF_Tensor* fetchValues2[1];
|
|
TF_SessionPRun(sess, handle, feeds2, feedValues2, 1, fetches2, fetchValues2,
|
|
1, nullptr, 0, s);
|
|
ASSERT_EQ(TF_OK, TF_GetCode(s)) << TF_Message(s);
|
|
EXPECT_EQ(7, *(static_cast<int32*>(TF_TensorData(fetchValues2[0]))));
|
|
TF_DeleteTensor(feedValues2[0]);
|
|
TF_DeleteTensor(fetchValues2[0]);
|
|
|
|
// Clean up.
|
|
TF_DeletePRunHandle(handle);
|
|
TF_DeleteSession(sess, s);
|
|
ASSERT_EQ(TF_OK, TF_GetCode(s)) << TF_Message(s);
|
|
TF_DeleteGraph(graph);
|
|
TF_DeleteStatus(s);
|
|
}
|
|
|
|
TEST(CAPI, ShapeInferenceError) {
|
|
// TF_FinishOperation should fail if the shape of the added operation cannot
|
|
// be inferred.
|
|
TF_Status* status = TF_NewStatus();
|
|
TF_Graph* graph = TF_NewGraph();
|
|
|
|
// Create this failure by trying to add two nodes with incompatible shapes
|
|
// (A tensor with shape [2] and a tensor with shape [3] cannot be added).
|
|
const char data[] = {1, 2, 3};
|
|
const int64_t vec2_dims[] = {2};
|
|
unique_tensor_ptr vec2_tensor(
|
|
Int8Tensor(vec2_dims, TF_ARRAYSIZE(vec2_dims), data), TF_DeleteTensor);
|
|
TF_Operation* vec2 = Const(vec2_tensor.get(), graph, status, "vec2");
|
|
ASSERT_EQ(TF_OK, TF_GetCode(status)) << TF_Message(status);
|
|
|
|
const int64_t vec3_dims[] = {3};
|
|
unique_tensor_ptr vec3_tensor(
|
|
Int8Tensor(vec3_dims, TF_ARRAYSIZE(vec3_dims), data), TF_DeleteTensor);
|
|
TF_Operation* vec3 = Const(vec3_tensor.get(), graph, status, "vec3");
|
|
ASSERT_EQ(TF_OK, TF_GetCode(status)) << TF_Message(status);
|
|
|
|
TF_Operation* add = AddNoCheck(vec2, vec3, graph, status);
|
|
ASSERT_NE(TF_OK, TF_GetCode(status));
|
|
ASSERT_TRUE(add == nullptr);
|
|
|
|
TF_DeleteGraph(graph);
|
|
TF_DeleteStatus(status);
|
|
}
|
|
|
|
TEST(CAPI, GetOpDef) {
|
|
TF_Status* status = TF_NewStatus();
|
|
TF_Graph* graph = TF_NewGraph();
|
|
TF_Buffer* buffer = TF_NewBuffer();
|
|
|
|
TF_GraphGetOpDef(graph, "Add", buffer, status);
|
|
ASSERT_EQ(TF_OK, TF_GetCode(status));
|
|
const OpDef* expected_op_def;
|
|
TF_ASSERT_OK(OpRegistry::Global()->LookUpOpDef("Add", &expected_op_def));
|
|
string expected_serialized;
|
|
expected_op_def->SerializeToString(&expected_serialized);
|
|
string actual_string(reinterpret_cast<const char*>(buffer->data),
|
|
buffer->length);
|
|
EXPECT_EQ(expected_serialized, actual_string);
|
|
|
|
TF_GraphGetOpDef(graph, "MyFakeOp", buffer, status);
|
|
EXPECT_EQ(TF_NOT_FOUND, TF_GetCode(status));
|
|
ExpectHasSubstr(TF_Message(status),
|
|
"Op type not registered 'MyFakeOp' in binary");
|
|
|
|
TF_DeleteBuffer(buffer);
|
|
TF_DeleteGraph(graph);
|
|
TF_DeleteStatus(status);
|
|
}
|
|
|
|
void StringVectorToArrays(const std::vector<string>& v,
|
|
std::unique_ptr<const void*[]>* ptrs,
|
|
std::unique_ptr<size_t[]>* lens) {
|
|
ptrs->reset(new const void*[v.size()]);
|
|
lens->reset(new size_t[v.size()]);
|
|
for (size_t i = 0; i < v.size(); ++i) {
|
|
(*ptrs)[i] = v[i].data();
|
|
(*lens)[i] = v[i].size();
|
|
}
|
|
}
|
|
|
|
class CApiColocationTest : public ::testing::Test {
|
|
protected:
|
|
CApiColocationTest() : s_(TF_NewStatus()), graph_(TF_NewGraph()) {}
|
|
|
|
void SetUp() override {
|
|
feed1_ = Placeholder(graph_, s_, "feed1");
|
|
ASSERT_EQ(TF_OK, TF_GetCode(s_)) << TF_Message(s_);
|
|
|
|
feed2_ = Placeholder(graph_, s_, "feed2");
|
|
ASSERT_EQ(TF_OK, TF_GetCode(s_)) << TF_Message(s_);
|
|
|
|
constant_ = ScalarConst(10, graph_, s_);
|
|
ASSERT_EQ(TF_OK, TF_GetCode(s_)) << TF_Message(s_);
|
|
|
|
desc_ = TF_NewOperation(graph_, "AddN", "add");
|
|
TF_Output inputs[] = {{feed1_, 0}, {constant_, 0}};
|
|
TF_AddInputList(desc_, inputs, TF_ARRAYSIZE(inputs));
|
|
}
|
|
|
|
~CApiColocationTest() override {
|
|
TF_DeleteGraph(graph_);
|
|
TF_DeleteStatus(s_);
|
|
}
|
|
|
|
void SetViaStringList(TF_OperationDescription* desc,
|
|
const std::vector<string>& list) {
|
|
std::unique_ptr<const void*[]> list_ptrs;
|
|
std::unique_ptr<size_t[]> list_lens;
|
|
StringVectorToArrays(list, &list_ptrs, &list_lens);
|
|
TF_SetAttrStringList(desc, tensorflow::kColocationAttrName, list_ptrs.get(),
|
|
list_lens.get(), list.size());
|
|
}
|
|
|
|
void SetViaProto(TF_OperationDescription* desc,
|
|
const std::vector<string>& list) {
|
|
tensorflow::AttrValue attr;
|
|
for (const string& v : list) {
|
|
attr.mutable_list()->add_s(v);
|
|
}
|
|
string bytes;
|
|
attr.SerializeToString(&bytes);
|
|
TF_SetAttrValueProto(desc, tensorflow::kColocationAttrName, bytes.data(),
|
|
bytes.size(), s_);
|
|
ASSERT_EQ(TF_OK, TF_GetCode(s_)) << TF_Message(s_);
|
|
}
|
|
|
|
void VerifyCollocation(TF_Operation* op,
|
|
const std::vector<string>& expected) {
|
|
TF_AttrMetadata m =
|
|
TF_OperationGetAttrMetadata(op, tensorflow::kColocationAttrName, s_);
|
|
if (expected.empty()) {
|
|
ASSERT_EQ(TF_INVALID_ARGUMENT, TF_GetCode(s_)) << TF_Message(s_);
|
|
EXPECT_EQ("Operation 'add' has no attr named '_class'.",
|
|
string(TF_Message(s_)));
|
|
return;
|
|
}
|
|
EXPECT_EQ(TF_OK, TF_GetCode(s_)) << TF_Message(s_);
|
|
EXPECT_EQ(1, m.is_list);
|
|
EXPECT_EQ(expected.size(), m.list_size);
|
|
EXPECT_EQ(TF_ATTR_STRING, m.type);
|
|
std::vector<void*> values(expected.size());
|
|
std::vector<size_t> lens(expected.size());
|
|
std::unique_ptr<char[]> storage(new char[m.total_size]);
|
|
TF_OperationGetAttrStringList(op, tensorflow::kColocationAttrName,
|
|
values.data(), lens.data(), expected.size(),
|
|
storage.get(), m.total_size, s_);
|
|
EXPECT_EQ(TF_OK, TF_GetCode(s_)) << TF_Message(s_);
|
|
for (int i = 0; i < expected.size(); ++i) {
|
|
EXPECT_EQ(expected[i],
|
|
string(static_cast<const char*>(values[i]), lens[i]));
|
|
}
|
|
}
|
|
|
|
void FinishAndVerify(TF_OperationDescription* desc,
|
|
const std::vector<string>& expected) {
|
|
TF_Operation* op = TF_FinishOperation(desc_, s_);
|
|
ASSERT_EQ(TF_OK, TF_GetCode(s_)) << TF_Message(s_);
|
|
VerifyCollocation(op, expected);
|
|
}
|
|
|
|
TF_Status* s_;
|
|
TF_Graph* graph_;
|
|
TF_Operation* feed1_;
|
|
TF_Operation* feed2_;
|
|
TF_Operation* constant_;
|
|
TF_OperationDescription* desc_;
|
|
};
|
|
|
|
TEST_F(CApiColocationTest, ColocateWith) {
|
|
TF_ColocateWith(desc_, feed1_);
|
|
FinishAndVerify(desc_, {"loc:@feed1"});
|
|
}
|
|
|
|
TEST_F(CApiColocationTest, StringList) {
|
|
SetViaStringList(desc_, {"loc:@feed1"});
|
|
FinishAndVerify(desc_, {"loc:@feed1"});
|
|
}
|
|
|
|
TEST_F(CApiColocationTest, Proto) {
|
|
SetViaProto(desc_, {"loc:@feed1"});
|
|
FinishAndVerify(desc_, {"loc:@feed1"});
|
|
}
|
|
|
|
TEST_F(CApiColocationTest, ColocateWith_StringList) {
|
|
TF_ColocateWith(desc_, feed1_);
|
|
SetViaStringList(desc_, {"loc:@feed2"});
|
|
FinishAndVerify(desc_, {"loc:@feed2"});
|
|
}
|
|
|
|
TEST_F(CApiColocationTest, ColocateWith_Proto) {
|
|
TF_ColocateWith(desc_, feed1_);
|
|
SetViaProto(desc_, {"loc:@feed2"});
|
|
FinishAndVerify(desc_, {"loc:@feed2"});
|
|
}
|
|
|
|
TEST_F(CApiColocationTest, StringList_ColocateWith) {
|
|
SetViaStringList(desc_, {"loc:@feed2"});
|
|
TF_ColocateWith(desc_, feed1_);
|
|
FinishAndVerify(desc_, {"loc:@feed1", "loc:@feed2"});
|
|
}
|
|
|
|
TEST_F(CApiColocationTest, Proto_ColocateWith) {
|
|
SetViaProto(desc_, {"loc:@feed2"});
|
|
TF_ColocateWith(desc_, feed1_);
|
|
FinishAndVerify(desc_, {"loc:@feed1", "loc:@feed2"});
|
|
}
|
|
|
|
TEST_F(CApiColocationTest, ColocateWith_ColocateWith) {
|
|
TF_ColocateWith(desc_, feed1_);
|
|
TF_ColocateWith(desc_, feed2_);
|
|
FinishAndVerify(desc_, {"loc:@feed1", "loc:@feed2"});
|
|
}
|
|
|
|
TEST_F(CApiColocationTest, Proto_StringList) {
|
|
SetViaProto(desc_, {"loc:@feed1"});
|
|
SetViaStringList(desc_, {"loc:@feed2"});
|
|
FinishAndVerify(desc_, {"loc:@feed2"});
|
|
}
|
|
|
|
TEST_F(CApiColocationTest, StringList_Proto) {
|
|
SetViaStringList(desc_, {"loc:@feed1"});
|
|
SetViaProto(desc_, {"loc:@feed2"});
|
|
FinishAndVerify(desc_, {"loc:@feed2"});
|
|
}
|
|
|
|
TEST_F(CApiColocationTest, ClearViaStringList) {
|
|
TF_ColocateWith(desc_, feed1_);
|
|
SetViaStringList(desc_, {});
|
|
FinishAndVerify(desc_, {});
|
|
}
|
|
|
|
TEST_F(CApiColocationTest, ClearViaProto) {
|
|
TF_ColocateWith(desc_, feed1_);
|
|
SetViaProto(desc_, {});
|
|
FinishAndVerify(desc_, {});
|
|
}
|
|
|
|
TEST(CAPI, SavedModel) {
|
|
// Load the saved model.
|
|
const char kSavedModel[] = "cc/saved_model/testdata/half_plus_two/00000123";
|
|
const string saved_model_dir = tensorflow::io::JoinPath(
|
|
tensorflow::testing::TensorFlowSrcRoot(), kSavedModel);
|
|
TF_SessionOptions* opt = TF_NewSessionOptions();
|
|
TF_Buffer* run_options = TF_NewBufferFromString("", 0);
|
|
TF_Buffer* metagraph = TF_NewBuffer();
|
|
TF_Status* s = TF_NewStatus();
|
|
const char* tags[] = {tensorflow::kSavedModelTagServe};
|
|
TF_Graph* graph = TF_NewGraph();
|
|
TF_Session* session = TF_LoadSessionFromSavedModel(
|
|
opt, run_options, saved_model_dir.c_str(), tags, 1, graph, metagraph, s);
|
|
TF_DeleteBuffer(run_options);
|
|
TF_DeleteSessionOptions(opt);
|
|
tensorflow::MetaGraphDef metagraph_def;
|
|
metagraph_def.ParseFromArray(metagraph->data, metagraph->length);
|
|
TF_DeleteBuffer(metagraph);
|
|
|
|
EXPECT_EQ(TF_OK, TF_GetCode(s)) << TF_Message(s);
|
|
CSession csession(session);
|
|
|
|
// Retrieve the regression signature from meta graph def.
|
|
const auto signature_def_map = metagraph_def.signature_def();
|
|
const auto signature_def = signature_def_map.at("regress_x_to_y");
|
|
|
|
const string input_name =
|
|
signature_def.inputs().at(tensorflow::kRegressInputs).name();
|
|
const string output_name =
|
|
signature_def.outputs().at(tensorflow::kRegressOutputs).name();
|
|
|
|
// Write {0, 1, 2, 3} as tensorflow::Example inputs.
|
|
Tensor input(tensorflow::DT_STRING, TensorShape({4}));
|
|
for (tensorflow::int64 i = 0; i < input.NumElements(); ++i) {
|
|
tensorflow::Example example;
|
|
auto* feature_map = example.mutable_features()->mutable_feature();
|
|
(*feature_map)["x"].mutable_float_list()->add_value(i);
|
|
input.flat<string>()(i) = example.SerializeAsString();
|
|
}
|
|
|
|
const tensorflow::string input_op_name(
|
|
tensorflow::ParseTensorName(input_name).first);
|
|
TF_Operation* input_op =
|
|
TF_GraphOperationByName(graph, input_op_name.c_str());
|
|
ASSERT_TRUE(input_op != nullptr);
|
|
csession.SetInputs({{input_op, TF_TensorFromTensor(input, s)}});
|
|
ASSERT_EQ(TF_OK, TF_GetCode(s)) << TF_Message(s);
|
|
|
|
const tensorflow::string output_op_name(
|
|
tensorflow::ParseTensorName(output_name).first);
|
|
TF_Operation* output_op =
|
|
TF_GraphOperationByName(graph, output_op_name.c_str());
|
|
ASSERT_TRUE(output_op != nullptr);
|
|
csession.SetOutputs({output_op});
|
|
csession.Run(s);
|
|
ASSERT_EQ(TF_OK, TF_GetCode(s)) << TF_Message(s);
|
|
|
|
TF_Tensor* out = csession.output_tensor(0);
|
|
ASSERT_TRUE(out != nullptr);
|
|
EXPECT_EQ(TF_FLOAT, TF_TensorType(out));
|
|
EXPECT_EQ(2, TF_NumDims(out));
|
|
EXPECT_EQ(4, TF_Dim(out, 0));
|
|
EXPECT_EQ(1, TF_Dim(out, 1));
|
|
float* values = static_cast<float*>(TF_TensorData(out));
|
|
// These values are defined to be (input / 2) + 2.
|
|
EXPECT_EQ(2, values[0]);
|
|
EXPECT_EQ(2.5, values[1]);
|
|
EXPECT_EQ(3, values[2]);
|
|
EXPECT_EQ(3.5, values[3]);
|
|
|
|
csession.CloseAndDelete(s);
|
|
EXPECT_EQ(TF_OK, TF_GetCode(s)) << TF_Message(s);
|
|
TF_DeleteGraph(graph);
|
|
TF_DeleteStatus(s);
|
|
}
|
|
|
|
TEST(CAPI, SavedModelNullArgsAreValid) {
|
|
const char kSavedModel[] = "cc/saved_model/testdata/half_plus_two/00000123";
|
|
const string saved_model_dir = tensorflow::io::JoinPath(
|
|
tensorflow::testing::TensorFlowSrcRoot(), kSavedModel);
|
|
TF_SessionOptions* opt = TF_NewSessionOptions();
|
|
TF_Status* s = TF_NewStatus();
|
|
const char* tags[] = {tensorflow::kSavedModelTagServe};
|
|
TF_Graph* graph = TF_NewGraph();
|
|
// NULL run_options and meta_graph_def should work.
|
|
TF_Session* session = TF_LoadSessionFromSavedModel(
|
|
opt, nullptr, saved_model_dir.c_str(), tags, 1, graph, nullptr, s);
|
|
EXPECT_EQ(TF_OK, TF_GetCode(s)) << TF_Message(s);
|
|
TF_DeleteSessionOptions(opt);
|
|
TF_CloseSession(session, s);
|
|
EXPECT_EQ(TF_OK, TF_GetCode(s)) << TF_Message(s);
|
|
TF_DeleteSession(session, s);
|
|
EXPECT_EQ(TF_OK, TF_GetCode(s)) << TF_Message(s);
|
|
TF_DeleteGraph(graph);
|
|
TF_DeleteStatus(s);
|
|
}
|
|
|
|
TEST(CAPI, DeletingNullPointerIsSafe) {
|
|
TF_Status* status = TF_NewStatus();
|
|
|
|
TF_DeleteStatus(nullptr);
|
|
TF_DeleteBuffer(nullptr);
|
|
TF_DeleteTensor(nullptr);
|
|
TF_DeleteSessionOptions(nullptr);
|
|
TF_DeleteGraph(nullptr);
|
|
TF_DeleteImportGraphDefOptions(nullptr);
|
|
TF_DeleteImportGraphDefResults(nullptr);
|
|
TF_DeleteFunction(nullptr);
|
|
TF_DeleteSession(nullptr, status);
|
|
EXPECT_EQ(TF_OK, TF_GetCode(status)) << TF_Message(status);
|
|
TF_DeletePRunHandle(nullptr);
|
|
TF_DeleteDeprecatedSession(nullptr, status);
|
|
EXPECT_EQ(TF_OK, TF_GetCode(status)) << TF_Message(status);
|
|
TF_DeleteDeviceList(nullptr);
|
|
TF_DeleteLibraryHandle(nullptr);
|
|
TF_DeleteApiDefMap(nullptr);
|
|
|
|
TF_DeleteStatus(status);
|
|
}
|
|
|
|
TEST(CAPI, TestBitcastFrom_Reshape) {
|
|
int64_t dims[] = {2, 3};
|
|
TF_Tensor* a =
|
|
TF_AllocateTensor(TF_UINT64, dims, 2, 6 * TF_DataTypeSize(TF_UINT64));
|
|
TF_Tensor* b =
|
|
TF_AllocateTensor(TF_UINT64, nullptr, 0, TF_DataTypeSize(TF_UINT64));
|
|
EXPECT_NE(a, nullptr);
|
|
EXPECT_NE(b, nullptr);
|
|
|
|
EXPECT_EQ(6, TF_TensorElementCount(a));
|
|
EXPECT_EQ(1, TF_TensorElementCount(b));
|
|
EXPECT_EQ(6 * TF_DataTypeSize(TF_UINT64), TF_TensorByteSize(a));
|
|
EXPECT_EQ(TF_DataTypeSize(TF_UINT64), TF_TensorByteSize(b));
|
|
|
|
int64_t new_dims[] = {3, 2};
|
|
TF_Status* status = TF_NewStatus();
|
|
TF_TensorBitcastFrom(a, TF_UINT64, b, new_dims, 2, status);
|
|
ASSERT_EQ(TF_OK, TF_GetCode(status));
|
|
TF_DeleteStatus(status);
|
|
|
|
EXPECT_EQ(6, TF_TensorElementCount(a));
|
|
EXPECT_EQ(6, TF_TensorElementCount(b));
|
|
EXPECT_EQ(6 * TF_DataTypeSize(TF_UINT64), TF_TensorByteSize(a));
|
|
EXPECT_EQ(6 * TF_DataTypeSize(TF_UINT64), TF_TensorByteSize(b));
|
|
|
|
// Check that a write to one tensor shows up in the other.
|
|
*(static_cast<int64_t*>(TF_TensorData(a))) = 4;
|
|
EXPECT_EQ(4, *(static_cast<int64_t*>(TF_TensorData(b))));
|
|
*(static_cast<int64_t*>(TF_TensorData(b))) = 6;
|
|
EXPECT_EQ(6, *(static_cast<int64_t*>(TF_TensorData(a))));
|
|
|
|
TF_DeleteTensor(a);
|
|
TF_DeleteTensor(b);
|
|
}
|
|
|
|
REGISTER_OP("TestOpWithNoGradient")
|
|
.Input("x: T")
|
|
.Output("y: T")
|
|
.Attr("T: {float, double}")
|
|
.Doc(R"doc(
|
|
Test op with no grad registered.
|
|
|
|
x: input
|
|
y: output
|
|
)doc")
|
|
.SetShapeFn(tensorflow::shape_inference::UnknownShape);
|
|
|
|
class CApiGradientsTest : public ::testing::Test {
|
|
protected:
|
|
CApiGradientsTest()
|
|
: s_(TF_NewStatus()),
|
|
graph_(TF_NewGraph()),
|
|
expected_graph_(TF_NewGraph()) {}
|
|
|
|
~CApiGradientsTest() override {
|
|
TF_DeleteGraph(graph_);
|
|
TF_DeleteGraph(expected_graph_);
|
|
TF_DeleteStatus(s_);
|
|
}
|
|
|
|
void TestGradientsSuccess(bool grad_inputs_provided) {
|
|
TF_Output inputs[2];
|
|
TF_Output outputs[1];
|
|
TF_Output grad_outputs[2];
|
|
TF_Output expected_grad_outputs[2];
|
|
|
|
BuildSuccessGraph(inputs, outputs);
|
|
BuildExpectedGraph(grad_inputs_provided, expected_grad_outputs);
|
|
|
|
AddGradients(grad_inputs_provided, nullptr, inputs, 2, outputs, 1,
|
|
grad_outputs);
|
|
EXPECT_EQ(TF_OK, TF_GetCode(s_)) << TF_Message(s_);
|
|
|
|
// Compare that the graphs match.
|
|
GraphDef expected_gdef;
|
|
GraphDef gdef;
|
|
EXPECT_TRUE(GetGraphDef(expected_graph_, &expected_gdef));
|
|
EXPECT_TRUE(GetGraphDef(graph_, &gdef));
|
|
TF_EXPECT_GRAPH_EQ(expected_gdef, gdef);
|
|
|
|
// Compare that the output of the gradients of both graphs match.
|
|
RunGraphsAndCompareOutputs(grad_outputs, expected_grad_outputs);
|
|
}
|
|
|
|
void TestGradientsError(bool grad_inputs_provided) {
|
|
TF_Output inputs[1];
|
|
TF_Output outputs[1];
|
|
TF_Output grad_outputs[1];
|
|
|
|
BuildErrorGraph(inputs, outputs);
|
|
|
|
AddGradients(grad_inputs_provided, nullptr, inputs, 1, outputs, 1,
|
|
grad_outputs);
|
|
|
|
string expected_msg =
|
|
"No gradient defined for op: TestOpWithNoGradient. Please see "
|
|
"https://www.tensorflow.org/code/"
|
|
"tensorflow/cc/gradients/README.md"
|
|
" for instructions on how to add C++ gradients.";
|
|
EXPECT_EQ(expected_msg, TF_Message(s_));
|
|
}
|
|
|
|
// Run the graph and ensure that the gradient values are as expected.
|
|
void RunGraphsAndCompareOutputs(TF_Output* grad_outputs,
|
|
TF_Output* expected_grad_outputs) {
|
|
std::unique_ptr<CSession> csession(new CSession(graph_, s_));
|
|
std::unique_ptr<CSession> expected_csession(
|
|
new CSession(expected_graph_, s_));
|
|
|
|
std::vector<TF_Output> grad_outputs_vec;
|
|
grad_outputs_vec.assign(grad_outputs, grad_outputs + 2);
|
|
csession->SetOutputs(grad_outputs_vec);
|
|
csession->Run(s_);
|
|
ASSERT_EQ(TF_OK, TF_GetCode(s_)) << TF_Message(s_);
|
|
TF_Tensor* out0 = csession->output_tensor(0);
|
|
TF_Tensor* out1 = csession->output_tensor(1);
|
|
|
|
std::vector<TF_Output> expected_grad_outputs_vec;
|
|
expected_grad_outputs_vec.assign(expected_grad_outputs,
|
|
expected_grad_outputs + 2);
|
|
expected_csession->SetOutputs(expected_grad_outputs_vec);
|
|
expected_csession->Run(s_);
|
|
ASSERT_EQ(TF_OK, TF_GetCode(s_)) << TF_Message(s_);
|
|
TF_Tensor* expected_out0 = expected_csession->output_tensor(0);
|
|
TF_Tensor* expected_out1 = expected_csession->output_tensor(1);
|
|
|
|
CompareTensors(out0, expected_out0);
|
|
CompareTensors(out1, expected_out1);
|
|
}
|
|
|
|
void CompareTensors(TF_Tensor* a, TF_Tensor* b) {
|
|
float* a_data = static_cast<float*>(TF_TensorData(a));
|
|
float* b_data = static_cast<float*>(TF_TensorData(b));
|
|
EXPECT_EQ(*a_data, *b_data);
|
|
}
|
|
|
|
void AddGradients(bool grad_inputs_provided, const char* prefix,
|
|
TF_Output* inputs, int ninputs, TF_Output* outputs,
|
|
int noutputs, TF_Output* grad_outputs) {
|
|
if (grad_inputs_provided) {
|
|
TF_Output grad_inputs[1];
|
|
const float grad_inputs_val[] = {1.0, 1.0, 1.0, 1.0};
|
|
TF_Operation* grad_inputs_op =
|
|
FloatConst2x2(graph_, s_, grad_inputs_val, "GradInputs");
|
|
grad_inputs[0] = TF_Output{grad_inputs_op, 0};
|
|
TF_AddGradientsWithPrefix(graph_, prefix, outputs, noutputs, inputs,
|
|
ninputs, grad_inputs, s_, grad_outputs);
|
|
} else {
|
|
TF_AddGradientsWithPrefix(graph_, prefix, outputs, noutputs, inputs,
|
|
ninputs, nullptr, s_, grad_outputs);
|
|
}
|
|
}
|
|
|
|
void BuildErrorGraph(TF_Output* inputs, TF_Output* outputs) {
|
|
const float const0_val[] = {1.0, 2.0, 3.0, 4.0};
|
|
TF_Operation* const0 = FloatConst2x2(graph_, s_, const0_val, "Const_0");
|
|
TF_Operation* nograd = NoGradientOp(graph_, s_, const0, "NoGrad");
|
|
inputs[0] = TF_Output{const0, 0};
|
|
outputs[0] = TF_Output{nograd, 0};
|
|
EXPECT_EQ(TF_OK, TF_GetCode(s_)) << TF_Message(s_);
|
|
}
|
|
|
|
void BuildSuccessGraph(TF_Output* inputs, TF_Output* outputs) {
|
|
// Construct the following graph:
|
|
// |
|
|
// z|
|
|
// |
|
|
// MatMul
|
|
// / \
|
|
// ^ ^
|
|
// | |
|
|
// x| y|
|
|
// | |
|
|
// | |
|
|
// Const_0 Const_1
|
|
//
|
|
const float const0_val[] = {1.0, 2.0, 3.0, 4.0};
|
|
const float const1_val[] = {1.0, 0.0, 0.0, 1.0};
|
|
TF_Operation* const0 = FloatConst2x2(graph_, s_, const0_val, "Const_0");
|
|
TF_Operation* const1 = FloatConst2x2(graph_, s_, const1_val, "Const_1");
|
|
TF_Operation* matmul = MatMul(graph_, s_, const0, const1, "MatMul");
|
|
inputs[0] = TF_Output{const0, 0};
|
|
inputs[1] = TF_Output{const1, 0};
|
|
outputs[0] = TF_Output{matmul, 0};
|
|
EXPECT_EQ(TF_OK, TF_GetCode(s_)) << TF_Message(s_);
|
|
}
|
|
|
|
void BuildExpectedGraph(bool grad_inputs_provided,
|
|
TF_Output* expected_grad_outputs) {
|
|
// The expected graph looks like this if grad_inputs_provided.
|
|
// If grad_inputs_provided is false, Const_0 will be a OnesLike op.
|
|
// ^ ^
|
|
// dy| dx| // MatMul Gradient Graph
|
|
// | |
|
|
// MatMul_2 MatMul_1
|
|
// ^ ^ ^ ^
|
|
// | |----------| |
|
|
// | ^ |
|
|
// | dz| |
|
|
// | | |
|
|
// | Const_3 |
|
|
// | |
|
|
// | ^ |
|
|
// | z| | // MatMul Forward Graph
|
|
// | | |
|
|
// | MatMul |
|
|
// | / \ |
|
|
// | ^ ^ |
|
|
// | | | |
|
|
// |---x| y|----|
|
|
// | |
|
|
// | |
|
|
// Const_0 Const_1
|
|
//
|
|
const float const0_val[] = {1.0, 2.0, 3.0, 4.0};
|
|
const float const1_val[] = {1.0, 0.0, 0.0, 1.0};
|
|
TF_Operation* const0 =
|
|
FloatConst2x2(expected_graph_, s_, const0_val, "Const_0");
|
|
TF_Operation* const1 =
|
|
FloatConst2x2(expected_graph_, s_, const1_val, "Const_1");
|
|
TF_Operation* matmul =
|
|
MatMul(expected_graph_, s_, const0, const1, "MatMul");
|
|
|
|
TF_Operation* const3;
|
|
if (grad_inputs_provided) {
|
|
const float const3_val[] = {1.0, 1.0, 1.0, 1.0};
|
|
const3 = FloatConst2x2(expected_graph_, s_, const3_val, "GradInputs");
|
|
} else {
|
|
const3 = OnesLike(expected_graph_, s_, matmul, "gradients/OnesLike");
|
|
}
|
|
|
|
TF_Operation* matmul1 = MatMul(expected_graph_, s_, const3, const1,
|
|
"gradients/MatMul", false, true);
|
|
TF_Operation* matmul2 = MatMul(expected_graph_, s_, const0, const3,
|
|
"gradients/MatMul_1", true, false);
|
|
expected_grad_outputs[0] = {matmul1, 0};
|
|
expected_grad_outputs[1] = {matmul2, 0};
|
|
}
|
|
|
|
TF_Tensor* FloatTensor2x2(const float* values) {
|
|
const int64_t dims[2] = {2, 2};
|
|
TF_Tensor* t = TF_AllocateTensor(TF_FLOAT, dims, 2, sizeof(float) * 4);
|
|
memcpy(TF_TensorData(t), values, sizeof(float) * 4);
|
|
return t;
|
|
}
|
|
|
|
TF_Operation* FloatConst2x2(TF_Graph* graph, TF_Status* s,
|
|
const float* values, const char* name) {
|
|
unique_tensor_ptr tensor(FloatTensor2x2(values), TF_DeleteTensor);
|
|
TF_OperationDescription* desc = TF_NewOperation(graph, "Const", name);
|
|
TF_SetAttrTensor(desc, "value", tensor.get(), s);
|
|
if (TF_GetCode(s) != TF_OK) return nullptr;
|
|
TF_SetAttrType(desc, "dtype", TF_FLOAT);
|
|
TF_Operation* op = TF_FinishOperation(desc, s);
|
|
EXPECT_EQ(TF_OK, TF_GetCode(s)) << TF_Message(s);
|
|
return op;
|
|
}
|
|
|
|
TF_Operation* MatMul(TF_Graph* graph, TF_Status* s, TF_Operation* l,
|
|
TF_Operation* r, const char* name,
|
|
bool transpose_a = false, bool transpose_b = false) {
|
|
TF_OperationDescription* desc = TF_NewOperation(graph, "MatMul", name);
|
|
if (transpose_a) {
|
|
TF_SetAttrBool(desc, "transpose_a", 1);
|
|
}
|
|
if (transpose_b) {
|
|
TF_SetAttrBool(desc, "transpose_b", 1);
|
|
}
|
|
TF_AddInput(desc, {l, 0});
|
|
TF_AddInput(desc, {r, 0});
|
|
TF_Operation* op = TF_FinishOperation(desc, s);
|
|
EXPECT_EQ(TF_OK, TF_GetCode(s)) << TF_Message(s);
|
|
return op;
|
|
}
|
|
|
|
TF_Operation* OnesLike(TF_Graph* graph, TF_Status* s, TF_Operation* in,
|
|
const char* name) {
|
|
TF_OperationDescription* desc = TF_NewOperation(graph, "OnesLike", name);
|
|
TF_AddInput(desc, {in, 0});
|
|
TF_Operation* op = TF_FinishOperation(desc, s);
|
|
EXPECT_EQ(TF_OK, TF_GetCode(s)) << TF_Message(s);
|
|
return op;
|
|
}
|
|
|
|
TF_Operation* NoGradientOp(TF_Graph* graph, TF_Status* s, TF_Operation* in,
|
|
const char* name) {
|
|
TF_OperationDescription* desc =
|
|
TF_NewOperation(graph, "TestOpWithNoGradient", name);
|
|
TF_AddInput(desc, {in, 0});
|
|
TF_Operation* op = TF_FinishOperation(desc, s);
|
|
EXPECT_EQ(TF_OK, TF_GetCode(s)) << TF_Message(s);
|
|
return op;
|
|
}
|
|
|
|
void BuildGraphAndAddGradientsWithPrefixes(const char* prefix1,
|
|
const char* prefix2 = nullptr) {
|
|
TF_Output inputs[2];
|
|
TF_Output outputs[1];
|
|
TF_Output grad_outputs[2];
|
|
|
|
BuildSuccessGraph(inputs, outputs);
|
|
|
|
AddGradients(false, prefix1, inputs, 2, outputs, 1, grad_outputs);
|
|
if (prefix2 != nullptr) {
|
|
AddGradients(false, prefix2, inputs, 2, outputs, 1, grad_outputs);
|
|
}
|
|
}
|
|
|
|
TF_Status* s_;
|
|
TF_Graph* graph_;
|
|
TF_Graph* expected_graph_;
|
|
};
|
|
|
|
TEST_F(CApiGradientsTest, Gradients_GradInputs) { TestGradientsSuccess(true); }
|
|
|
|
TEST_F(CApiGradientsTest, Gradients_NoGradInputs) {
|
|
TestGradientsSuccess(false);
|
|
}
|
|
|
|
TEST_F(CApiGradientsTest, OpWithNoGradientRegistered_GradInputs) {
|
|
TestGradientsError(true);
|
|
}
|
|
|
|
TEST_F(CApiGradientsTest, OpWithNoGradientRegistered_NoGradInputs) {
|
|
TestGradientsError(false);
|
|
}
|
|
|
|
TEST_F(CApiGradientsTest, GradientsPrefix_PrefixIsOk) {
|
|
BuildGraphAndAddGradientsWithPrefixes("gradients");
|
|
ASSERT_EQ(TF_OK, TF_GetCode(s_)) << TF_Message(s_);
|
|
}
|
|
|
|
TEST_F(CApiGradientsTest, GradientsPrefix_TwoGradientsWithDistinctPrefixes) {
|
|
BuildGraphAndAddGradientsWithPrefixes("gradients", "gradients_1");
|
|
ASSERT_EQ(TF_OK, TF_GetCode(s_)) << TF_Message(s_);
|
|
}
|
|
|
|
TEST_F(CApiGradientsTest, GradientsPrefix_TwoGradientsInSameScope) {
|
|
BuildGraphAndAddGradientsWithPrefixes("scope/gradients", "scope/gradients_1");
|
|
ASSERT_EQ(TF_OK, TF_GetCode(s_)) << TF_Message(s_);
|
|
}
|
|
|
|
TEST_F(CApiGradientsTest, GradientsPrefix_TwoGradientsInDifferentScopes) {
|
|
BuildGraphAndAddGradientsWithPrefixes("scope/gradients", "scope_1/gradients");
|
|
ASSERT_EQ(TF_OK, TF_GetCode(s_)) << TF_Message(s_);
|
|
}
|
|
|
|
TEST_F(CApiGradientsTest, GradientsPrefix_2ndGradientsAsSubScopeOf1st) {
|
|
BuildGraphAndAddGradientsWithPrefixes("gradients", "gradients/sub");
|
|
ASSERT_EQ(TF_OK, TF_GetCode(s_)) << TF_Message(s_);
|
|
}
|
|
|
|
TEST_F(CApiGradientsTest, GradientsPrefix_PrefixMatchesExistingNodeName) {
|
|
BuildGraphAndAddGradientsWithPrefixes("Const_0");
|
|
ASSERT_EQ(TF_INVALID_ARGUMENT, TF_GetCode(s_)) << TF_Message(s_);
|
|
}
|
|
|
|
TEST_F(CApiGradientsTest, GradientsPrefix_TwoGradientsWithIdenticalPrefixes) {
|
|
BuildGraphAndAddGradientsWithPrefixes("gradients", "gradients");
|
|
ASSERT_EQ(TF_INVALID_ARGUMENT, TF_GetCode(s_)) << TF_Message(s_);
|
|
}
|
|
|
|
TEST_F(CApiGradientsTest, GradientsPrefix_2ndGradientsMatchingNodeOf1st) {
|
|
BuildGraphAndAddGradientsWithPrefixes("gradients", "gradients/MatMul");
|
|
ASSERT_EQ(TF_INVALID_ARGUMENT, TF_GetCode(s_)) << TF_Message(s_);
|
|
}
|
|
|
|
TEST_F(CApiGradientsTest, GradientsPrefix_1stGradientsMatchingNodeOf2nd) {
|
|
BuildGraphAndAddGradientsWithPrefixes("gradients/MatMul", "gradients");
|
|
ASSERT_EQ(TF_INVALID_ARGUMENT, TF_GetCode(s_)) << TF_Message(s_);
|
|
}
|
|
|
|
TEST_F(CApiGradientsTest, GradientsPrefix_2ndGradientsAsParentScopeOf1st) {
|
|
BuildGraphAndAddGradientsWithPrefixes("gradients/sub", "gradients");
|
|
ASSERT_EQ(TF_INVALID_ARGUMENT, TF_GetCode(s_)) << TF_Message(s_);
|
|
}
|
|
|
|
void ScalarFloatFromTensor(const TF_Tensor* t, float* f) {
|
|
ASSERT_TRUE(t != nullptr);
|
|
ASSERT_EQ(TF_FLOAT, TF_TensorType(t));
|
|
ASSERT_EQ(0, TF_NumDims(t));
|
|
ASSERT_EQ(4, TF_TensorByteSize(t));
|
|
float* p = static_cast<float*>(TF_TensorData(t));
|
|
*f = *p;
|
|
}
|
|
|
|
TEST_F(CApiGradientsTest, MultipleCallsToAddGradients) {
|
|
const float X = 3.0f, Y = 7.0f;
|
|
TF_Operation* x = Placeholder(graph_, s_, "x", TF_FLOAT);
|
|
TF_Operation* y = Placeholder(graph_, s_, "y", TF_FLOAT);
|
|
TF_Operation* xy = Mul(x, y, graph_, s_, "xy");
|
|
TF_Output dxy_dx, dxy_dy;
|
|
|
|
TF_Output outputs[1] = {{xy, 0}};
|
|
TF_Output inputs[1] = {{x, 0}};
|
|
TF_AddGradients(graph_, outputs, 1, inputs, 1, nullptr, s_, &dxy_dx);
|
|
ASSERT_EQ(TF_OK, TF_GetCode(s_)) << TF_Message(s_);
|
|
|
|
inputs[0] = {y, 0};
|
|
TF_AddGradients(graph_, outputs, 1, inputs, 1, nullptr, s_, &dxy_dy);
|
|
ASSERT_EQ(TF_OK, TF_GetCode(s_)) << TF_Message(s_);
|
|
|
|
TF_SessionOptions* opts = TF_NewSessionOptions();
|
|
TF_Session* sess = TF_NewSession(graph_, opts, s_);
|
|
TF_DeleteSessionOptions(opts);
|
|
ASSERT_EQ(TF_OK, TF_GetCode(s_)) << TF_Message(s_);
|
|
|
|
TF_Output feeds[] = {{x, 0}, {y, 0}};
|
|
TF_Tensor* feedValues[] = {FloatTensor(X), FloatTensor(Y)};
|
|
TF_Output fetches[] = {dxy_dx, dxy_dy};
|
|
TF_Tensor* fetchValues[] = {nullptr, nullptr};
|
|
|
|
TF_SessionRun(sess, nullptr /* run_options */, feeds, feedValues, 2, fetches,
|
|
fetchValues, 2, nullptr /* target_opers */, 0,
|
|
nullptr /* run_metadata */, s_);
|
|
TF_DeleteTensor(feedValues[0]);
|
|
TF_DeleteTensor(feedValues[1]);
|
|
ASSERT_EQ(TF_OK, TF_GetCode(s_)) << TF_Message(s_);
|
|
TF_DeleteSession(sess, s_);
|
|
ASSERT_EQ(TF_OK, TF_GetCode(s_)) << TF_Message(s_);
|
|
|
|
float dxy_dxValue = 0.0f, dxy_dyValue = 0.0f;
|
|
ScalarFloatFromTensor(fetchValues[0], &dxy_dxValue);
|
|
EXPECT_EQ(Y, dxy_dxValue);
|
|
|
|
ScalarFloatFromTensor(fetchValues[1], &dxy_dyValue);
|
|
EXPECT_EQ(X, dxy_dyValue);
|
|
|
|
TF_DeleteTensor(fetchValues[0]);
|
|
TF_DeleteTensor(fetchValues[1]);
|
|
}
|
|
|
|
// REGISTER_OP for CApiAttributesTest test cases.
|
|
// Registers two ops, each with a single attribute called 'v'.
|
|
// The attribute in one op will have a type 'type', the other
|
|
// will have list(type).
|
|
#define ATTR_TEST_REGISTER_OP(type) \
|
|
REGISTER_OP("CApiAttributesTestOp" #type) \
|
|
.Attr("v: " #type) \
|
|
.SetShapeFn(tensorflow::shape_inference::UnknownShape); \
|
|
REGISTER_OP("CApiAttributesTestOpList" #type) \
|
|
.Attr("v: list(" #type ")") \
|
|
.SetShapeFn(tensorflow::shape_inference::UnknownShape)
|
|
ATTR_TEST_REGISTER_OP(string);
|
|
ATTR_TEST_REGISTER_OP(int);
|
|
ATTR_TEST_REGISTER_OP(float);
|
|
ATTR_TEST_REGISTER_OP(bool);
|
|
ATTR_TEST_REGISTER_OP(type);
|
|
ATTR_TEST_REGISTER_OP(shape);
|
|
ATTR_TEST_REGISTER_OP(tensor);
|
|
#undef ATTR_TEST_REGISTER_OP
|
|
|
|
class CApiAttributesTest : public ::testing::Test {
|
|
protected:
|
|
CApiAttributesTest()
|
|
: s_(TF_NewStatus()), graph_(TF_NewGraph()), counter_(0) {}
|
|
|
|
~CApiAttributesTest() override {
|
|
TF_DeleteGraph(graph_);
|
|
TF_DeleteStatus(s_);
|
|
}
|
|
|
|
TF_OperationDescription* init(string type) {
|
|
// Construct op_name to match the name used by REGISTER_OP in the
|
|
// ATTR_TEST_REGISTER calls above.
|
|
string op_name = "CApiAttributesTestOp";
|
|
if (type.find("list(") == 0) {
|
|
op_name += "List";
|
|
type = type.replace(0, 5, "");
|
|
type = type.replace(type.size() - 1, 1, "");
|
|
}
|
|
op_name += type;
|
|
return TF_NewOperation(
|
|
graph_, op_name.c_str(),
|
|
::tensorflow::strings::StrCat("name", counter_++).c_str());
|
|
}
|
|
|
|
TF_Status* s_;
|
|
|
|
private:
|
|
TF_Graph* graph_;
|
|
int counter_;
|
|
};
|
|
|
|
// Helper macros for the TF_OperationGetAttr* tests.
|
|
// TODO(ashankar): Use gmock matchers instead?
|
|
// (https://github.com/google/googletest/blob/master/googlemock/docs/CookBook.md#writing-new-parameterized-matchers-quickly)
|
|
// That will require setting up the tensorflow build with gmock.
|
|
#define EXPECT_TF_META(attr_name, expected_list_size, expected_type, \
|
|
expected_total_size) \
|
|
do { \
|
|
auto m = TF_OperationGetAttrMetadata(oper, attr_name, s_); \
|
|
EXPECT_EQ(TF_OK, TF_GetCode(s_)) << TF_Message(s_); \
|
|
const unsigned char e = expected_list_size >= 0 ? 1 : 0; \
|
|
EXPECT_EQ(e, m.is_list); \
|
|
EXPECT_EQ(expected_list_size, m.list_size); \
|
|
EXPECT_EQ(expected_type, m.type); \
|
|
EXPECT_EQ(expected_total_size, m.total_size); \
|
|
} while (0)
|
|
|
|
TEST_F(CApiAttributesTest, String) {
|
|
auto desc = init("string");
|
|
TF_SetAttrString(desc, "v", "bunny", 5);
|
|
|
|
auto oper = TF_FinishOperation(desc, s_);
|
|
ASSERT_EQ(TF_OK, TF_GetCode(s_)) << TF_Message(s_);
|
|
EXPECT_TF_META("v", -1, TF_ATTR_STRING, 5);
|
|
std::unique_ptr<char[]> value(new char[5]);
|
|
|
|
TF_OperationGetAttrString(oper, "v", value.get(), 5, s_);
|
|
EXPECT_EQ(TF_OK, TF_GetCode(s_)) << TF_Message(s_);
|
|
EXPECT_EQ("bunny", string(static_cast<const char*>(value.get()), 5));
|
|
}
|
|
|
|
TEST_F(CApiAttributesTest, StringList) {
|
|
std::vector<string> list = {"bugs", "bunny", "duck"};
|
|
std::unique_ptr<const void*[]> list_ptrs;
|
|
std::unique_ptr<size_t[]> list_lens;
|
|
StringVectorToArrays(list, &list_ptrs, &list_lens);
|
|
int list_total_size = 0;
|
|
for (const auto& s : list) {
|
|
list_total_size += s.size();
|
|
}
|
|
|
|
auto desc = init("list(string)");
|
|
TF_SetAttrStringList(desc, "v", list_ptrs.get(), list_lens.get(),
|
|
list.size());
|
|
|
|
auto oper = TF_FinishOperation(desc, s_);
|
|
ASSERT_EQ(TF_OK, TF_GetCode(s_)) << TF_Message(s_);
|
|
|
|
EXPECT_TF_META("v", list.size(), TF_ATTR_STRING, list_total_size);
|
|
std::unique_ptr<void*[]> values(new void*[list.size()]);
|
|
std::unique_ptr<size_t[]> lens(new size_t[list.size()]);
|
|
std::unique_ptr<char[]> storage(new char[list_total_size]);
|
|
TF_OperationGetAttrStringList(oper, "v", values.get(), lens.get(),
|
|
list.size(), storage.get(), list_total_size,
|
|
s_);
|
|
EXPECT_EQ(TF_OK, TF_GetCode(s_)) << TF_Message(s_);
|
|
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;
|
|
}
|
|
}
|
|
|
|
TEST_F(CApiAttributesTest, Int) {
|
|
auto desc = init("int");
|
|
TF_SetAttrInt(desc, "v", 31415);
|
|
|
|
auto oper = TF_FinishOperation(desc, s_);
|
|
ASSERT_EQ(TF_OK, TF_GetCode(s_)) << TF_Message(s_);
|
|
EXPECT_TF_META("v", -1, TF_ATTR_INT, -1);
|
|
|
|
int64_t value;
|
|
TF_OperationGetAttrInt(oper, "v", &value, s_);
|
|
EXPECT_EQ(TF_OK, TF_GetCode(s_)) << TF_Message(s_);
|
|
EXPECT_EQ(31415, value);
|
|
}
|
|
|
|
TEST_F(CApiAttributesTest, IntList) {
|
|
const int64_t list[] = {1, 2, 3, 4};
|
|
const size_t list_size = TF_ARRAYSIZE(list);
|
|
|
|
auto desc = init("list(int)");
|
|
TF_SetAttrIntList(desc, "v", list, list_size);
|
|
|
|
auto oper = TF_FinishOperation(desc, s_);
|
|
ASSERT_EQ(TF_OK, TF_GetCode(s_)) << TF_Message(s_);
|
|
|
|
int64_t values[list_size];
|
|
EXPECT_TF_META("v", list_size, TF_ATTR_INT, -1);
|
|
TF_OperationGetAttrIntList(oper, "v", values, list_size, s_);
|
|
EXPECT_EQ(TF_OK, TF_GetCode(s_)) << TF_Message(s_);
|
|
EXPECT_TRUE(std::equal(std::begin(list), std::end(list), std::begin(values)));
|
|
}
|
|
|
|
TEST_F(CApiAttributesTest, Float) {
|
|
auto desc = init("float");
|
|
TF_SetAttrFloat(desc, "v", 2.718);
|
|
|
|
auto oper = TF_FinishOperation(desc, s_);
|
|
ASSERT_EQ(TF_OK, TF_GetCode(s_)) << TF_Message(s_);
|
|
EXPECT_TF_META("v", -1, TF_ATTR_FLOAT, -1);
|
|
|
|
float value;
|
|
TF_OperationGetAttrFloat(oper, "v", &value, s_);
|
|
EXPECT_EQ(TF_OK, TF_GetCode(s_)) << TF_Message(s_);
|
|
EXPECT_FLOAT_EQ(2.718, value);
|
|
}
|
|
|
|
TEST_F(CApiAttributesTest, FloatList) {
|
|
const float list[] = {1.414, 2.718, 3.1415};
|
|
const size_t list_size = TF_ARRAYSIZE(list);
|
|
|
|
auto desc = init("list(float)");
|
|
TF_SetAttrFloatList(desc, "v", list, list_size);
|
|
|
|
auto oper = TF_FinishOperation(desc, s_);
|
|
ASSERT_EQ(TF_OK, TF_GetCode(s_)) << TF_Message(s_);
|
|
|
|
float values[list_size];
|
|
EXPECT_TF_META("v", list_size, TF_ATTR_FLOAT, -1);
|
|
TF_OperationGetAttrFloatList(oper, "v", values, list_size, s_);
|
|
EXPECT_EQ(TF_OK, TF_GetCode(s_)) << TF_Message(s_);
|
|
EXPECT_TRUE(std::equal(std::begin(list), std::end(list), std::begin(values)));
|
|
}
|
|
|
|
TEST_F(CApiAttributesTest, Bool) {
|
|
auto desc = init("bool");
|
|
TF_SetAttrBool(desc, "v", 1);
|
|
|
|
auto oper = TF_FinishOperation(desc, s_);
|
|
ASSERT_EQ(TF_OK, TF_GetCode(s_)) << TF_Message(s_);
|
|
EXPECT_TF_META("v", -1, TF_ATTR_BOOL, -1);
|
|
|
|
unsigned char value;
|
|
TF_OperationGetAttrBool(oper, "v", &value, s_);
|
|
EXPECT_EQ(TF_OK, TF_GetCode(s_)) << TF_Message(s_);
|
|
EXPECT_EQ(1, value);
|
|
}
|
|
|
|
TEST_F(CApiAttributesTest, BoolList) {
|
|
const unsigned char list[] = {0, 1, 1, 0, 0, 1, 1};
|
|
const size_t list_size = TF_ARRAYSIZE(list);
|
|
|
|
auto desc = init("list(bool)");
|
|
TF_SetAttrBoolList(desc, "v", list, list_size);
|
|
|
|
auto oper = TF_FinishOperation(desc, s_);
|
|
ASSERT_EQ(TF_OK, TF_GetCode(s_)) << TF_Message(s_);
|
|
|
|
unsigned char values[list_size];
|
|
EXPECT_TF_META("v", list_size, TF_ATTR_BOOL, -1);
|
|
TF_OperationGetAttrBoolList(oper, "v", values, list_size, s_);
|
|
EXPECT_EQ(TF_OK, TF_GetCode(s_)) << TF_Message(s_);
|
|
EXPECT_TRUE(std::equal(std::begin(list), std::end(list), std::begin(values)));
|
|
}
|
|
|
|
TEST_F(CApiAttributesTest, Type) {
|
|
auto desc = init("type");
|
|
TF_SetAttrType(desc, "v", TF_COMPLEX128);
|
|
|
|
auto oper = TF_FinishOperation(desc, s_);
|
|
ASSERT_EQ(TF_OK, TF_GetCode(s_)) << TF_Message(s_);
|
|
EXPECT_TF_META("v", -1, TF_ATTR_TYPE, -1);
|
|
|
|
TF_DataType value;
|
|
TF_OperationGetAttrType(oper, "v", &value, s_);
|
|
EXPECT_EQ(TF_OK, TF_GetCode(s_)) << TF_Message(s_);
|
|
EXPECT_EQ(TF_COMPLEX128, value);
|
|
}
|
|
|
|
TEST_F(CApiAttributesTest, TypeList) {
|
|
const TF_DataType list[] = {TF_FLOAT, TF_DOUBLE, TF_HALF, TF_COMPLEX128};
|
|
const size_t list_size = TF_ARRAYSIZE(list);
|
|
|
|
auto desc = init("list(type)");
|
|
TF_SetAttrTypeList(desc, "v", list, list_size);
|
|
|
|
auto oper = TF_FinishOperation(desc, s_);
|
|
ASSERT_EQ(TF_OK, TF_GetCode(s_)) << TF_Message(s_);
|
|
|
|
TF_DataType values[list_size];
|
|
EXPECT_TF_META("v", list_size, TF_ATTR_TYPE, -1);
|
|
TF_OperationGetAttrTypeList(oper, "v", values, list_size, s_);
|
|
EXPECT_EQ(TF_OK, TF_GetCode(s_)) << TF_Message(s_);
|
|
EXPECT_TRUE(std::equal(std::begin(list), std::end(list), std::begin(values)));
|
|
}
|
|
|
|
TEST_F(CApiAttributesTest, Shape) {
|
|
// Unknown shape
|
|
auto desc = init("shape");
|
|
TF_SetAttrShape(desc, "v", nullptr, -1);
|
|
auto oper = TF_FinishOperation(desc, s_);
|
|
ASSERT_EQ(TF_OK, TF_GetCode(s_)) << TF_Message(s_);
|
|
EXPECT_TF_META("v", -1, TF_ATTR_SHAPE, -1);
|
|
TF_OperationGetAttrShape(oper, "v", nullptr, 10, s_);
|
|
EXPECT_EQ(TF_OK, TF_GetCode(s_)) << TF_Message(s_);
|
|
|
|
// Partially specified shape
|
|
const int64_t partial_shape[] = {17, -1};
|
|
const size_t sz = TF_ARRAYSIZE(partial_shape);
|
|
desc = init("shape");
|
|
TF_SetAttrShape(desc, "v", partial_shape, sz);
|
|
oper = TF_FinishOperation(desc, s_);
|
|
ASSERT_EQ(TF_OK, TF_GetCode(s_)) << TF_Message(s_);
|
|
EXPECT_TF_META("v", -1, TF_ATTR_SHAPE, sz);
|
|
int64_t values[sz];
|
|
TF_OperationGetAttrShape(oper, "v", values, sz, s_);
|
|
EXPECT_EQ(TF_OK, TF_GetCode(s_)) << TF_Message(s_);
|
|
EXPECT_TRUE(
|
|
std::equal(std::begin(partial_shape), std::end(partial_shape), values));
|
|
}
|
|
|
|
TEST_F(CApiAttributesTest, ShapeList) {
|
|
const int64_t shape_1[] = {1, 3};
|
|
const int64_t shape_2[] = {2, 4, 6};
|
|
const int64_t* list[] = {&shape_1[0], &shape_2[0]};
|
|
const size_t list_size = TF_ARRAYSIZE(list);
|
|
const int ndims[] = {TF_ARRAYSIZE(shape_1), TF_ARRAYSIZE(shape_2)};
|
|
const int total_ndims = 5; // ndims[0] + ndims[1]
|
|
|
|
auto desc = init("list(shape)");
|
|
TF_SetAttrShapeList(desc, "v", list, ndims, list_size);
|
|
auto oper = TF_FinishOperation(desc, s_);
|
|
ASSERT_EQ(TF_OK, TF_GetCode(s_)) << TF_Message(s_);
|
|
|
|
EXPECT_TF_META("v", list_size, TF_ATTR_SHAPE, total_ndims);
|
|
int64_t* values[list_size];
|
|
int values_ndims[list_size];
|
|
int64_t storage[total_ndims];
|
|
TF_OperationGetAttrShapeList(oper, "v", values, values_ndims, list_size,
|
|
storage, total_ndims, s_);
|
|
EXPECT_EQ(TF_OK, TF_GetCode(s_)) << TF_Message(s_);
|
|
for (size_t i = 0; i < list_size; ++i) {
|
|
EXPECT_EQ(ndims[i], values_ndims[i]) << i;
|
|
for (int j = 0; j < values_ndims[i]; ++j) {
|
|
EXPECT_EQ(list[i][j], values[i][j]) << "(" << i << ", " << j << ")";
|
|
}
|
|
}
|
|
}
|
|
|
|
TEST_F(CApiAttributesTest, TensorShapeProto) {
|
|
const tensorflow::int64 pts[] = {2, 4, -1, 8};
|
|
tensorflow::TensorShapeProto proto;
|
|
tensorflow::PartialTensorShape(pts).AsProto(&proto);
|
|
string bytes;
|
|
proto.SerializeToString(&bytes);
|
|
|
|
auto desc = init("shape");
|
|
TF_SetAttrTensorShapeProto(desc, "v", bytes.data(), bytes.length(), s_);
|
|
ASSERT_EQ(TF_OK, TF_GetCode(s_)) << TF_Message(s_);
|
|
auto oper = TF_FinishOperation(desc, s_);
|
|
ASSERT_EQ(TF_OK, TF_GetCode(s_)) << TF_Message(s_);
|
|
|
|
EXPECT_TF_META("v", -1, TF_ATTR_SHAPE, 4);
|
|
TF_Buffer* value = TF_NewBuffer();
|
|
TF_OperationGetAttrTensorShapeProto(oper, "v", value, s_);
|
|
EXPECT_EQ(TF_OK, TF_GetCode(s_)) << TF_Message(s_);
|
|
EXPECT_EQ(bytes.length(), value->length);
|
|
EXPECT_EQ(0, memcmp(bytes.data(), value->data, value->length));
|
|
TF_DeleteBuffer(value);
|
|
}
|
|
|
|
TEST_F(CApiAttributesTest, TensorShapeProtoList) {
|
|
string bytes1, bytes2;
|
|
tensorflow::TensorShapeProto proto;
|
|
|
|
const tensorflow::int64 pts1[] = {2, 4, -1, 8};
|
|
tensorflow::PartialTensorShape(pts1).AsProto(&proto);
|
|
proto.SerializeToString(&bytes1);
|
|
|
|
const tensorflow::int64 pts2[] = {1, 3, 5, 7};
|
|
tensorflow::PartialTensorShape(pts2).AsProto(&proto);
|
|
proto.SerializeToString(&bytes2);
|
|
|
|
std::unique_ptr<const void*[]> list_ptrs;
|
|
std::unique_ptr<size_t[]> list_lens;
|
|
const std::vector<string> list = {bytes1, bytes2};
|
|
StringVectorToArrays(list, &list_ptrs, &list_lens);
|
|
|
|
auto desc = init("list(shape)");
|
|
TF_SetAttrTensorShapeProtoList(desc, "v", list_ptrs.get(), list_lens.get(),
|
|
list.size(), s_);
|
|
ASSERT_EQ(TF_OK, TF_GetCode(s_)) << TF_Message(s_);
|
|
auto oper = TF_FinishOperation(desc, s_);
|
|
ASSERT_EQ(TF_OK, TF_GetCode(s_)) << TF_Message(s_);
|
|
|
|
EXPECT_TF_META("v", 2, TF_ATTR_SHAPE, 8);
|
|
TF_Buffer* values[2];
|
|
TF_OperationGetAttrTensorShapeProtoList(oper, "v", values, 2, s_);
|
|
EXPECT_EQ(TF_OK, TF_GetCode(s_)) << TF_Message(s_);
|
|
for (int i = 0; i < 2; ++i) {
|
|
int le = list_lens[i];
|
|
int la = values[i]->length;
|
|
const void* e = list_ptrs[i];
|
|
const void* a = values[i]->data;
|
|
EXPECT_EQ(le, la) << i;
|
|
EXPECT_EQ(0, memcmp(e, a, std::min(le, la))) << i;
|
|
TF_DeleteBuffer(values[i]);
|
|
}
|
|
}
|
|
|
|
TEST_F(CApiAttributesTest, Tensor) {
|
|
const char tensor[] = {5, 7};
|
|
const int64_t dims[] = {1, 2};
|
|
const size_t ndims = TF_ARRAYSIZE(dims);
|
|
|
|
auto desc = init("tensor");
|
|
unique_tensor_ptr v(Int8Tensor(dims, ndims, tensor), TF_DeleteTensor);
|
|
TF_SetAttrTensor(desc, "v", v.get(), s_);
|
|
ASSERT_EQ(TF_OK, TF_GetCode(s_)) << TF_Message(s_);
|
|
|
|
auto oper = TF_FinishOperation(desc, s_);
|
|
ASSERT_EQ(TF_OK, TF_GetCode(s_)) << TF_Message(s_);
|
|
|
|
EXPECT_TF_META("v", -1, TF_ATTR_TENSOR, -1);
|
|
TF_Tensor* value;
|
|
TF_OperationGetAttrTensor(oper, "v", &value, s_);
|
|
ASSERT_EQ(TF_OK, TF_GetCode(s_)) << TF_Message(s_);
|
|
ASSERT_NE(nullptr, value);
|
|
EXPECT_EQ(TF_INT8, TF_TensorType(value));
|
|
EXPECT_EQ(ndims, TF_NumDims(value));
|
|
for (int i = 0; i < TF_NumDims(value); ++i) {
|
|
EXPECT_EQ(dims[i], TF_Dim(value, i)) << i;
|
|
}
|
|
EXPECT_EQ(sizeof(char) * TF_ARRAYSIZE(tensor), TF_TensorByteSize(value));
|
|
EXPECT_EQ(0, memcmp(tensor, TF_TensorData(value), TF_TensorByteSize(value)));
|
|
TF_DeleteTensor(value);
|
|
}
|
|
|
|
TEST_F(CApiAttributesTest, StringTensor) {
|
|
// Create the string-Tensor "attribute" value.
|
|
char encoded[] = {
|
|
0, 0, 0, 0, 0, 0, 0, 0, // array[uint64] offsets
|
|
1, // varint encoded string length
|
|
'A',
|
|
};
|
|
auto deallocator = [](void* data, size_t len, void* arg) {};
|
|
unique_tensor_ptr t_in(TF_NewTensor(TF_STRING, nullptr, 0, &encoded[0],
|
|
sizeof(encoded), deallocator, nullptr),
|
|
TF_DeleteTensor);
|
|
|
|
// Create a TF_Operation with the attribute t_in
|
|
auto desc = init("tensor");
|
|
TF_SetAttrTensor(desc, "v", t_in.get(), s_);
|
|
ASSERT_EQ(TF_OK, TF_GetCode(s_)) << TF_Message(s_);
|
|
|
|
auto oper = TF_FinishOperation(desc, s_);
|
|
ASSERT_EQ(TF_OK, TF_GetCode(s_)) << TF_Message(s_);
|
|
|
|
// Fetch the attribute back.
|
|
EXPECT_TF_META("v", -1, TF_ATTR_TENSOR, -1);
|
|
TF_Tensor* t_out = nullptr;
|
|
TF_OperationGetAttrTensor(oper, "v", &t_out, s_);
|
|
ASSERT_EQ(TF_OK, TF_GetCode(s_)) << TF_Message(s_);
|
|
EXPECT_EQ(TF_STRING, TF_TensorType(t_out));
|
|
EXPECT_EQ(0, TF_NumDims(t_out));
|
|
ASSERT_EQ(TF_TensorByteSize(t_in.get()), TF_TensorByteSize(t_out));
|
|
EXPECT_EQ(0, memcmp(TF_TensorData(t_in.get()), TF_TensorData(t_out),
|
|
TF_TensorByteSize(t_out)));
|
|
TF_DeleteTensor(t_out);
|
|
}
|
|
|
|
TEST_F(CApiAttributesTest, TensorList) {
|
|
const char tensor1[] = {5, 7};
|
|
const int64_t dims1[] = {1, 2};
|
|
const size_t ndims1 = TF_ARRAYSIZE(dims1);
|
|
|
|
const char tensor2[] = {2, 4, 6, 8};
|
|
const int64_t dims2[] = {2, 2};
|
|
const size_t ndims2 = TF_ARRAYSIZE(dims2);
|
|
|
|
auto desc = init("list(tensor)");
|
|
TF_Tensor* tmp[] = {
|
|
Int8Tensor(dims1, ndims1, tensor1),
|
|
Int8Tensor(dims2, ndims2, tensor2),
|
|
};
|
|
TF_SetAttrTensorList(desc, "v", tmp, TF_ARRAYSIZE(tmp), s_);
|
|
for (int i = 0; i < TF_ARRAYSIZE(tmp); ++i) {
|
|
TF_DeleteTensor(tmp[i]);
|
|
}
|
|
ASSERT_EQ(TF_OK, TF_GetCode(s_)) << TF_Message(s_);
|
|
auto oper = TF_FinishOperation(desc, s_);
|
|
ASSERT_EQ(TF_OK, TF_GetCode(s_)) << TF_Message(s_);
|
|
|
|
EXPECT_TF_META("v", 2, TF_ATTR_TENSOR, -1);
|
|
TF_Tensor* values[2];
|
|
TF_OperationGetAttrTensorList(oper, "v", &values[0], TF_ARRAYSIZE(values),
|
|
s_);
|
|
ASSERT_EQ(TF_OK, TF_GetCode(s_)) << TF_Message(s_);
|
|
|
|
const char* tensor_data[] = {&tensor1[0], &tensor2[0]};
|
|
const size_t tensor_size[] = {TF_ARRAYSIZE(tensor1), TF_ARRAYSIZE(tensor2)};
|
|
const int64_t* tensor_dims[] = {&dims1[0], &dims2[0]};
|
|
const size_t tensor_ndims[] = {ndims1, ndims2};
|
|
for (int i = 0; i < 2; ++i) {
|
|
TF_Tensor* v = values[i];
|
|
ASSERT_NE(nullptr, v) << i;
|
|
EXPECT_EQ(TF_INT8, TF_TensorType(v)) << i;
|
|
EXPECT_EQ(tensor_ndims[i], TF_NumDims(v)) << i;
|
|
for (int j = 0; j < TF_NumDims(v); ++j) {
|
|
EXPECT_EQ(tensor_dims[i][j], TF_Dim(v, j))
|
|
<< "Tensor #" << i << ", dimension #" << j;
|
|
}
|
|
EXPECT_EQ(sizeof(char) * tensor_size[i], TF_TensorByteSize(v)) << i;
|
|
EXPECT_EQ(0,
|
|
memcmp(tensor_data[i], TF_TensorData(v), TF_TensorByteSize(v)));
|
|
TF_DeleteTensor(v);
|
|
}
|
|
}
|
|
|
|
TEST_F(CApiAttributesTest, EmptyList) {
|
|
auto desc = init("list(int)");
|
|
TF_SetAttrIntList(desc, "v", nullptr, 0);
|
|
auto oper = TF_FinishOperation(desc, s_);
|
|
ASSERT_EQ(TF_OK, TF_GetCode(s_)) << TF_Message(s_);
|
|
EXPECT_TF_META("v", 0, TF_ATTR_INT, -1);
|
|
}
|
|
|
|
TEST_F(CApiAttributesTest, Errors) {
|
|
auto desc = init("int");
|
|
TF_SetAttrInt(desc, "v", 3);
|
|
auto oper = TF_FinishOperation(desc, s_);
|
|
ASSERT_EQ(TF_OK, TF_GetCode(s_)) << TF_Message(s_);
|
|
TF_OperationGetAttrString(oper, "v", nullptr, 0, s_);
|
|
EXPECT_EQ(TF_INVALID_ARGUMENT, TF_GetCode(s_)) << TF_Message(s_);
|
|
}
|
|
|
|
TEST(TestApiDef, TestCreateApiDef) {
|
|
// TODO(b/73318067): Fix linking for the GPU test generated by the
|
|
// tf_cuda_cc_test() bazel rule and remove the next line.
|
|
if (!GPUDeviceName().empty()) return;
|
|
|
|
TF_Buffer* op_list_buf = TF_GetAllOpList();
|
|
TF_Status* status = TF_NewStatus();
|
|
auto* api_def_map = TF_NewApiDefMap(op_list_buf, status);
|
|
EXPECT_EQ(TF_OK, TF_GetCode(status)) << TF_Message(status);
|
|
TF_DeleteStatus(status);
|
|
|
|
string op_name = "TestCApi";
|
|
status = TF_NewStatus();
|
|
auto* api_def_buf =
|
|
TF_ApiDefMapGet(api_def_map, op_name.c_str(), op_name.size(), status);
|
|
EXPECT_EQ(TF_OK, TF_GetCode(status)) << TF_Message(status);
|
|
TF_DeleteStatus(status);
|
|
|
|
tensorflow::ApiDef api_def;
|
|
EXPECT_TRUE(api_def.ParseFromArray(api_def_buf->data, api_def_buf->length));
|
|
EXPECT_EQ(op_name, api_def.graph_op_name());
|
|
EXPECT_EQ(R"doc(Used to test C API)doc", api_def.summary());
|
|
|
|
TF_DeleteBuffer(api_def_buf);
|
|
TF_DeleteApiDefMap(api_def_map);
|
|
TF_DeleteBuffer(op_list_buf);
|
|
}
|
|
|
|
TEST(TestApiDef, TestCreateApiDefWithOverwrites) {
|
|
// TODO(b/73318067): Fix linking for the GPU test generated by the
|
|
// tf_cuda_cc_test() bazel rule and remove the next line.
|
|
if (!GPUDeviceName().empty()) return;
|
|
|
|
TF_Buffer* op_list_buf = TF_GetAllOpList();
|
|
TF_Status* status = TF_NewStatus();
|
|
auto* api_def_map = TF_NewApiDefMap(op_list_buf, status);
|
|
EXPECT_EQ(TF_OK, TF_GetCode(status)) << TF_Message(status);
|
|
TF_DeleteStatus(status);
|
|
|
|
string api_def_overwrites = R"(op: <
|
|
graph_op_name: "TestCApi"
|
|
summary: "New summary"
|
|
>
|
|
)";
|
|
status = TF_NewStatus();
|
|
TF_ApiDefMapPut(api_def_map, api_def_overwrites.c_str(),
|
|
api_def_overwrites.size(), status);
|
|
EXPECT_EQ(TF_OK, TF_GetCode(status)) << TF_Message(status);
|
|
TF_DeleteStatus(status);
|
|
|
|
string op_name = "TestCApi";
|
|
status = TF_NewStatus();
|
|
auto* api_def_buf =
|
|
TF_ApiDefMapGet(api_def_map, op_name.c_str(), op_name.size(), status);
|
|
EXPECT_EQ(TF_OK, TF_GetCode(status)) << TF_Message(status);
|
|
TF_DeleteStatus(status);
|
|
|
|
tensorflow::ApiDef api_def;
|
|
EXPECT_TRUE(api_def.ParseFromArray(api_def_buf->data, api_def_buf->length));
|
|
EXPECT_EQ(op_name, api_def.graph_op_name());
|
|
EXPECT_EQ("New summary", api_def.summary());
|
|
|
|
TF_DeleteBuffer(api_def_buf);
|
|
TF_DeleteApiDefMap(api_def_map);
|
|
TF_DeleteBuffer(op_list_buf);
|
|
}
|
|
|
|
class DummyKernel : public tensorflow::OpKernel {
|
|
public:
|
|
explicit DummyKernel(tensorflow::OpKernelConstruction* context)
|
|
: OpKernel(context) {}
|
|
void Compute(tensorflow::OpKernelContext* context) override {}
|
|
};
|
|
|
|
// Test we can query kernels
|
|
REGISTER_OP("TestOpWithSingleKernel")
|
|
.Input("a: float")
|
|
.Input("b: float")
|
|
.Output("o: float");
|
|
REGISTER_KERNEL_BUILDER(
|
|
Name("TestOpWithSingleKernel").Device(tensorflow::DEVICE_CPU), DummyKernel);
|
|
|
|
TEST(TestKernel, TestGetAllRegisteredKernels) {
|
|
TF_Status* status = TF_NewStatus();
|
|
TF_Buffer* kernel_list_buf = TF_GetAllRegisteredKernels(status);
|
|
EXPECT_EQ(TF_OK, TF_GetCode(status)) << TF_Message(status);
|
|
KernelList kernel_list;
|
|
kernel_list.ParseFromArray(kernel_list_buf->data, kernel_list_buf->length);
|
|
ASSERT_GT(kernel_list.kernel_size(), 0);
|
|
TF_DeleteBuffer(kernel_list_buf);
|
|
TF_DeleteStatus(status);
|
|
}
|
|
|
|
TEST(TestKernel, TestGetRegisteredKernelsForOp) {
|
|
TF_Status* status = TF_NewStatus();
|
|
TF_Buffer* kernel_list_buf =
|
|
TF_GetRegisteredKernelsForOp("TestOpWithSingleKernel", status);
|
|
EXPECT_EQ(TF_OK, TF_GetCode(status)) << TF_Message(status);
|
|
KernelList kernel_list;
|
|
kernel_list.ParseFromArray(kernel_list_buf->data, kernel_list_buf->length);
|
|
ASSERT_EQ(kernel_list.kernel_size(), 1);
|
|
EXPECT_EQ(kernel_list.kernel(0).op(), "TestOpWithSingleKernel");
|
|
EXPECT_EQ(kernel_list.kernel(0).device_type(), "CPU");
|
|
TF_DeleteBuffer(kernel_list_buf);
|
|
TF_DeleteStatus(status);
|
|
}
|
|
|
|
TEST(TestKernel, TestGetRegisteredKernelsForOpNoKernels) {
|
|
TF_Status* status = TF_NewStatus();
|
|
TF_Buffer* kernel_list_buf = TF_GetRegisteredKernelsForOp("Unknown", status);
|
|
EXPECT_EQ(TF_OK, TF_GetCode(status)) << TF_Message(status);
|
|
KernelList kernel_list;
|
|
kernel_list.ParseFromArray(kernel_list_buf->data, kernel_list_buf->length);
|
|
ASSERT_EQ(kernel_list.kernel_size(), 0);
|
|
TF_DeleteBuffer(kernel_list_buf);
|
|
TF_DeleteStatus(status);
|
|
}
|
|
|
|
#undef EXPECT_TF_META
|
|
|
|
TEST(CAPI, TestTensorAligned) {
|
|
int64_t dim = 7;
|
|
size_t tensor_size_bytes = dim * TF_DataTypeSize(TF_FLOAT);
|
|
TF_Tensor* a = TF_AllocateTensor(
|
|
/*dtype=*/TF_FLOAT, /*dims=*/&dim, /*num_dims=*/1,
|
|
/*len=*/tensor_size_bytes);
|
|
float* data = reinterpret_cast<float*>(TF_TensorData(a));
|
|
for (int i = 0; i < dim; ++i) {
|
|
data[i] = 0;
|
|
}
|
|
if (EIGEN_MAX_ALIGN_BYTES > 0) {
|
|
EXPECT_TRUE(TF_TensorIsAligned(a));
|
|
}
|
|
TF_DeleteTensor(a);
|
|
}
|
|
|
|
TEST(CAPI, TestTensorIsNotAligned) {
|
|
// Test unaligned access via a Slice.
|
|
Tensor x(DT_FLOAT, TensorShape({30}));
|
|
x.flat<float>().setConstant(0.0);
|
|
|
|
// Take an unaligned slice.
|
|
Tensor y = x.Slice(1, 13);
|
|
TF_Status* status = TF_NewStatus();
|
|
TF_Tensor* a = TF_TensorFromTensor(y, status);
|
|
if (EIGEN_MAX_ALIGN_BYTES > 0) {
|
|
EXPECT_FALSE(TF_TensorIsAligned(a));
|
|
}
|
|
TF_DeleteStatus(status);
|
|
TF_DeleteTensor(a);
|
|
}
|
|
|
|
} // namespace
|
|
} // namespace tensorflow
|
|
|
|
// TODO(josh11b): Test:
|
|
// * TF_SetDevice(desc, "/job:worker");
|
|
// * control inputs / outputs
|
|
// * targets
|
|
// * TF_DeleteGraph() before TF_DeleteSession()
|