/* Copyright 2019 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 #include #include #include #include "absl/memory/memory.h" #include "absl/strings/match.h" #include "tensorflow/lite/core/interpreter.h" #include "tensorflow/lite/core/model.h" #include "tensorflow/lite/experimental/resource/lookup_interfaces.h" #include "tensorflow/lite/kernels/internal/tensor_ctypes.h" #include "tensorflow/lite/kernels/test_util.h" #include "tensorflow/lite/testing/util.h" namespace tflite { // Forward declaration for op kernels. namespace ops { namespace builtin { TfLiteRegistration* Register_HASHTABLE(); TfLiteRegistration* Register_HASHTABLE_FIND(); TfLiteRegistration* Register_HASHTABLE_IMPORT(); TfLiteRegistration* Register_HASHTABLE_SIZE(); } // namespace builtin } // namespace ops namespace { using ::testing::ElementsAreArray; typedef enum { kResourceTensorId = 0, kKeyTensorId = 1, kValueTensorId = 2, kQueryTensorId = 3, kResultTensorId = 4, kSizeTensorId = 5, kDefaultValueTensorId = 6, kResourceTwoTensorId = 7, kKeyTwoTensorId = 8, kValueTwoTensorId = 9, kQueryTwoTensorId = 10, kResultTwoTensorId = 11, kSizeTwoTensorId = 12, kDefaultValueTwoTensorId = 13, } TensorIds; template void SetTensorData(Interpreter* interpreter, int tensorId, std::vector data) { auto* tensor = interpreter->tensor(tensorId); auto* tensor_data = GetTensorData(tensor); int i = 0; for (auto item : data) { tensor_data[i++] = item; } } template <> void SetTensorData(Interpreter* interpreter, int tensorId, std::vector data) { auto* tensor = interpreter->tensor(tensorId); DynamicBuffer buf; for (auto item : data) { buf.AddString(item.c_str(), item.length()); } buf.WriteToTensorAsVector(tensor); } TensorType ConvertTfLiteType(TfLiteType type) { // Currently, hashtable kernels support INT64 and STRING types only. switch (type) { case kTfLiteInt64: return TensorType_INT64; case kTfLiteString: return TensorType_STRING; default: CHECK(false); // Not reached. return TensorType_MIN; } } // HashtableGraph generates a graph with hash table ops. This class can create // the following scenarios: // // - Default graph: One hash table resource with import, lookup, and size ops. // - Graph without any import node // - Graph with two import nodes // - Graph has two hash table resources. // template class HashtableGraph { public: HashtableGraph(TfLiteType key_type, TfLiteType value_type) : key_type_(key_type), value_type_(value_type) { interpreter_ = std::make_unique(&error_reporter_); InitOpRegistrations(); } ~HashtableGraph() {} void BuildDefaultGraph() { TfLiteHashtableParams* hashtable_params = GetHashtableParams(); int node_index; // Hash table node. interpreter_->AddNodeWithParameters( {}, {kResourceTensorId}, nullptr, 0, reinterpret_cast(hashtable_params), hashtable_registration_, &node_index); // Hash table import node. interpreter_->AddNodeWithParameters( {kResourceTensorId, kKeyTensorId, kValueTensorId}, {}, nullptr, 0, nullptr, hashtable_import_registration_, &node_index); // Hash table lookup node. interpreter_->AddNodeWithParameters( {kResourceTensorId, kQueryTensorId, kDefaultValueTensorId}, {kResultTensorId}, nullptr, 0, nullptr, hashtable_find_registration_, &node_index); // Hash table size node. interpreter_->AddNodeWithParameters( {kResourceTensorId}, {kSizeTensorId}, nullptr, 0, nullptr, hashtable_size_registration_, &node_index); } void BuildNoImportGraph() { TfLiteHashtableParams* hashtable_params = GetHashtableParams(); int node_index; // Hash table node. interpreter_->AddNodeWithParameters( {}, {kResourceTensorId}, nullptr, 0, reinterpret_cast(hashtable_params), hashtable_registration_, &node_index); // Hash table lookup node. interpreter_->AddNodeWithParameters( {kResourceTensorId, kQueryTensorId, kDefaultValueTensorId}, {kResultTensorId}, nullptr, 0, nullptr, hashtable_find_registration_, &node_index); // Hash table size node. interpreter_->AddNodeWithParameters( {kResourceTensorId}, {kSizeTensorId}, nullptr, 0, nullptr, hashtable_size_registration_, &node_index); } void BuildImportTwiceGraph() { TfLiteHashtableParams* hashtable_params = GetHashtableParams(); int node_index; // Hash table node. interpreter_->AddNodeWithParameters( {}, {kResourceTensorId}, nullptr, 0, reinterpret_cast(hashtable_params), hashtable_registration_, &node_index); // Hash table import node. interpreter_->AddNodeWithParameters( {kResourceTensorId, kKeyTensorId, kValueTensorId}, {}, nullptr, 0, nullptr, hashtable_import_registration_, &node_index); // Hash table import node. interpreter_->AddNodeWithParameters( {kResourceTensorId, kKeyTensorId, kValueTensorId}, {}, nullptr, 0, nullptr, hashtable_import_registration_, &node_index); // Hash table lookup node. interpreter_->AddNodeWithParameters( {kResourceTensorId, kQueryTensorId, kDefaultValueTensorId}, {kResultTensorId}, nullptr, 0, nullptr, hashtable_find_registration_, &node_index); // Hash table size node. interpreter_->AddNodeWithParameters( {kResourceTensorId}, {kSizeTensorId}, nullptr, 0, nullptr, hashtable_size_registration_, &node_index); } void BuildTwoHashtablesGraph() { TfLiteHashtableParams* hashtable_params = GetHashtableParams(); int node_index; // Hash table node. interpreter_->AddNodeWithParameters( {}, {kResourceTensorId}, nullptr, 0, reinterpret_cast(hashtable_params), hashtable_registration_, &node_index); // Hash table import node. interpreter_->AddNodeWithParameters( {kResourceTensorId, kKeyTensorId, kValueTensorId}, {}, nullptr, 0, nullptr, hashtable_import_registration_, &node_index); // Hash table lookup node. interpreter_->AddNodeWithParameters( {kResourceTensorId, kQueryTensorId, kDefaultValueTensorId}, {kResultTensorId}, nullptr, 0, nullptr, hashtable_find_registration_, &node_index); // Hash table size node. interpreter_->AddNodeWithParameters( {kResourceTensorId}, {kSizeTensorId}, nullptr, 0, nullptr, hashtable_size_registration_, &node_index); TfLiteHashtableParams* hashtable_two_params = GetHashtableParams(); // Hash table two node. interpreter_->AddNodeWithParameters( {}, {kResourceTwoTensorId}, nullptr, 0, reinterpret_cast(hashtable_two_params), hashtable_registration_, &node_index); // Hash table two import node. interpreter_->AddNodeWithParameters( {kResourceTwoTensorId, kKeyTwoTensorId, kValueTwoTensorId}, {}, nullptr, 0, nullptr, hashtable_import_registration_, &node_index); // Hash table two lookup node. interpreter_->AddNodeWithParameters( {kResourceTwoTensorId, kQueryTwoTensorId, kDefaultValueTwoTensorId}, {kResultTwoTensorId}, nullptr, 0, nullptr, hashtable_find_registration_, &node_index); // Hash table two size node. interpreter_->AddNodeWithParameters( {kResourceTwoTensorId}, {kSizeTwoTensorId}, nullptr, 0, nullptr, hashtable_size_registration_, &node_index); } TfLiteStatus Invoke() { return interpreter_->Invoke(); } void SetTable(std::initializer_list keys, std::initializer_list values) { keys_ = std::vector(keys); values_ = std::vector(values); } void SetTableTwo(std::initializer_list keys, std::initializer_list values) { keys_two_ = std::vector(keys); values_two_ = std::vector(values); } void SetQuery(std::initializer_list queries, ValueType default_value) { queries_ = std::vector(queries); default_value_ = default_value; } void SetQueryForTableTwo(std::initializer_list queries, ValueType default_value) { queries_two_ = std::vector(queries); default_value_two_ = default_value; } int64_t GetTableSize() { auto* size_tensor = interpreter_->tensor(kSizeTensorId); auto size_tensor_shape = GetTensorShape(size_tensor); return GetTensorData(size_tensor)[0]; } int64_t GetTableTwoSize() { auto* size_tensor = interpreter_->tensor(kSizeTwoTensorId); auto size_tensor_shape = GetTensorShape(size_tensor); return GetTensorData(size_tensor)[0]; } std::vector GetLookupResult() { auto* result_tensor = interpreter_->tensor(kResultTensorId); auto result_tensor_shape = GetTensorShape(result_tensor); auto* result_tensor_data = GetTensorData(result_tensor); int size = result_tensor_shape.FlatSize(); std::vector result; for (int i = 0; i < size; ++i) { result.push_back(result_tensor_data[i]); } return result; } std::vector GetLookupTwoResult() { auto* result_tensor = interpreter_->tensor(kResultTwoTensorId); auto result_tensor_shape = GetTensorShape(result_tensor); auto* result_tensor_data = GetTensorData(result_tensor); int size = result_tensor_shape.FlatSize(); std::vector result; for (int i = 0; i < size; ++i) { result.push_back(result_tensor_data[i]); } return result; } std::vector GetStringLookupResult() { auto* result_tensor = interpreter_->tensor(kResultTensorId); auto result_tensor_shape = GetTensorShape(result_tensor); int size = result_tensor_shape.FlatSize(); std::vector result; for (int i = 0; i < size; ++i) { auto string_ref = GetString(result_tensor, i); result.push_back(std::string(string_ref.str, string_ref.len)); } return result; } void AddTensors(bool table_two_initialization = false) { int first_new_tensor_index; if (!table_two_initialization) { ASSERT_EQ(interpreter_->AddTensors(7, &first_new_tensor_index), kTfLiteOk); ASSERT_EQ(interpreter_->SetInputs({kResourceTensorId, kKeyTensorId, kValueTensorId, kQueryTensorId, kDefaultValueTensorId}), kTfLiteOk); ASSERT_EQ(interpreter_->SetOutputs({kResultTensorId, kSizeTensorId}), kTfLiteOk); } else { ASSERT_EQ(interpreter_->AddTensors(14, &first_new_tensor_index), kTfLiteOk); ASSERT_EQ( interpreter_->SetInputs( {kResourceTensorId, kKeyTensorId, kValueTensorId, kQueryTensorId, kDefaultValueTensorId, kResourceTwoTensorId, kKeyTwoTensorId, kValueTwoTensorId, kQueryTwoTensorId, kDefaultValueTwoTensorId}), kTfLiteOk); ASSERT_EQ( interpreter_->SetOutputs({kResultTensorId, kSizeTensorId, kResultTwoTensorId, kSizeTwoTensorId}), kTfLiteOk); } // Resource id tensor. interpreter_->SetTensorParametersReadWrite( kResourceTensorId, kTfLiteResource, "", {}, TfLiteQuantization()); // Key tensor for import. interpreter_->SetTensorParametersReadWrite(kKeyTensorId, key_type_, "", {static_cast(keys_.size())}, TfLiteQuantization()); // Value tensor for import. interpreter_->SetTensorParametersReadWrite( kValueTensorId, value_type_, "", {static_cast(values_.size())}, TfLiteQuantization()); // Query tensor for lookup. interpreter_->SetTensorParametersReadWrite( kQueryTensorId, key_type_, "", {static_cast(queries_.size())}, TfLiteQuantization()); // Result tensor for lookup result. interpreter_->SetTensorParametersReadWrite( kResultTensorId, value_type_, "", {static_cast(queries_.size())}, TfLiteQuantization()); // Result tensor for size calculation. interpreter_->SetTensorParametersReadWrite(kSizeTensorId, kTfLiteInt64, "", {1}, TfLiteQuantization()); // Default value tensor for lookup. interpreter_->SetTensorParametersReadWrite( kDefaultValueTensorId, value_type_, "", {1}, TfLiteQuantization()); if (table_two_initialization) { // Resource id tensor. interpreter_->SetTensorParametersReadWrite( kResourceTwoTensorId, kTfLiteResource, "", {}, TfLiteQuantization()); // Key tensor for import. interpreter_->SetTensorParametersReadWrite( kKeyTwoTensorId, key_type_, "", {static_cast(keys_two_.size())}, TfLiteQuantization()); // Value tensor for import. interpreter_->SetTensorParametersReadWrite( kValueTwoTensorId, value_type_, "", {static_cast(values_two_.size())}, TfLiteQuantization()); // Query tensor for lookup. interpreter_->SetTensorParametersReadWrite( kQueryTwoTensorId, key_type_, "", {static_cast(queries_two_.size())}, TfLiteQuantization()); // Result tensor for lookup result. interpreter_->SetTensorParametersReadWrite( kResultTwoTensorId, value_type_, "", {static_cast(queries_two_.size())}, TfLiteQuantization()); // Result tensor for size calculation. interpreter_->SetTensorParametersReadWrite(kSizeTwoTensorId, kTfLiteInt64, "", {1}, TfLiteQuantization()); // Default value tensor for lookup. interpreter_->SetTensorParametersReadWrite( kDefaultValueTwoTensorId, value_type_, "", {1}, TfLiteQuantization()); } } TfLiteStatus AllocateTensors(bool table_two_initialization = false) { if (interpreter_->AllocateTensors() != kTfLiteOk) { return kTfLiteError; } SetTensorData(interpreter_.get(), kKeyTensorId, keys_); SetTensorData(interpreter_.get(), kValueTensorId, values_); SetTensorData(interpreter_.get(), kQueryTensorId, queries_); SetTensorData(interpreter_.get(), kDefaultValueTensorId, std::vector({default_value_})); if (table_two_initialization) { SetTensorData(interpreter_.get(), kKeyTwoTensorId, keys_two_); SetTensorData(interpreter_.get(), kValueTwoTensorId, values_two_); SetTensorData(interpreter_.get(), kQueryTwoTensorId, queries_two_); SetTensorData(interpreter_.get(), kDefaultValueTwoTensorId, std::vector({default_value_two_})); } return kTfLiteOk; } TestErrorReporter* GetErrorReporter() { return &error_reporter_; } private: void InitOpRegistrations() { hashtable_registration_ = tflite::ops::builtin::Register_HASHTABLE(); ASSERT_NE(hashtable_registration_, nullptr); hashtable_find_registration_ = tflite::ops::builtin::Register_HASHTABLE_FIND(); ASSERT_NE(hashtable_find_registration_, nullptr); hashtable_import_registration_ = tflite::ops::builtin::Register_HASHTABLE_IMPORT(); ASSERT_NE(hashtable_import_registration_, nullptr); hashtable_size_registration_ = tflite::ops::builtin::Register_HASHTABLE_SIZE(); ASSERT_NE(hashtable_size_registration_, nullptr); } TfLiteHashtableParams* GetHashtableParams() { TfLiteHashtableParams* params = reinterpret_cast( malloc(sizeof(TfLiteHashtableParams))); params->table_id = std::rand(); params->key_dtype = key_type_; params->value_dtype = value_type_; return params; } // Tensor types TfLiteType key_type_; TfLiteType value_type_; // Tensor data std::vector keys_; std::vector values_; std::vector queries_; ValueType default_value_; // Tensor data for table two. std::vector keys_two_; std::vector values_two_; std::vector queries_two_; ValueType default_value_two_; // Op registrations. TfLiteRegistration* hashtable_registration_; TfLiteRegistration* hashtable_find_registration_; TfLiteRegistration* hashtable_import_registration_; TfLiteRegistration* hashtable_size_registration_; // Hashtable params. TfLiteHashtableParams* hashtable_params_; TfLiteHashtableParams* hashtable_two_params_; // Interpreter. std::unique_ptr interpreter_; TestErrorReporter error_reporter_; }; // HashtableDefaultGraphTest tests hash table features on a basic graph, created // by the HashtableGraph class. template class HashtableDefaultGraphTest { public: HashtableDefaultGraphTest(TfLiteType key_type, TfLiteType value_type, std::initializer_list keys, std::initializer_list values, std::initializer_list queries, ValueType default_value, int table_size, std::initializer_list lookup_result) { graph_ = std::make_unique>(key_type, value_type); graph_->SetTable(keys, values); graph_->SetQuery(queries, default_value); graph_->AddTensors(); graph_->BuildDefaultGraph(); value_type_ = value_type; table_size_ = table_size; lookup_result_ = std::vector(lookup_result); } void Invoke() { EXPECT_EQ(graph_->AllocateTensors(), kTfLiteOk); EXPECT_EQ(graph_->Invoke(), kTfLiteOk); EXPECT_THAT(graph_->GetTableSize(), table_size_); } void InvokeAndVerifyStringResult() { Invoke(); EXPECT_THAT(graph_->GetStringLookupResult(), ElementsAreArray(lookup_result_)); } void InvokeAndVerifyIntResult() { Invoke(); EXPECT_THAT(graph_->GetLookupResult(), ElementsAreArray(lookup_result_)); } void InvokeAndVerifyFloatResult() { Invoke(); EXPECT_THAT(graph_->GetLookupResult(), ElementsAreArray(ArrayFloatNear(lookup_result_))); } private: std::unique_ptr> graph_; TfLiteType value_type_; int table_size_; std::vector lookup_result_; }; TEST(HashtableOpsTest, TestInt64ToStringHashtable) { HashtableDefaultGraphTest t( kTfLiteInt64, kTfLiteString, /*keys=*/{1, 2, 3}, /*values=*/{"a", "b", "c"}, /*queries=*/{2, 3, 4}, /*default_value=*/"d", /*table_size=*/3, /*lookup_result=*/{"b", "c", "d"}); t.InvokeAndVerifyStringResult(); } TEST(HashtableOpsTest, TestStringToInt64Hashtable) { HashtableDefaultGraphTest t( kTfLiteString, kTfLiteInt64, /*keys=*/{"A", "B", "C"}, /*values=*/{4, 5, 6}, /*queries=*/{"B", "C", "D"}, /*default_value=*/-1, /*table_size=*/3, /*lookup_result=*/{5, 6, -1}); t.InvokeAndVerifyIntResult(); } TEST(HashtableOpsTest, TestNoImport) { HashtableGraph graph(kTfLiteString, kTfLiteInt64); graph.SetQuery({"1", "2", "3"}, -1); graph.AddTensors(); graph.BuildNoImportGraph(); EXPECT_EQ(graph.AllocateTensors(), kTfLiteOk); EXPECT_EQ(graph.Invoke(), kTfLiteError); EXPECT_TRUE( absl::StrContains(graph.GetErrorReporter()->error_messages(), "hashtable need to be initialized before using")); } TEST(HashtableOpsTest, TestImportTwice) { HashtableGraph graph(kTfLiteString, kTfLiteInt64); graph.SetTable({"1", "2", "3"}, {4, 5, 6}); graph.SetQuery({"2", "3", "4"}, -1); graph.AddTensors(); graph.BuildImportTwiceGraph(); EXPECT_EQ(graph.AllocateTensors(), kTfLiteOk); // The invocation of thesecond import node will be ignored. EXPECT_EQ(graph.Invoke(), kTfLiteOk); EXPECT_THAT(graph.GetTableSize(), 3); EXPECT_THAT(graph.GetLookupResult(), ElementsAreArray({5, 6, -1})); } TEST(HashtableOpsTest, TestTwoHashtables) { HashtableGraph graph(kTfLiteString, kTfLiteInt64); graph.SetTable({"1", "2", "3"}, {4, 5, 6}); graph.SetQuery({"2", "3", "4"}, -1); graph.SetTableTwo({"-1", "-2", "-3"}, {7, 8, 9}); graph.SetQueryForTableTwo({"-4", "-2", "-3"}, -2); graph.AddTensors(/*table_two_initialization=*/true); graph.BuildTwoHashtablesGraph(); EXPECT_EQ(graph.AllocateTensors(/*table_two_initialization=*/true), kTfLiteOk); EXPECT_EQ(graph.Invoke(), kTfLiteOk); EXPECT_THAT(graph.GetTableSize(), 3); EXPECT_THAT(graph.GetTableTwoSize(), 3); EXPECT_THAT(graph.GetLookupResult(), ElementsAreArray({5, 6, -1})); EXPECT_THAT(graph.GetLookupTwoResult(), ElementsAreArray({-2, 8, 9})); } TEST(HashtableOpsTest, TestImportDifferentKeyAndValueSize) { HashtableGraph graph(kTfLiteString, kTfLiteInt64); graph.SetTable({"1", "2", "3"}, {4, 5}); graph.SetQuery({"2", "3", "4"}, -1); graph.AddTensors(); graph.BuildDefaultGraph(); EXPECT_EQ(graph.AllocateTensors(), kTfLiteOk); EXPECT_EQ(graph.Invoke(), kTfLiteError); } // HashtableOpModel creates a model with one single Hashtable op. class HashtableOpModel : public SingleOpModel { public: explicit HashtableOpModel(const int table_id, TensorType key_dtype, TensorType value_dtype) { output_ = AddOutput(TensorType_RESOURCE); SetBuiltinOp( BuiltinOperator_HASHTABLE, BuiltinOptions_HashtableOptions, CreateHashtableOptions(builder_, table_id, key_dtype, value_dtype) .Union()); BuildInterpreter({}); } int GetOutput() { int* int32_ptr = reinterpret_cast(interpreter_->tensor(0)->data.raw); return *int32_ptr; } std::vector GetOutputShape() { return GetTensorShape(output_); } resource::ResourceMap& GetResources() { return interpreter_->primary_subgraph().resources(); } private: int output_; }; TEST(HashtableOpsTest, TestHashtable) { HashtableOpModel m(/*table_id=*/1, TensorType_INT64, TensorType_STRING); EXPECT_EQ(m.GetResources().size(), 0); ASSERT_EQ(m.Invoke(), kTfLiteOk); auto& resources = m.GetResources(); EXPECT_EQ(resources.size(), 1); int resource_id = m.GetOutput(); EXPECT_NE(resource_id, 0); auto* hashtable = resource::GetHashtableResource(&resources, resource_id); EXPECT_TRUE(hashtable != nullptr); EXPECT_TRUE(hashtable->GetKeyType() == kTfLiteInt64); EXPECT_TRUE(hashtable->GetValueType() == kTfLiteString); } template TfLiteTensor CreateTensor(TfLiteType type, const std::vector& vec) { TfLiteTensor tensor = {}; TfLiteIntArray* dims = TfLiteIntArrayCreate(1); dims->data[0] = vec.size(); tensor.dims = dims; tensor.name = ""; tensor.params = {}; tensor.quantization = {kTfLiteNoQuantization, nullptr}; tensor.is_variable = false; tensor.allocation_type = kTfLiteDynamic; tensor.allocation = nullptr; tensor.type = type; tensor.bytes = sizeof(T) * vec.size(); T* data = static_cast(malloc(sizeof(T) * vec.size())); for (int i = 0; i < vec.size(); ++i) { data[i] = vec[i]; } tensor.data.raw = reinterpret_cast(data); return tensor; } template <> TfLiteTensor CreateTensor(TfLiteType type, const std::vector& vec) { TfLiteTensor tensor = {}; TfLiteIntArray* dims = TfLiteIntArrayCreate(1); dims->data[0] = vec.size(); tensor.dims = dims; tensor.name = ""; tensor.params = {}; tensor.quantization = {kTfLiteNoQuantization, nullptr}; tensor.is_variable = false; tensor.allocation_type = kTfLiteDynamic; tensor.allocation = nullptr; tensor.type = type; DynamicBuffer buf; for (std::string str : vec) { buf.AddString(str.c_str(), str.size()); } buf.WriteToTensor(&tensor, nullptr); return tensor; } template void InitHashtableResource(resource::ResourceMap* resources, int resource_id, TfLiteType key_type, TfLiteType value_type, std::initializer_list keys, std::initializer_list values) { resource::CreateHashtableResourceIfNotAvailable(resources, resource_id, key_type, value_type); auto lookup = resource::GetHashtableResource(resources, resource_id); TfLiteContext context; TfLiteTensor key_tensor = CreateTensor(key_type, keys); TfLiteTensor value_tensor = CreateTensor(value_type, values); lookup->Import(&context, &key_tensor, &value_tensor); TfLiteTensorFree(&key_tensor); TfLiteTensorFree(&value_tensor); } // BaseHashtableOpModel is a base class for creating a model with any single // hashtable op node, which takes a hash table resource as an input. class BaseHashtableOpModel : public SingleOpModel { public: BaseHashtableOpModel() {} void SetResourceId(int resource_id) { auto* tensor = interpreter_->tensor(resource_id_); size_t bytesRequired = sizeof(int32_t); TfLiteTensorRealloc(bytesRequired, tensor); tensor->bytes = bytesRequired; TfLiteIntArray* outputSize = TfLiteIntArrayCreate(1); outputSize->data[0] = 1; if (tensor->dims) TfLiteIntArrayFree(tensor->dims); tensor->dims = outputSize; int32_t* resource_ptr = reinterpret_cast(tensor->data.raw); resource_ptr[0] = resource_id; } void CreateHashtableResource(int resource_id) { auto key_tensor = interpreter_->tensor(keys_); auto value_tensor = interpreter_->tensor(values_); auto& resources = GetResources(); resource::CreateHashtableResourceIfNotAvailable( &resources, resource_id, key_tensor->type, value_tensor->type); } template std::vector GetOutput() { return ExtractVector(output_); } std::vector GetOutputShape() { return GetTensorShape(output_); } resource::ResourceMap& GetResources() { return interpreter_->primary_subgraph().resources(); } protected: int resource_id_; int keys_; int values_; int output_; TensorType key_type_; TensorType value_type_; }; // HashtableFindOpModel creates a model with a HashtableLookup op. template class HashtableFindOpModel : public BaseHashtableOpModel { public: HashtableFindOpModel(const TensorType key_type, const TensorType value_type, int lookup_size) { key_type_ = key_type; value_type_ = value_type; resource_id_ = AddInput({TensorType_RESOURCE, {1}}); lookup_ = AddInput({key_type, {lookup_size}}); default_value_ = AddInput({value_type, {1}}); output_ = AddOutput({value_type, {lookup_size}}); SetBuiltinOp(BuiltinOperator_HASHTABLE_FIND, BuiltinOptions_HashtableFindOptions, CreateHashtableFindOptions(builder_).Union()); BuildInterpreter( {GetShape(resource_id_), GetShape(lookup_), GetShape(default_value_)}); } void SetLookup(const std::vector& data) { PopulateTensor(lookup_, data); } void SetStringLookup(const std::vector& data) { PopulateStringTensor(lookup_, data); } void SetDefaultValue(const std::vector& data) { PopulateTensor(default_value_, data); } void SetStringDefaultValue(const std::vector& data) { PopulateStringTensor(default_value_, data); } private: int lookup_; int default_value_; }; TEST(HashtableOpsTest, TestHashtableLookupStringToInt64) { const int kResourceId = 42; HashtableFindOpModel m(TensorType_STRING, TensorType_INT64, 3); m.SetResourceId(kResourceId); m.SetStringLookup({"5", "6", "7"}); m.SetDefaultValue({4}); InitHashtableResource( &m.GetResources(), kResourceId, kTfLiteString, kTfLiteInt64, {"4", "5", "6"}, {1, 2, 3}); ASSERT_EQ(m.Invoke(), kTfLiteOk); EXPECT_THAT(m.GetOutput(), ElementsAreArray({2, 3, 4})); EXPECT_THAT(m.GetOutputShape(), ElementsAreArray({3})); } TEST(HashtableOpsTest, TestHashtableLookupInt64ToString) { const int kResourceId = 42; HashtableFindOpModel m(TensorType_INT64, TensorType_STRING, 3); m.SetResourceId(kResourceId); m.SetLookup({5, 6, 7}); m.SetStringDefaultValue({"4"}); InitHashtableResource( &m.GetResources(), kResourceId, kTfLiteInt64, kTfLiteString, {4, 5, 6}, {"1", "2", "3"}); ASSERT_EQ(m.Invoke(), kTfLiteOk); EXPECT_THAT(m.GetOutput(), ElementsAreArray({"2", "3", "4"})); EXPECT_THAT(m.GetOutputShape(), ElementsAreArray({3})); } // HashtableImportOpModel creates a model with a HashtableImport op. template class HashtableImportOpModel : public BaseHashtableOpModel { public: HashtableImportOpModel(const TensorType key_type, const TensorType value_type, int initdata_size) { key_type_ = key_type; value_type_ = value_type; resource_id_ = AddInput({TensorType_RESOURCE, {1}}); keys_ = AddInput({key_type, {initdata_size}}); values_ = AddInput({value_type, {initdata_size}}); SetBuiltinOp(BuiltinOperator_HASHTABLE_IMPORT, BuiltinOptions_HashtableImportOptions, CreateHashtableImportOptions(builder_).Union()); BuildInterpreter( {GetShape(resource_id_), GetShape(keys_), GetShape(values_)}); } void SetKeys(const std::vector& data) { PopulateTensor(keys_, data); } void SetStringKeys(const std::vector& data) { PopulateStringTensor(keys_, data); } void SetValues(const std::vector& data) { PopulateTensor(values_, data); } void SetStringValues(const std::vector& data) { PopulateStringTensor(values_, data); } }; TEST(HashtableOpsTest, TestHashtableImport) { const int kResourceId = 42; HashtableImportOpModel m(TensorType_INT64, TensorType_STRING, 3); EXPECT_EQ(m.GetResources().size(), 0); m.SetResourceId(kResourceId); m.SetKeys({1, 2, 3}); m.SetStringValues({"1", "2", "3"}); m.CreateHashtableResource(kResourceId); ASSERT_EQ(m.Invoke(), kTfLiteOk); auto& resources = m.GetResources(); EXPECT_EQ(resources.size(), 1); auto* hashtable = resource::GetHashtableResource(&resources, kResourceId); EXPECT_TRUE(hashtable != nullptr); EXPECT_TRUE(hashtable->GetKeyType() == kTfLiteInt64); EXPECT_TRUE(hashtable->GetValueType() == kTfLiteString); EXPECT_EQ(hashtable->Size(), 3); } TEST(HashtableOpsTest, TestHashtableImportTwice) { const int kResourceId = 42; HashtableImportOpModel m(TensorType_INT64, TensorType_STRING, 3); EXPECT_EQ(m.GetResources().size(), 0); m.SetResourceId(kResourceId); m.SetKeys({1, 2, 3}); m.SetStringValues({"1", "2", "3"}); m.CreateHashtableResource(kResourceId); ASSERT_EQ(m.Invoke(), kTfLiteOk); ASSERT_EQ(m.Invoke(), kTfLiteOk); auto& resources = m.GetResources(); EXPECT_EQ(resources.size(), 1); auto* hashtable = resource::GetHashtableResource(&resources, kResourceId); EXPECT_TRUE(hashtable != nullptr); EXPECT_TRUE(hashtable->GetKeyType() == kTfLiteInt64); EXPECT_TRUE(hashtable->GetValueType() == kTfLiteString); EXPECT_EQ(hashtable->Size(), 3); } // HashtableSizeOpModel creates a model with a HashtableSize op. template class HashtableSizeOpModel : public BaseHashtableOpModel { public: HashtableSizeOpModel(const TensorType key_type, const TensorType value_type) { key_type_ = key_type; value_type_ = value_type; resource_id_ = AddInput({TensorType_RESOURCE, {1}}); output_ = AddOutput({TensorType_INT64, {1}}); SetBuiltinOp(BuiltinOperator_HASHTABLE_SIZE, BuiltinOptions_HashtableSizeOptions, CreateHashtableSizeOptions(builder_).Union()); BuildInterpreter({GetShape(resource_id_)}); } }; TEST(HashtableOpsTest, TestHashtableSize) { const int kResourceId = 42; HashtableSizeOpModel m(TensorType_STRING, TensorType_INT64); m.SetResourceId(kResourceId); InitHashtableResource( &m.GetResources(), kResourceId, kTfLiteString, kTfLiteInt64, {"4", "5", "6"}, {1, 2, 3}); ASSERT_EQ(m.Invoke(), kTfLiteOk); EXPECT_THAT(m.GetOutput(), ElementsAreArray({3})); EXPECT_THAT(m.GetOutputShape(), ElementsAreArray({1})); } TEST(HashtableOpsTest, TestHashtableSizeNonInitialized) { const int kResourceId = 42; HashtableSizeOpModel m(TensorType_STRING, TensorType_INT64); m.SetResourceId(kResourceId); // Invoke without hash table initialization. EXPECT_NE(m.Invoke(), kTfLiteOk); } } // namespace } // namespace tflite