Spaces:
Running
Running
| // Copyright 2025 The ODML Authors. | |
| // | |
| // 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. | |
| namespace litert::lm { | |
| constexpr char kTestdataDir[] = | |
| "litert_lm/runtime/components/testdata/"; | |
| class EmbeddingLookupTextTest : public testing::Test { | |
| protected: | |
| absl::Status CreateModelFromFile() { | |
| // The model is a dummy embedding model that takes a token as input and | |
| // returns an embedding vector. The embedding table has the shape | |
| // [10, 1, 4, 32]. | |
| // The values in the table are generated by the following formula: | |
| // for idx_0 in range(dim[0]): | |
| // for idx_1 in range(dim[1]): | |
| // for idx_2 in range(dim[2]): | |
| // for idx_3 in range(dim[3]): | |
| // table[idx_0, idx_1, idx_2, idx_3] = | |
| // idx_0*10000 + idx_1*1000 + idx_2*100 + idx_3 | |
| auto model_path = std::filesystem::path(::testing::SrcDir()) / | |
| kTestdataDir / "dummy_embedding_cpu_model.tflite"; | |
| LITERT_ASSIGN_OR_RETURN(model_, Model::CreateFromFile(model_path.string())); | |
| return absl::OkStatus(); | |
| } | |
| std::unique_ptr<EmbeddingLookupText> GetEmbeddingLookupText( | |
| std::optional<std::string> signature_key = std::nullopt) { | |
| if (!CreateModelFromFile().ok()) { | |
| return nullptr; | |
| } | |
| if (!model_.has_value()) { | |
| return nullptr; | |
| } | |
| auto status = EmbeddingLookupText::Create(&*model_, signature_key, &*env_); | |
| if (!status.ok()) { | |
| return nullptr; | |
| } | |
| return std::move(status.value()); | |
| } | |
| Expected<TensorBuffer> GetTensorBuffer(Dimensions& dimensions) { | |
| size_t buffer_size = sizeof(float); | |
| for (auto dim : dimensions) { | |
| buffer_size *= dim; | |
| } | |
| Layout layout(dimensions); | |
| RankedTensorType ranked_tensor_type(ElementType::Float32, | |
| std::move(layout)); | |
| return TensorBuffer::CreateManaged(*env_, | |
| ::litert::TensorBufferType::kHostMemory, | |
| ranked_tensor_type, buffer_size); | |
| } | |
| Expected<Environment> env_ = Environment::Create({}); | |
| std::optional<Model> model_; | |
| }; | |
| TEST_F(EmbeddingLookupTextTest, LookupDecodeVector) { | |
| std::unique_ptr<EmbeddingLookupText> embedding = GetEmbeddingLookupText(); | |
| EXPECT_NE(embedding, nullptr); | |
| std::vector<float> output_vector(4 * 32); | |
| int32_t token = 1; | |
| EXPECT_OK(embedding->LookupDecode(token, output_vector)); | |
| size_t offset = 0; | |
| // Dimensions 0 and 1 both have size 1. | |
| for (int idx2 = 0; idx2 < 4; ++idx2) { | |
| for (int idx3 = 0; idx3 < 32; ++idx3) { | |
| // Dimensions 0 and 1 both have size 1 so offset and expected value can | |
| // ignore them. | |
| float expected_value = 10000.0 * token + 100.0 * idx2 + idx3; | |
| EXPECT_NEAR(output_vector[offset++], expected_value, 1e-5); | |
| } | |
| } | |
| } | |
| TEST_F(EmbeddingLookupTextTest, LookupDecodeVectorBadOutputVector) { | |
| std::unique_ptr<EmbeddingLookupText> embedding = GetEmbeddingLookupText(); | |
| EXPECT_NE(embedding, nullptr); | |
| std::vector<float> output_vector(4 * 32 + 1); | |
| int32_t token = 1; | |
| EXPECT_THAT( | |
| embedding->LookupDecode(token, output_vector), | |
| testing::status::StatusIs( | |
| absl::StatusCode::kInvalidArgument, | |
| testing::HasSubstr( | |
| "The text embedding lookup output vector must be of size"))); | |
| } | |
| TEST_F(EmbeddingLookupTextTest, LookupDecodeVectorNegativeToken) { | |
| std::unique_ptr<EmbeddingLookupText> embedding = GetEmbeddingLookupText(); | |
| EXPECT_NE(embedding, nullptr); | |
| std::vector<float> output_vector(4 * 32); | |
| int32_t token = -1; | |
| EXPECT_OK(embedding->LookupDecode(token, output_vector)); | |
| size_t offset = 0; | |
| // Dimensions 0 and 1 both have size 1. | |
| for (int idx2 = 0; idx2 < 4; ++idx2) { | |
| for (int idx3 = 0; idx3 < 32; ++idx3) { | |
| // Dimensions 0 and 1 both have size 1 so offset and expected value can | |
| // ignore them. | |
| float expected_value = 10000.0 * 0 + 100.0 * idx2 + idx3; | |
| EXPECT_NEAR(output_vector[offset++], expected_value, 1e-5); | |
| } | |
| } | |
| } | |
| TEST_F(EmbeddingLookupTextTest, LookupDecode) { | |
| std::unique_ptr<EmbeddingLookupText> embedding = GetEmbeddingLookupText(); | |
| EXPECT_NE(embedding, nullptr); | |
| Dimensions dimensions({1, 1, 4, 32}); | |
| LITERT_ASSERT_OK_AND_ASSIGN(TensorBuffer output_tensor, | |
| GetTensorBuffer(dimensions)); | |
| int32_t token = 1; | |
| EXPECT_OK(embedding->LookupDecode(token, &output_tensor)); | |
| auto output_tensor_lock_and_addr = ::litert::TensorBufferScopedLock::Create( | |
| output_tensor, ::litert::TensorBuffer::LockMode::kRead); | |
| auto output_tensor_ptr = | |
| reinterpret_cast<float*>(output_tensor_lock_and_addr->second); | |
| // Dimensions 0 and 1 both have size 1. | |
| for (int idx2 = 0; idx2 < dimensions[2]; ++idx2) { | |
| for (int idx3 = 0; idx3 < dimensions[3]; ++idx3) { | |
| // Dimensions 0 and 1 both have size 1 so offset and expected value can | |
| // ignore them. | |
| size_t offset = idx2 * dimensions[3] + idx3; | |
| float expected_value = 10000.0 * token + 100.0 * idx2 + idx3; | |
| EXPECT_NEAR(output_tensor_ptr[offset], expected_value, 1e-5); | |
| } | |
| } | |
| } | |
| TEST_F(EmbeddingLookupTextTest, LookupDecodeBadOutputTensorDimNum) { | |
| std::unique_ptr<EmbeddingLookupText> embedding = GetEmbeddingLookupText(); | |
| EXPECT_NE(embedding, nullptr); | |
| Dimensions dimensions({1, 1, 4}); | |
| LITERT_ASSERT_OK_AND_ASSIGN(TensorBuffer output_tensor, | |
| GetTensorBuffer(dimensions)); | |
| EXPECT_THAT(embedding->LookupDecode(1, &output_tensor), | |
| testing::status::StatusIs( | |
| absl::StatusCode::kInvalidArgument, | |
| testing::HasSubstr("The output tensor from the Embedding " | |
| "model must be have the same " | |
| "number of dimensions"))); | |
| } | |
| TEST_F(EmbeddingLookupTextTest, LookupDecodeBadOutputTensorDimSize) { | |
| std::unique_ptr<EmbeddingLookupText> embedding = GetEmbeddingLookupText(); | |
| EXPECT_NE(embedding, nullptr); | |
| Dimensions dimensions({1, 1, 4, 256}); | |
| LITERT_ASSERT_OK_AND_ASSIGN(TensorBuffer output_tensor, | |
| GetTensorBuffer(dimensions)); | |
| EXPECT_THAT(embedding->LookupDecode(1, &output_tensor), | |
| testing::status::StatusIs( | |
| absl::StatusCode::kInvalidArgument, | |
| testing::HasSubstr("The output tensor from the Embedding " | |
| "model must be have the same " | |
| "dimensions"))); | |
| } | |
| TEST_F(EmbeddingLookupTextTest, LookupDecodeNullOutputTensor) { | |
| std::unique_ptr<EmbeddingLookupText> embedding = GetEmbeddingLookupText(); | |
| EXPECT_NE(embedding, nullptr); | |
| EXPECT_THAT(embedding->LookupDecode(1, nullptr), | |
| testing::status::StatusIs( | |
| absl::StatusCode::kInvalidArgument, | |
| testing::HasSubstr("Decode output tensor buffer is null"))); | |
| } | |
| TEST_F(EmbeddingLookupTextTest, LookupPrefillVector) { | |
| std::unique_ptr<EmbeddingLookupText> embedding = GetEmbeddingLookupText(); | |
| EXPECT_NE(embedding, nullptr); | |
| std::vector<float> output_vector(4 * 32); | |
| int32_t token = 1; | |
| EXPECT_OK(embedding->LookupPrefill(token, output_vector)); | |
| size_t offset = 0; | |
| // Dimensions 0 and 1 both have size 1. | |
| for (int idx2 = 0; idx2 < 4; ++idx2) { | |
| for (int idx3 = 0; idx3 < 32; ++idx3) { | |
| // Dimensions 0 and 1 both have size 1 so offset and expected value can | |
| // ignore them. | |
| float expected_value = 10000.0 * token + 100.0 * idx2 + idx3; | |
| EXPECT_NEAR(output_vector[offset++], expected_value, 1e-5); | |
| } | |
| } | |
| } | |
| TEST_F(EmbeddingLookupTextTest, LookupPrefillVectorBadOutputVector) { | |
| std::unique_ptr<EmbeddingLookupText> embedding = GetEmbeddingLookupText(); | |
| EXPECT_NE(embedding, nullptr); | |
| std::vector<float> output_vector(4 * 32 + 1); | |
| int32_t token = 1; | |
| EXPECT_THAT( | |
| embedding->LookupPrefill(token, output_vector), | |
| testing::status::StatusIs( | |
| absl::StatusCode::kInvalidArgument, | |
| testing::HasSubstr( | |
| "The text embedding lookup output vector must be of size"))); | |
| } | |
| TEST_F(EmbeddingLookupTextTest, LookupPrefillVectorNegativeToken) { | |
| std::unique_ptr<EmbeddingLookupText> embedding = GetEmbeddingLookupText(); | |
| EXPECT_NE(embedding, nullptr); | |
| std::vector<float> output_vector(4 * 32); | |
| int32_t token = -1; | |
| EXPECT_OK(embedding->LookupPrefill(token, output_vector)); | |
| size_t offset = 0; | |
| // Dimensions 0 and 1 both have size 1. | |
| for (int idx2 = 0; idx2 < 4; ++idx2) { | |
| for (int idx3 = 0; idx3 < 32; ++idx3) { | |
| // Dimensions 0 and 1 both have size 1 so offset and expected value can | |
| // ignore them. | |
| float expected_value = 10000.0 * 0 + 100.0 * idx2 + idx3; | |
| EXPECT_NEAR(output_vector[offset++], expected_value, 1e-5); | |
| } | |
| } | |
| } | |
| TEST_F(EmbeddingLookupTextTest, GetFloatsPerToken) { | |
| std::unique_ptr<EmbeddingLookupText> embedding = GetEmbeddingLookupText(); | |
| EXPECT_NE(embedding, nullptr); | |
| EXPECT_EQ(embedding->GetFloatsPerToken(), 4 * 32); | |
| } | |
| TEST_F(EmbeddingLookupTextTest, LookupPrefill) { | |
| std::unique_ptr<EmbeddingLookupText> embedding = GetEmbeddingLookupText(); | |
| EXPECT_NE(embedding, nullptr); | |
| Dimensions dimensions({1, 3, 4, 32}); | |
| LITERT_ASSERT_OK_AND_ASSIGN(TensorBuffer output_tensor, | |
| GetTensorBuffer(dimensions)); | |
| std::vector<int> tokens = {1, 2, 3}; | |
| absl::Span<const int> tokens_span(tokens); | |
| EXPECT_OK(embedding->LookupPrefill(tokens_span, &output_tensor, 0)); | |
| auto output_tensor_lock_and_addr = ::litert::TensorBufferScopedLock::Create( | |
| output_tensor, ::litert::TensorBuffer::LockMode::kRead); | |
| auto output_tensor_ptr = | |
| reinterpret_cast<float*>(output_tensor_lock_and_addr->second); | |
| for (int idx0 = 0; idx0 < tokens.size(); ++idx0) { | |
| int token = tokens[idx0]; | |
| for (int idx2 = 0; idx2 < dimensions[2]; ++idx2) { | |
| for (int idx3 = 0; idx3 < dimensions[3]; ++idx3) { | |
| // Since dimension 1 is of size 1, the offset and expected value can | |
| // ignore it. | |
| size_t offset = | |
| idx0 * dimensions[2] * dimensions[3] + idx2 * dimensions[3] + idx3; | |
| float expected_value = 10000.0 * token + 100.0 * idx2 + idx3; | |
| EXPECT_NEAR(output_tensor_ptr[offset], expected_value, 1e-5); | |
| } | |
| } | |
| } | |
| } | |
| TEST_F(EmbeddingLookupTextTest, LookupPrefillDecendingTokens) { | |
| std::unique_ptr<EmbeddingLookupText> embedding = GetEmbeddingLookupText(); | |
| EXPECT_NE(embedding, nullptr); | |
| Dimensions dimensions({1, 3, 4, 32}); | |
| LITERT_ASSERT_OK_AND_ASSIGN(TensorBuffer output_tensor, | |
| GetTensorBuffer(dimensions)); | |
| std::vector<int> tokens = {3, 2, 1}; | |
| absl::Span<const int> tokens_span(tokens); | |
| EXPECT_OK(embedding->LookupPrefill(tokens_span, &output_tensor, 0)); | |
| auto output_tensor_lock_and_addr = ::litert::TensorBufferScopedLock::Create( | |
| output_tensor, ::litert::TensorBuffer::LockMode::kRead); | |
| auto output_tensor_ptr = | |
| reinterpret_cast<float*>(output_tensor_lock_and_addr->second); | |
| for (int idx0 = 0; idx0 < tokens.size(); ++idx0) { | |
| int token = tokens[idx0]; | |
| for (int idx2 = 0; idx2 < dimensions[2]; ++idx2) { | |
| for (int idx3 = 0; idx3 < dimensions[3]; ++idx3) { | |
| // Since dimension 1 is of size 1, the offset and expected value can | |
| // ignore it. | |
| size_t offset = | |
| idx0 * dimensions[2] * dimensions[3] + idx2 * dimensions[3] + idx3; | |
| float expected_value = 10000.0 * token + 100.0 * idx2 + idx3; | |
| EXPECT_NEAR(output_tensor_ptr[offset], expected_value, 1e-5); | |
| } | |
| } | |
| } | |
| } | |
| TEST_F(EmbeddingLookupTextTest, LookupPrefillRepeatedToken) { | |
| std::unique_ptr<EmbeddingLookupText> embedding = GetEmbeddingLookupText(); | |
| EXPECT_NE(embedding, nullptr); | |
| Dimensions dimensions({1, 3, 4, 32}); | |
| LITERT_ASSERT_OK_AND_ASSIGN(TensorBuffer output_tensor, | |
| GetTensorBuffer(dimensions)); | |
| std::vector<int> tokens = {1, 1, 1}; | |
| absl::Span<const int> tokens_span(tokens); | |
| EXPECT_OK(embedding->LookupPrefill(tokens_span, &output_tensor, 0)); | |
| auto output_tensor_lock_and_addr = ::litert::TensorBufferScopedLock::Create( | |
| output_tensor, ::litert::TensorBuffer::LockMode::kRead); | |
| auto output_tensor_ptr = | |
| reinterpret_cast<float*>(output_tensor_lock_and_addr->second); | |
| for (int idx0 = 0; idx0 < tokens.size(); ++idx0) { | |
| int token = tokens[idx0]; | |
| for (int idx2 = 0; idx2 < dimensions[2]; ++idx2) { | |
| for (int idx3 = 0; idx3 < dimensions[3]; ++idx3) { | |
| // Since dimension 1 is of size 1, the offset and expected value can | |
| // ignore it. | |
| size_t offset = | |
| idx0 * dimensions[2] * dimensions[3] + idx2 * dimensions[3] + idx3; | |
| float expected_value = 10000.0 * token + 100.0 * idx2 + idx3; | |
| EXPECT_NEAR(output_tensor_ptr[offset], expected_value, 1e-5); | |
| } | |
| } | |
| } | |
| } | |
| TEST_F(EmbeddingLookupTextTest, LookupPrefillBadOutputTensorDimNum) { | |
| std::unique_ptr<EmbeddingLookupText> embedding = GetEmbeddingLookupText(); | |
| EXPECT_NE(embedding, nullptr); | |
| Dimensions dimensions({1, 3, 4}); | |
| LITERT_ASSERT_OK_AND_ASSIGN(TensorBuffer output_tensor, | |
| GetTensorBuffer(dimensions)); | |
| std::vector<int> tokens = {1, 2, 3}; | |
| absl::Span<const int> tokens_span(tokens); | |
| EXPECT_THAT(embedding->LookupPrefill(tokens_span, &output_tensor, 0), | |
| testing::status::StatusIs( | |
| absl::StatusCode::kInvalidArgument, | |
| testing::HasSubstr("The output tensor from the Embedding " | |
| "model must be have the same " | |
| "number of dimensions"))); | |
| } | |
| TEST_F(EmbeddingLookupTextTest, LookupPrefillBadOutputTensorDimSize) { | |
| std::unique_ptr<EmbeddingLookupText> embedding = GetEmbeddingLookupText(); | |
| EXPECT_NE(embedding, nullptr); | |
| Dimensions dimensions({1, 3, 4, 256}); | |
| LITERT_ASSERT_OK_AND_ASSIGN(TensorBuffer output_tensor, | |
| GetTensorBuffer(dimensions)); | |
| std::vector<int> tokens = {1, 2, 3}; | |
| absl::Span<const int> tokens_span(tokens); | |
| EXPECT_THAT(embedding->LookupPrefill(tokens_span, &output_tensor, 0), | |
| testing::status::StatusIs( | |
| absl::StatusCode::kInvalidArgument, | |
| testing::HasSubstr("The output tensor from the Embedding " | |
| "model must be have the same " | |
| "dimensions"))); | |
| } | |
| TEST_F(EmbeddingLookupTextTest, LookupPrefillBadOutputTensorDim0) { | |
| std::unique_ptr<EmbeddingLookupText> embedding = GetEmbeddingLookupText(); | |
| EXPECT_NE(embedding, nullptr); | |
| Dimensions dimensions({3, 1, 4, 256}); | |
| LITERT_ASSERT_OK_AND_ASSIGN(TensorBuffer output_tensor, | |
| GetTensorBuffer(dimensions)); | |
| std::vector<int> tokens = {1, 2, 3}; | |
| absl::Span<const int> tokens_span(tokens); | |
| EXPECT_THAT( | |
| embedding->LookupPrefill(tokens_span, &output_tensor, 0), | |
| testing::status::StatusIs( | |
| absl::StatusCode::kUnimplemented, | |
| testing::HasSubstr("The output tensor to fill from the Embedding " | |
| "model must be have the 0th dimension as 1."))); | |
| } | |
| TEST_F(EmbeddingLookupTextTest, LookupPrefillBadOutputTensorDim1) { | |
| std::unique_ptr<EmbeddingLookupText> embedding = GetEmbeddingLookupText(); | |
| EXPECT_NE(embedding, nullptr); | |
| Dimensions dimensions({1, 1, 4, 256}); | |
| LITERT_ASSERT_OK_AND_ASSIGN(TensorBuffer output_tensor, | |
| GetTensorBuffer(dimensions)); | |
| std::vector<int> tokens = {1, 2, 3}; | |
| absl::Span<const int> tokens_span(tokens); | |
| EXPECT_THAT( | |
| embedding->LookupPrefill(tokens_span, &output_tensor, 0), | |
| testing::status::StatusIs( | |
| absl::StatusCode::kInvalidArgument, | |
| testing::HasSubstr("The output tensor to fill from the Embedding " | |
| "model must have a 1st dimension that is at least " | |
| "the same size as the number of tokens"))); | |
| } | |
| TEST_F(EmbeddingLookupTextTest, LookupPrefillLargerOutputTensor) { | |
| std::unique_ptr<EmbeddingLookupText> embedding = GetEmbeddingLookupText(); | |
| EXPECT_NE(embedding, nullptr); | |
| Dimensions dimensions({1, 4, 4, 32}); | |
| LITERT_ASSERT_OK_AND_ASSIGN(TensorBuffer output_tensor, | |
| GetTensorBuffer(dimensions)); | |
| std::vector<int> tokens = {1, 2, 3}; | |
| absl::Span<const int> tokens_span(tokens); | |
| EXPECT_OK(embedding->LookupPrefill(tokens_span, &output_tensor, 0)); | |
| auto output_tensor_lock_and_addr = ::litert::TensorBufferScopedLock::Create( | |
| output_tensor, ::litert::TensorBuffer::LockMode::kRead); | |
| auto output_tensor_ptr = | |
| reinterpret_cast<float*>(output_tensor_lock_and_addr->second); | |
| for (int idx0 = 0; idx0 < tokens.size() + 1; ++idx0) { | |
| int token = 0; | |
| if (idx0 < tokens.size()) { | |
| token = tokens[idx0]; | |
| } | |
| for (int idx2 = 0; idx2 < dimensions[2]; ++idx2) { | |
| for (int idx3 = 0; idx3 < dimensions[3]; ++idx3) { | |
| // Since dimension 1 is of size 1, the offset and expected value can | |
| // ignore it. | |
| size_t offset = | |
| idx0 * dimensions[2] * dimensions[3] + idx2 * dimensions[3] + idx3; | |
| float expected_value = 10000.0 * token + 100.0 * idx2 + idx3; | |
| EXPECT_NEAR(output_tensor_ptr[offset], expected_value, 1e-5); | |
| } | |
| } | |
| } | |
| } | |
| TEST_F(EmbeddingLookupTextTest, LookupPrefillNullOutputTensor) { | |
| std::unique_ptr<EmbeddingLookupText> embedding = GetEmbeddingLookupText(); | |
| EXPECT_NE(embedding, nullptr); | |
| std::vector<int> tokens = {1, 2, 3}; | |
| absl::Span<const int> tokens_span(tokens); | |
| EXPECT_THAT(embedding->LookupPrefill(tokens_span, nullptr, 0), | |
| testing::status::StatusIs( | |
| absl::StatusCode::kInvalidArgument, | |
| testing::HasSubstr("Prefill output tensor buffer is null"))); | |
| } | |
| TEST_F(EmbeddingLookupTextTest, LookupPrefillNegativeToken) { | |
| std::unique_ptr<EmbeddingLookupText> embedding = GetEmbeddingLookupText(); | |
| EXPECT_NE(embedding, nullptr); | |
| Dimensions dimensions({1, 3, 4, 32}); | |
| LITERT_ASSERT_OK_AND_ASSIGN(TensorBuffer output_tensor, | |
| GetTensorBuffer(dimensions)); | |
| float filler_value = 9999.0; | |
| { | |
| auto output_tensor_lock_and_addr = ::litert::TensorBufferScopedLock::Create( | |
| output_tensor, ::litert::TensorBuffer::LockMode::kWrite); | |
| auto output_tensor_ptr = | |
| reinterpret_cast<float*>(output_tensor_lock_and_addr->second); | |
| LITERT_ASSERT_OK_AND_ASSIGN(size_t output_tensor_size, | |
| output_tensor.Size()); | |
| for (int i = 0; i < output_tensor_size / sizeof(float); ++i) { | |
| output_tensor_ptr[i] = filler_value; | |
| } | |
| } | |
| std::vector<int> tokens = {1, -1, 2}; | |
| absl::Span<const int> tokens_span(tokens); | |
| EXPECT_OK(embedding->LookupPrefill(tokens_span, &output_tensor, 0)); | |
| auto output_tensor_lock_and_addr = ::litert::TensorBufferScopedLock::Create( | |
| output_tensor, ::litert::TensorBuffer::LockMode::kRead); | |
| auto output_tensor_ptr = | |
| reinterpret_cast<float*>(output_tensor_lock_and_addr->second); | |
| for (int idx0 = 0; idx0 < tokens.size(); ++idx0) { | |
| int token = tokens[idx0]; | |
| for (int idx2 = 0; idx2 < dimensions[2]; ++idx2) { | |
| for (int idx3 = 0; idx3 < dimensions[3]; ++idx3) { | |
| float expected_value; | |
| if (token < 0) { | |
| // If the token is negative, the expected value is the value of token | |
| // 0. | |
| expected_value = 10000.0 * 0 + 100.0 * idx2 + idx3; | |
| } else { | |
| // Since dimension 1 is of size 1, the offset and expected value can | |
| // ignore it. | |
| expected_value = 10000.0 * token + 100.0 * idx2 + idx3; | |
| } | |
| size_t offset = | |
| idx0 * dimensions[2] * dimensions[3] + idx2 * dimensions[3] + idx3; | |
| EXPECT_NEAR(output_tensor_ptr[offset], expected_value, 1e-5); | |
| } | |
| } | |
| } | |
| } | |
| TEST_F(EmbeddingLookupTextTest, LookupPrefillWithOffset) { | |
| std::unique_ptr<EmbeddingLookupText> embedding = GetEmbeddingLookupText(); | |
| EXPECT_NE(embedding, nullptr); | |
| Dimensions dimensions({1, 3, 4, 32}); | |
| LITERT_ASSERT_OK_AND_ASSIGN(TensorBuffer output_tensor, | |
| GetTensorBuffer(dimensions)); | |
| { | |
| auto output_tensor_lock_and_addr = ::litert::TensorBufferScopedLock::Create( | |
| output_tensor, ::litert::TensorBuffer::LockMode::kWrite); | |
| auto output_tensor_ptr = | |
| reinterpret_cast<float*>(output_tensor_lock_and_addr->second); | |
| LITERT_ASSERT_OK_AND_ASSIGN(size_t output_tensor_size, | |
| output_tensor.Size()); | |
| memset(output_tensor_ptr, 99, output_tensor_size); | |
| } | |
| std::vector<int> tokens = {1, 2}; | |
| absl::Span<const int> tokens_span(tokens); | |
| const size_t token_offset = 1; | |
| const size_t float_offset = token_offset * 4 * 32; | |
| const size_t byte_offset = float_offset * sizeof(float); | |
| EXPECT_OK(embedding->LookupPrefill(tokens_span, &output_tensor, byte_offset)); | |
| auto output_tensor_lock_and_addr = ::litert::TensorBufferScopedLock::Create( | |
| output_tensor, ::litert::TensorBuffer::LockMode::kRead); | |
| auto output_tensor_ptr = | |
| reinterpret_cast<float*>(output_tensor_lock_and_addr->second); | |
| for (int i = 0; i < byte_offset; ++i) { | |
| EXPECT_EQ(reinterpret_cast<uint8_t*>(output_tensor_ptr)[i], 99); | |
| } | |
| for (int idx0 = 0; idx0 < tokens.size(); ++idx0) { | |
| int token = tokens[idx0]; | |
| for (int idx2 = 0; idx2 < dimensions[2]; ++idx2) { | |
| for (int idx3 = 0; idx3 < dimensions[3]; ++idx3) { | |
| // Since dimension 1 is of size 1, the offset and expected value can | |
| // ignore it. | |
| size_t offset = float_offset + idx0 * dimensions[2] * dimensions[3] + | |
| idx2 * dimensions[3] + idx3; | |
| float expected_value = 10000.0 * token + 100.0 * idx2 + idx3; | |
| EXPECT_NEAR(output_tensor_ptr[offset], expected_value, 1e-5); | |
| } | |
| } | |
| } | |
| } | |
| TEST_F(EmbeddingLookupTextTest, LookupPrefillWithOffsetAndDefaultEmbedding) { | |
| std::unique_ptr<EmbeddingLookupText> embedding = GetEmbeddingLookupText(); | |
| EXPECT_NE(embedding, nullptr); | |
| Dimensions dimensions({1, 3, 4, 32}); | |
| LITERT_ASSERT_OK_AND_ASSIGN(TensorBuffer output_tensor, | |
| GetTensorBuffer(dimensions)); | |
| { | |
| auto output_tensor_lock_and_addr = ::litert::TensorBufferScopedLock::Create( | |
| output_tensor, ::litert::TensorBuffer::LockMode::kWrite); | |
| auto output_tensor_ptr = | |
| reinterpret_cast<float*>(output_tensor_lock_and_addr->second); | |
| LITERT_ASSERT_OK_AND_ASSIGN(size_t output_tensor_size, | |
| output_tensor.Size()); | |
| memset(output_tensor_ptr, 99, output_tensor_size); | |
| } | |
| std::vector<int> tokens = {1}; | |
| absl::Span<const int> tokens_span(tokens); | |
| const size_t token_offset = 1; | |
| const size_t float_offset = token_offset * 4 * 32; | |
| const size_t byte_offset = float_offset * sizeof(float); | |
| EXPECT_OK(embedding->LookupPrefill(tokens_span, &output_tensor, byte_offset)); | |
| // Check that the first token is not overwritten. | |
| auto output_tensor_lock_and_addr = ::litert::TensorBufferScopedLock::Create( | |
| output_tensor, ::litert::TensorBuffer::LockMode::kRead); | |
| auto output_tensor_ptr = | |
| reinterpret_cast<float*>(output_tensor_lock_and_addr->second); | |
| for (int i = 0; i < byte_offset; ++i) { | |
| EXPECT_EQ(reinterpret_cast<uint8_t*>(output_tensor_ptr)[i], 99); | |
| } | |
| for (int idx0 = 0; idx0 < tokens.size(); ++idx0) { | |
| int token = tokens[idx0]; | |
| for (int idx2 = 0; idx2 < dimensions[2]; ++idx2) { | |
| for (int idx3 = 0; idx3 < dimensions[3]; ++idx3) { | |
| // Since dimension 1 is of size 1, the offset and expected value can | |
| // ignore it. | |
| size_t offset = float_offset + idx0 * dimensions[2] * dimensions[3] + | |
| idx2 * dimensions[3] + idx3; | |
| float expected_value = 10000.0 * token + 100.0 * idx2 + idx3; | |
| EXPECT_NEAR(output_tensor_ptr[offset], expected_value, 1e-5); | |
| } | |
| } | |
| } | |
| // Check that the last token is filled with the default embedding. | |
| int token = 0; | |
| for (int idx2 = 0; idx2 < dimensions[2]; ++idx2) { | |
| for (int idx3 = 0; idx3 < dimensions[3]; ++idx3) { | |
| // Since dimension 1 is of size 1, the offset and expected value can | |
| // ignore it. | |
| size_t offset = float_offset * 2 + idx2 * dimensions[3] + idx3; | |
| float expected_value = 10000.0 * token + 100.0 * idx2 + idx3; | |
| EXPECT_NEAR(output_tensor_ptr[offset], expected_value, 1e-5); | |
| } | |
| } | |
| } | |
| TEST_F(EmbeddingLookupTextTest, LookupPrefillWithBadOffset) { | |
| std::unique_ptr<EmbeddingLookupText> embedding = GetEmbeddingLookupText(); | |
| EXPECT_NE(embedding, nullptr); | |
| Dimensions dimensions({1, 2, 4, 32}); | |
| LITERT_ASSERT_OK_AND_ASSIGN(TensorBuffer output_tensor, | |
| GetTensorBuffer(dimensions)); | |
| std::vector<int> tokens = {1, 2}; | |
| absl::Span<const int> tokens_span(tokens); | |
| const size_t token_offset = 1; | |
| const size_t float_offset = token_offset * 4 * 32; | |
| const size_t byte_offset = float_offset * sizeof(float); | |
| EXPECT_THAT( | |
| embedding->LookupPrefill(tokens_span, &output_tensor, byte_offset), | |
| testing::status::StatusIs( | |
| absl::StatusCode::kInvalidArgument, | |
| testing::HasSubstr( | |
| "The byte offset and the total number of bytes to be written " | |
| "must not exceed the size of the output tensor"))); | |
| } | |
| TEST_F(EmbeddingLookupTextTest, LookupDecodeVectorSpecifySignatureKey) { | |
| std::unique_ptr<EmbeddingLookupText> embedding = | |
| GetEmbeddingLookupText("serving_default"); | |
| EXPECT_NE(embedding, nullptr); | |
| std::vector<float> output_vector(4 * 32); | |
| int32_t token = 1; | |
| EXPECT_OK(embedding->LookupDecode(token, output_vector)); | |
| size_t offset = 0; | |
| // Dimensions 0 and 1 both have size 1. | |
| for (int idx2 = 0; idx2 < 4; ++idx2) { | |
| for (int idx3 = 0; idx3 < 32; ++idx3) { | |
| // Dimensions 0 and 1 both have size 1 so offset and expected value can | |
| // ignore them. | |
| float expected_value = 10000.0 * token + 100.0 * idx2 + idx3; | |
| EXPECT_NEAR(output_vector[offset++], expected_value, 1e-5); | |
| } | |
| } | |
| } | |
| TEST_F(EmbeddingLookupTextTest, LookupDecodeVectorSpecifySignatureKeyNotFound) { | |
| EXPECT_TRUE(CreateModelFromFile().ok()); | |
| EXPECT_TRUE(model_.has_value()); | |
| auto status = EmbeddingLookupText::Create(&*model_, "not_found"); | |
| EXPECT_TRUE(!status.ok()); | |
| } | |
| } // namespace litert::lm | |