/* 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 "ruy/prepacked_cache.h" #include // NOLINT(build/c++11) #include "ruy/context.h" #include "ruy/context_get_ctx.h" #include "ruy/gtest_wrapper.h" #include "ruy/mat.h" #include "ruy/matrix.h" #include "ruy/ruy.h" #include "ruy/time.h" namespace ruy { namespace { PEMat MakeDummyPEMat(Type data_type, int rows, int cols) { PEMat ret; ret.data_type = data_type; if (!data_type.is_floating_point) { ret.sums_type = Type::Create(); } ret.layout.rows = rows; ret.layout.cols = cols; ret.layout.stride = rows; ret.layout.order = Order::kColMajor; // The kernel block layout is not relevant to this test, so we leave it // trivial 1x1. ret.layout.kernel.rows = 1; ret.layout.kernel.cols = 1; return ret; } template void DummyPack(const std::vector& data, PEMat* packed_matrix) { EXPECT_EQ(data.size(), FlatSize(packed_matrix->layout)); memcpy(packed_matrix->data, data.data(), data.size() * sizeof(T)); } TEST(PrepackedCacheTest, TestCacheBasic) { PrepackedCache prepacked_cache(307); // Allocate the prepacked matrix. // DataBytes=200, SumsBytes=20*4=80, Total: 280 bytes std::vector data1(10 * 20); PEMat mat1 = MakeDummyPEMat(Type::Create(), 10, 20); EXPECT_TRUE(prepacked_cache.Get(data1.data(), &mat1) == PrepackedCache::Action::kInsertedNewEntry); DummyPack(data1, &mat1); // DataBytes=15, SumsBytes=3*4=12, Total: 27 bytes std::vector data2(5 * 3); PEMat mat2 = MakeDummyPEMat(Type::Create(), 5, 3); EXPECT_TRUE(prepacked_cache.Get(data2.data(), &mat2) == PrepackedCache::Action::kInsertedNewEntry); DummyPack(data2, &mat2); // Both should now be in cache. EXPECT_EQ(prepacked_cache.MatrixCount(), 2); EXPECT_EQ(prepacked_cache.BuffersBytes(), 307); EXPECT_TRUE(prepacked_cache.Get(data1.data(), &mat1) == PrepackedCache::Action::kGotExistingEntry); EXPECT_TRUE(prepacked_cache.Get(data2.data(), &mat2) == PrepackedCache::Action::kGotExistingEntry); } TEST(PrepackedCacheTest, TestCacheBasicFloat) { PrepackedCache prepacked_cache(860); // Allocate the prepacked matrix. // DataBytes=200*4, SumsBytes=0 because float, Total: 800 bytes std::vector data1(10 * 20); PEMat mat1 = MakeDummyPEMat(Type::Create(), 10, 20); EXPECT_TRUE(prepacked_cache.Get(data1.data(), &mat1) == PrepackedCache::Action::kInsertedNewEntry); DummyPack(data1, &mat1); // DataBytes=15*4, SumsBytes=0 because float, Total: 60 bytes std::vector data2(5 * 3); PEMat mat2 = MakeDummyPEMat(Type::Create(), 5, 3); EXPECT_TRUE(prepacked_cache.Get(data2.data(), &mat2) == PrepackedCache::Action::kInsertedNewEntry); DummyPack(data2, &mat2); // Both should now be in cache. EXPECT_EQ(prepacked_cache.MatrixCount(), 2); EXPECT_EQ(prepacked_cache.BuffersBytes(), 860); EXPECT_TRUE(prepacked_cache.Get(data1.data(), &mat1) == PrepackedCache::Action::kGotExistingEntry); EXPECT_TRUE(prepacked_cache.Get(data2.data(), &mat2) == PrepackedCache::Action::kGotExistingEntry); } TEST(PrepackedCacheTest, TestCacheEjection) { PrepackedCache prepacked_cache(306); // Allocate the prepacked matrix. // DataBytes=200, SumsBytes=20*4=80, Total: 280 bytes std::vector data1(10 * 20); PEMat mat1 = MakeDummyPEMat(Type::Create(), 10, 20); prepacked_cache.Get(data1.data(), &mat1); DummyPack(data1, &mat1); // DataBytes=15, SumsBytes=3*4=12, Total: 27 bytes std::vector data2(5 * 3); PEMat mat2 = MakeDummyPEMat(Type::Create(), 5, 3); prepacked_cache.Get(data2.data(), &mat2); DummyPack(data2, &mat2); // The first matrix should have been ejected from the cache. // Only the second matrix should now be in cache. EXPECT_EQ(prepacked_cache.MatrixCount(), 1); EXPECT_EQ(prepacked_cache.BuffersBytes(), 27); EXPECT_TRUE(prepacked_cache.Get(data2.data(), &mat2) == PrepackedCache::Action::kGotExistingEntry); EXPECT_TRUE(prepacked_cache.Get(data1.data(), &mat1) == PrepackedCache::Action::kInsertedNewEntry); // The second matrix should have been ejected from the cache. // Only the first matrix should now be in cache. EXPECT_EQ(prepacked_cache.MatrixCount(), 1); EXPECT_EQ(prepacked_cache.BuffersBytes(), 280); EXPECT_TRUE(prepacked_cache.Get(data1.data(), &mat1) == PrepackedCache::Action::kGotExistingEntry); EXPECT_TRUE(prepacked_cache.Get(data2.data(), &mat2) == PrepackedCache::Action::kInsertedNewEntry); } TEST(PrepackedCacheTest, TestCacheEjection2) { PrepackedCache prepacked_cache(1000); // Allocate the prepacked matrix 1. // DataBytes=200, SumsBytes=20*4=80, Total: 280 bytes std::vector data1(10 * 20); PEMat mat1 = MakeDummyPEMat(Type::Create(), 10, 20); prepacked_cache.Get(data1.data(), &mat1); DummyPack(data1, &mat1); // Allocate the prepacked matrix 2. // DataBytes=200, SumsBytes=20*4=80, Total: 280 bytes std::vector data2(10 * 20); PEMat mat2 = MakeDummyPEMat(Type::Create(), 10, 20); prepacked_cache.Get(data2.data(), &mat2); DummyPack(data2, &mat2); // Allocate the prepacked matrix 3. // DataBytes=200, SumsBytes=20*4=80, Total: 280 bytes std::vector data3(10 * 20); PEMat mat3 = MakeDummyPEMat(Type::Create(), 10, 20); prepacked_cache.Get(data3.data(), &mat3); DummyPack(data3, &mat3); // The next insertion will cause the cache size to go over the ejection // threshold. Touch matrix 1 and matrix 3 to make matrix 2 the oldest EXPECT_TRUE(prepacked_cache.Get(data1.data(), &mat1) == PrepackedCache::Action::kGotExistingEntry); EXPECT_TRUE(prepacked_cache.Get(data3.data(), &mat3) == PrepackedCache::Action::kGotExistingEntry); // Allocate the prepacked matrix 4. // DataBytes=200, SumsBytes=20*4=80, Total: 280 bytes std::vector data4(10 * 20); PEMat mat4 = MakeDummyPEMat(Type::Create(), 10, 20); prepacked_cache.Get(data4.data(), &mat4); DummyPack(data4, &mat4); // Ensure that mat2 was ejected, but mat1, mat3, and mat4 were not. EXPECT_EQ(prepacked_cache.MatrixCount(), 3); EXPECT_TRUE(prepacked_cache.Get(data1.data(), &mat1) == PrepackedCache::Action::kGotExistingEntry); EXPECT_TRUE(prepacked_cache.Get(data3.data(), &mat3) == PrepackedCache::Action::kGotExistingEntry); EXPECT_TRUE(prepacked_cache.Get(data4.data(), &mat4) == PrepackedCache::Action::kGotExistingEntry); EXPECT_TRUE(prepacked_cache.Get(data2.data(), &mat2) == PrepackedCache::Action::kInsertedNewEntry); } TEST(PrepackedCacheTest, TestDistinguishSubtlyDifferentMatrices) { PrepackedCache prepacked_cache; std::vector data(10 * 20); PEMat mat = MakeDummyPEMat(Type::Create(), 10, 20); EXPECT_EQ(prepacked_cache.Get(data.data(), &mat), PrepackedCache::Action::kInsertedNewEntry); // Same layout, different source data pointer EXPECT_EQ(prepacked_cache.Get(data.data() + 1, &mat), PrepackedCache::Action::kInsertedNewEntry); // Layout tweaks mat.layout.rows = 9; EXPECT_EQ(prepacked_cache.Get(data.data(), &mat), PrepackedCache::Action::kInsertedNewEntry); mat.layout.cols = 19; EXPECT_EQ(prepacked_cache.Get(data.data(), &mat), PrepackedCache::Action::kInsertedNewEntry); mat.layout.order = Order::kRowMajor; EXPECT_EQ(prepacked_cache.Get(data.data(), &mat), PrepackedCache::Action::kInsertedNewEntry); mat.layout.kernel.rows = 2; EXPECT_EQ(prepacked_cache.Get(data.data(), &mat), PrepackedCache::Action::kInsertedNewEntry); mat.layout.kernel.cols = 2; EXPECT_EQ(prepacked_cache.Get(data.data(), &mat), PrepackedCache::Action::kInsertedNewEntry); mat.layout.kernel.order = Order::kRowMajor; EXPECT_EQ(prepacked_cache.Get(data.data(), &mat), PrepackedCache::Action::kInsertedNewEntry); EXPECT_EQ(prepacked_cache.MatrixCount(), 8); } void TestCachePolicies(CachePolicy cache_policy, bool expected_cached) { ruy::Context context; ruy::Ctx* ctx = get_ctx(&context); PrepackedCache* cache = ctx->GetPrepackedCache(); EXPECT_EQ(cache->MatrixCount(), 0); const float lhs_data[] = {1, 2, 3, 4}; const float rhs_data[] = {1, 2}; float dst_data[4]; ruy::Matrix lhs; ruy::MakeSimpleLayout(2, 2, ruy::Order::kRowMajor, lhs.mutable_layout()); lhs.set_data(lhs_data); ruy::Matrix rhs; ruy::MakeSimpleLayout(2, 1, ruy::Order::kColMajor, rhs.mutable_layout()); rhs.set_data(rhs_data); ruy::Matrix dst; ruy::MakeSimpleLayout(2, 1, ruy::Order::kColMajor, dst.mutable_layout()); dst.set_data(dst_data); ruy::MulParams mul_params; // Perform the multiplication and confirm no caching occurred. ruy::Mul(lhs, rhs, mul_params, &context, &dst); EXPECT_EQ(cache->MatrixCount(), 0); // Set cache policy for the LHS, repeat the multiplication, and see // that caching did occur. lhs.set_cache_policy(cache_policy); ruy::Mul(lhs, rhs, mul_params, &context, &dst); const bool actual_cached = cache->MatrixCount() == 1; EXPECT_EQ(actual_cached, expected_cached); } TEST(PrepackedCacheTest, TestCachePolicies) { for (CachePolicy cache_policy : {CachePolicy::kNeverCache, CachePolicy::kCacheIfLargeSpeedup, CachePolicy::kCacheIfSignificantSpeedup, CachePolicy::kAlwaysCache}) { TestCachePolicies(cache_policy, cache_policy != CachePolicy::kNeverCache); } } TEST(PrepackedCacheTest, TestClearCache) { ruy::Context context; PrepackedCache* cache = get_ctx(&context)->GetPrepackedCache(); EXPECT_EQ(cache->MatrixCount(), 0); const float lhs_data[] = {1, 2, 3, 4}; const float rhs_data[] = {1, 2}; float dst_data[4]; ruy::Matrix lhs; ruy::MakeSimpleLayout(2, 2, ruy::Order::kRowMajor, lhs.mutable_layout()); lhs.set_data(lhs_data); ruy::Matrix rhs; ruy::MakeSimpleLayout(2, 1, ruy::Order::kColMajor, rhs.mutable_layout()); rhs.set_data(rhs_data); ruy::Matrix dst; ruy::MakeSimpleLayout(2, 1, ruy::Order::kColMajor, dst.mutable_layout()); dst.set_data(dst_data); ruy::MulParams mul_params; // Set cache policy for the LHS and see that caching occurs. lhs.set_cache_policy(CachePolicy::kAlwaysCache); ruy::Mul(lhs, rhs, mul_params, &context, &dst); EXPECT_NE(cache->MatrixCount(), 0); // Clear the cache via the Context. context.ClearPrepackedCache(); // Verify that the cache is now empty. cache = get_ctx(&context)->GetPrepackedCache(); EXPECT_EQ(cache->MatrixCount(), 0); } } // namespace } // namespace ruy int main(int argc, char** argv) { ::testing::InitGoogleTest(&argc, argv); return RUN_ALL_TESTS(); }