From caa7deedfe1b0d0020c6099d8f616ec92b1bd5e9 Mon Sep 17 00:00:00 2001 From: Michalis Spyrou Date: Mon, 9 Sep 2019 19:23:39 +0100 Subject: COMPMID-2641 [NEON] Create a test case for dynamic tensor support Change-Id: I181e9acffd34ff1c807c65a822cfafb7327b8c8a Signed-off-by: Michalis Spyrou Reviewed-on: https://review.mlplatform.org/c/1913 Comments-Addressed: Arm Jenkins Tested-by: Arm Jenkins --- arm_compute/runtime/MemoryGroupBase.h | 10 +- arm_compute/runtime/OffsetLifetimeManager.h | 8 + arm_compute/runtime/OffsetMemoryPool.h | 5 + src/runtime/CL/CLTensorAllocator.cpp | 2 +- src/runtime/GLES_COMPUTE/GCTensorAllocator.cpp | 4 +- src/runtime/ISimpleLifetimeManager.cpp | 4 +- src/runtime/OffsetLifetimeManager.cpp | 5 + src/runtime/OffsetMemoryPool.cpp | 12 +- src/runtime/TensorAllocator.cpp | 2 +- tests/validation/NEON/UNIT/DynamicTensor.cpp | 80 ++++++++ .../fixtures/UNIT/DynamicTensorFixture.h | 224 +++++++++++++++++++++ 11 files changed, 340 insertions(+), 16 deletions(-) create mode 100644 tests/validation/NEON/UNIT/DynamicTensor.cpp create mode 100644 tests/validation/fixtures/UNIT/DynamicTensorFixture.h diff --git a/arm_compute/runtime/MemoryGroupBase.h b/arm_compute/runtime/MemoryGroupBase.h index 7dc18c8b4f..e3f38f7cfe 100644 --- a/arm_compute/runtime/MemoryGroupBase.h +++ b/arm_compute/runtime/MemoryGroupBase.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017-2018 ARM Limited. + * Copyright (c) 2017-2019 ARM Limited. * * SPDX-License-Identifier: MIT * @@ -101,7 +101,7 @@ inline MemoryGroupBase::MemoryGroupBase(std::shared_ptr inline void MemoryGroupBase::manage(TensorType *obj) { - if(_memory_manager && _mappings.empty()) + if(_memory_manager) { ARM_COMPUTE_ERROR_ON(!_memory_manager->lifetime_manager()); @@ -119,11 +119,7 @@ inline void MemoryGroupBase::manage(TensorType *obj) template inline void MemoryGroupBase::finalize_memory(TensorType *obj, IMemory &obj_memory, size_t size, size_t alignment) { - // TODO (geopin01) : Check size (track size in MemoryMappings) - // Check if existing mapping is valid - ARM_COMPUTE_ERROR_ON(!_mappings.empty() && (_mappings.find(&obj_memory) == std::end(_mappings))); - - if(_memory_manager && _mappings.empty()) + if(_memory_manager) { ARM_COMPUTE_ERROR_ON(!_memory_manager->lifetime_manager()); _memory_manager->lifetime_manager()->end_lifetime(obj, obj_memory, size, alignment); diff --git a/arm_compute/runtime/OffsetLifetimeManager.h b/arm_compute/runtime/OffsetLifetimeManager.h index 26aeb1ef6a..748f3b6f81 100644 --- a/arm_compute/runtime/OffsetLifetimeManager.h +++ b/arm_compute/runtime/OffsetLifetimeManager.h @@ -40,6 +40,9 @@ class IMemoryPool; * calculates the systems memory requirements in terms of a single blob and a list of offsets */ class OffsetLifetimeManager : public ISimpleLifetimeManager { +public: + using info_type = BlobInfo; + public: /** Constructor */ OffsetLifetimeManager(); @@ -51,6 +54,11 @@ public: OffsetLifetimeManager(OffsetLifetimeManager &&) = default; /** Allow instances of this class to be moved */ OffsetLifetimeManager &operator=(OffsetLifetimeManager &&) = default; + /** Accessor to the pool internal configuration meta-data + * + * @return Pool internal configuration meta-data + */ + const info_type &info() const; // Inherited methods overridden: std::unique_ptr create_pool(IAllocator *allocator) override; diff --git a/arm_compute/runtime/OffsetMemoryPool.h b/arm_compute/runtime/OffsetMemoryPool.h index bc09de6a14..26005706df 100644 --- a/arm_compute/runtime/OffsetMemoryPool.h +++ b/arm_compute/runtime/OffsetMemoryPool.h @@ -58,6 +58,11 @@ public: OffsetMemoryPool(OffsetMemoryPool &&) = default; /** Allow instances of this class to be move assigned */ OffsetMemoryPool &operator=(OffsetMemoryPool &&) = default; + /** Accessor to the pool internal configuration meta-data + * + * @return Pool internal configuration meta-data + */ + const BlobInfo &info() const; // Inherited methods overridden: void acquire(MemoryMappings &handles) override; diff --git a/src/runtime/CL/CLTensorAllocator.cpp b/src/runtime/CL/CLTensorAllocator.cpp index 51caf69297..72b5854c5c 100644 --- a/src/runtime/CL/CLTensorAllocator.cpp +++ b/src/runtime/CL/CLTensorAllocator.cpp @@ -181,7 +181,7 @@ Status CLTensorAllocator::import_memory(cl::Buffer buffer) void CLTensorAllocator::set_associated_memory_group(CLMemoryGroup *associated_memory_group) { ARM_COMPUTE_ERROR_ON(associated_memory_group == nullptr); - ARM_COMPUTE_ERROR_ON(_associated_memory_group != nullptr); + ARM_COMPUTE_ERROR_ON(_associated_memory_group != nullptr && _associated_memory_group != associated_memory_group); ARM_COMPUTE_ERROR_ON(_memory.region() != nullptr && _memory.cl_region()->cl_data().get() != nullptr); _associated_memory_group = associated_memory_group; diff --git a/src/runtime/GLES_COMPUTE/GCTensorAllocator.cpp b/src/runtime/GLES_COMPUTE/GCTensorAllocator.cpp index a0dd540a7c..9a5d139517 100644 --- a/src/runtime/GLES_COMPUTE/GCTensorAllocator.cpp +++ b/src/runtime/GLES_COMPUTE/GCTensorAllocator.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017-2018 ARM Limited. + * Copyright (c) 2017-2019 ARM Limited. * * SPDX-License-Identifier: MIT * @@ -65,7 +65,7 @@ void GCTensorAllocator::free() void GCTensorAllocator::set_associated_memory_group(GCMemoryGroup *associated_memory_group) { ARM_COMPUTE_ERROR_ON(associated_memory_group == nullptr); - ARM_COMPUTE_ERROR_ON(_associated_memory_group != nullptr); + ARM_COMPUTE_ERROR_ON(_associated_memory_group != nullptr && _associated_memory_group != associated_memory_group); ARM_COMPUTE_ERROR_ON(_memory.region() != nullptr && _memory.gc_region()->gc_ssbo_name() != 0); _associated_memory_group = associated_memory_group; diff --git a/src/runtime/ISimpleLifetimeManager.cpp b/src/runtime/ISimpleLifetimeManager.cpp index 97c20d1882..39a4096799 100644 --- a/src/runtime/ISimpleLifetimeManager.cpp +++ b/src/runtime/ISimpleLifetimeManager.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017-2018 ARM Limited. + * Copyright (c) 2017-2019 ARM Limited. * * SPDX-License-Identifier: MIT * @@ -100,7 +100,7 @@ void ISimpleLifetimeManager::end_lifetime(void *obj, IMemory &obj_memory, size_t occupied_blob_it->id = nullptr; _free_blobs.splice(std::begin(_free_blobs), _occupied_blobs, occupied_blob_it); - // Check if all object are finalized and reset active group + // Check if all objects are finalized and reset active group if(are_all_finalized()) { ARM_COMPUTE_ERROR_ON(!_occupied_blobs.empty()); diff --git a/src/runtime/OffsetLifetimeManager.cpp b/src/runtime/OffsetLifetimeManager.cpp index ad23220c0e..e9aa1ff447 100644 --- a/src/runtime/OffsetLifetimeManager.cpp +++ b/src/runtime/OffsetLifetimeManager.cpp @@ -49,6 +49,11 @@ OffsetLifetimeManager::OffsetLifetimeManager() { } +const OffsetLifetimeManager::info_type &OffsetLifetimeManager::info() const +{ + return _blob; +} + std::unique_ptr OffsetLifetimeManager::create_pool(IAllocator *allocator) { ARM_COMPUTE_ERROR_ON(allocator == nullptr); diff --git a/src/runtime/OffsetMemoryPool.cpp b/src/runtime/OffsetMemoryPool.cpp index 70cbe90bf0..a335f6087a 100644 --- a/src/runtime/OffsetMemoryPool.cpp +++ b/src/runtime/OffsetMemoryPool.cpp @@ -32,8 +32,8 @@ #include "arm_compute/runtime/Types.h" #include "support/ToolchainSupport.h" -using namespace arm_compute; - +namespace arm_compute +{ OffsetMemoryPool::OffsetMemoryPool(IAllocator *allocator, BlobInfo blob_info) : _allocator(allocator), _blob(), _blob_info(blob_info) { @@ -41,6 +41,11 @@ OffsetMemoryPool::OffsetMemoryPool(IAllocator *allocator, BlobInfo blob_info) _blob = _allocator->make_region(blob_info.size, blob_info.alignment); } +const BlobInfo &OffsetMemoryPool::info() const +{ + return _blob_info; +} + void OffsetMemoryPool::acquire(MemoryMappings &handles) { ARM_COMPUTE_ERROR_ON(_blob == nullptr); @@ -71,4 +76,5 @@ std::unique_ptr OffsetMemoryPool::duplicate() { ARM_COMPUTE_ERROR_ON(!_allocator); return support::cpp14::make_unique(_allocator, _blob_info); -} \ No newline at end of file +} +} // namespace arm_compute \ No newline at end of file diff --git a/src/runtime/TensorAllocator.cpp b/src/runtime/TensorAllocator.cpp index d9616ca09d..dfe239c586 100644 --- a/src/runtime/TensorAllocator.cpp +++ b/src/runtime/TensorAllocator.cpp @@ -164,7 +164,7 @@ Status TensorAllocator::import_memory(void *memory) void TensorAllocator::set_associated_memory_group(MemoryGroup *associated_memory_group) { ARM_COMPUTE_ERROR_ON(associated_memory_group == nullptr); - ARM_COMPUTE_ERROR_ON(_associated_memory_group != nullptr); + ARM_COMPUTE_ERROR_ON(_associated_memory_group != nullptr && _associated_memory_group != associated_memory_group); ARM_COMPUTE_ERROR_ON(_memory.region() != nullptr && _memory.region()->buffer() != nullptr); _associated_memory_group = associated_memory_group; diff --git a/tests/validation/NEON/UNIT/DynamicTensor.cpp b/tests/validation/NEON/UNIT/DynamicTensor.cpp new file mode 100644 index 0000000000..731c6d1650 --- /dev/null +++ b/tests/validation/NEON/UNIT/DynamicTensor.cpp @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2019 ARM Limited. + * + * SPDX-License-Identifier: MIT + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#include "arm_compute/runtime/Allocator.h" +#include "arm_compute/runtime/MemoryGroup.h" +#include "arm_compute/runtime/MemoryManagerOnDemand.h" +#include "arm_compute/runtime/NEON/functions/NENormalizationLayer.h" +#include "arm_compute/runtime/OffsetLifetimeManager.h" +#include "arm_compute/runtime/PoolManager.h" +#include "support/ToolchainSupport.h" +#include "tests/AssetsLibrary.h" +#include "tests/Globals.h" +#include "tests/NEON/Accessor.h" +#include "tests/Utils.h" +#include "tests/framework/Asserts.h" +#include "tests/framework/Macros.h" +#include "tests/framework/datasets/Datasets.h" +#include "tests/validation/fixtures/UNIT/DynamicTensorFixture.h" +#include + +namespace arm_compute +{ +namespace test +{ +namespace validation +{ +TEST_SUITE(NEON) +TEST_SUITE(UNIT) +TEST_SUITE(DynamicTensor) + +using NEDynamicTensorType3SingleFunction = DynamicTensorType3SingleFunction; + +/** Tests the memory manager with dynamic input and output tensors. + * + * Create and manage the tensors needed to run a simple function. After the function is executed, + * change the input and output size requesting more memory and go through the manage/allocate process. + * The memory manager should be able to update the inner structures and allocate the requested memory + * */ +FIXTURE_DATA_TEST_CASE(DynamicTensorType3Single, NEDynamicTensorType3SingleFunction, framework::DatasetMode::ALL, + framework::dataset::zip(framework::dataset::make("Level0Shape", { TensorShape(12U, 11U, 3U), TensorShape(256U, 8U, 12U) }), + framework::dataset::make("Level1Shape", { TensorShape(67U, 31U, 15U), TensorShape(11U, 2U, 3U) }))) +{ + if(input_l0.total_size() < input_l1.total_size()) + { + ARM_COMPUTE_EXPECT(internal_l0.size < internal_l1.size, framework::LogLevel::ERRORS); + ARM_COMPUTE_EXPECT(cross_l0.size < cross_l1.size, framework::LogLevel::ERRORS); + } + else + { + ARM_COMPUTE_EXPECT(internal_l0.size == internal_l1.size, framework::LogLevel::ERRORS); + ARM_COMPUTE_EXPECT(cross_l0.size == cross_l1.size, framework::LogLevel::ERRORS); + } +} + +TEST_SUITE_END() // DynamicTensor +TEST_SUITE_END() // UNIT +TEST_SUITE_END() // NEON +} // namespace validation +} // namespace test +} // namespace arm_compute diff --git a/tests/validation/fixtures/UNIT/DynamicTensorFixture.h b/tests/validation/fixtures/UNIT/DynamicTensorFixture.h new file mode 100644 index 0000000000..df12a4aa30 --- /dev/null +++ b/tests/validation/fixtures/UNIT/DynamicTensorFixture.h @@ -0,0 +1,224 @@ +/* + * Copyright (c) 2019 ARM Limited. + * + * SPDX-License-Identifier: MIT + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef ARM_COMPUTE_TEST_UNIT_DYNAMIC_TENSOR +#define ARM_COMPUTE_TEST_UNIT_DYNAMIC_TENSOR + +#include "arm_compute/core/TensorShape.h" +#include "arm_compute/core/Types.h" +#include "tests/AssetsLibrary.h" +#include "tests/Globals.h" +#include "tests/IAccessor.h" +#include "tests/framework/Asserts.h" +#include "tests/framework/Fixture.h" +#include "tests/validation/Helpers.h" +#include "tests/validation/reference/NormalizationLayer.h" + +namespace arm_compute +{ +namespace test +{ +namespace validation +{ +namespace +{ +template +struct MemoryManagementService +{ +public: + MemoryManagementService() + : allocator(), lifetime_mgr(nullptr), pool_mgr(nullptr), mm(nullptr), mg(), num_pools(0) + { + lifetime_mgr = std::make_shared(); + pool_mgr = std::make_shared(); + mm = std::make_shared(lifetime_mgr, pool_mgr); + mg = MemoryGroup(mm); + } + + void populate(size_t pools) + { + mm->populate(allocator, pools); + num_pools = pools; + } + + void clear() + { + mm->clear(); + num_pools = 0; + } + + void validate(bool validate_finalized) const + { + ARM_COMPUTE_EXPECT(mm->pool_manager() != nullptr, framework::LogLevel::ERRORS); + ARM_COMPUTE_EXPECT(mm->lifetime_manager() != nullptr, framework::LogLevel::ERRORS); + + if(validate_finalized) + { + ARM_COMPUTE_EXPECT(mm->lifetime_manager()->are_all_finalized(), framework::LogLevel::ERRORS); + } + ARM_COMPUTE_EXPECT(mm->pool_manager()->num_pools() == num_pools, framework::LogLevel::ERRORS); + } + + AllocatorType allocator; + std::shared_ptr lifetime_mgr; + std::shared_ptr pool_mgr; + std::shared_ptr mm; + MemoryGroup mg; + size_t num_pools; +}; +} // namespace + +/** Simple test case to run a single function with different shapes twice. + * + * Runs a specified function twice, where the second time the size of the input/output is different + * Internal memory of the function and input/output are managed by different services + */ +template +class DynamicTensorType3SingleFunction : public framework::Fixture +{ + using T = float; + using MemoryManagementServiceType = MemoryManagementService; + +public: + template + void setup(TensorShape input_level0, TensorShape input_level1) + { + input_l0 = input_level0; + input_l1 = input_level1; + run(); + } + +protected: + void run() + { + MemoryManagementServiceType serv_internal; + MemoryManagementServiceType serv_cross; + const size_t num_pools = 1; + const bool validate_finalized = true; + + // Create Tensor shapes. + TensorShape level_0 = TensorShape(input_l0); + TensorShape level_1 = TensorShape(input_l1); + + // Level 0 + // Create tensors + TensorType src = create_tensor(level_0, DataType::F32, 1); + TensorType dst = create_tensor(level_0, DataType::F32, 1); + + serv_cross.mg.manage(&src); + serv_cross.mg.manage(&dst); + + // Create and configure function + NormalizationFunctionType norm_layer(serv_internal.mm); + norm_layer.configure(&src, &dst, NormalizationLayerInfo(NormType::CROSS_MAP, 3)); + + ARM_COMPUTE_EXPECT(src.info()->is_resizable(), framework::LogLevel::ERRORS); + ARM_COMPUTE_EXPECT(dst.info()->is_resizable(), framework::LogLevel::ERRORS); + + // Allocate tensors + src.allocator()->allocate(); + dst.allocator()->allocate(); + + ARM_COMPUTE_EXPECT(!src.info()->is_resizable(), framework::LogLevel::ERRORS); + ARM_COMPUTE_EXPECT(!dst.info()->is_resizable(), framework::LogLevel::ERRORS); + + // Populate and validate memory manager + serv_cross.populate(num_pools); + serv_internal.populate(num_pools); + serv_cross.validate(validate_finalized); + serv_internal.validate(validate_finalized); + + // Extract lifetime manager meta-data information + internal_l0 = serv_internal.lifetime_mgr->info(); + cross_l0 = serv_cross.lifetime_mgr->info(); + + // Acquire memory manager, fill tensors and compute functions + serv_cross.mg.acquire(); + arm_compute::test::library->fill_tensor_value(Accessor(src), 12.f); + norm_layer.run(); + serv_cross.mg.release(); + + // Clear manager + serv_cross.clear(); + serv_internal.clear(); + serv_cross.validate(validate_finalized); + serv_internal.validate(validate_finalized); + + // Level 1 + // Update the tensor shapes + src.info()->set_tensor_shape(level_1); + dst.info()->set_tensor_shape(level_1); + src.info()->set_is_resizable(true); + dst.info()->set_is_resizable(true); + + serv_cross.mg.manage(&src); + serv_cross.mg.manage(&dst); + + // Re-configure the function + norm_layer.configure(&src, &dst, NormalizationLayerInfo(NormType::CROSS_MAP, 3)); + + // Allocate tensors + src.allocator()->allocate(); + dst.allocator()->allocate(); + + // Populate and validate memory manager + serv_cross.populate(num_pools); + serv_internal.populate(num_pools); + serv_cross.validate(validate_finalized); + serv_internal.validate(validate_finalized); + + // Extract lifetime manager meta-data information + internal_l1 = serv_internal.lifetime_mgr->info(); + cross_l1 = serv_cross.lifetime_mgr->info(); + + // Compute functions + serv_cross.mg.acquire(); + arm_compute::test::library->fill_tensor_value(AccessorType(src), 12.f); + norm_layer.run(); + serv_cross.mg.release(); + + // Clear manager + serv_cross.clear(); + serv_internal.clear(); + serv_cross.validate(validate_finalized); + serv_internal.validate(validate_finalized); + } + +public: + TensorShape input_l0{}, input_l1{}; + typename LifetimeMgrType::info_type internal_l0{}, internal_l1{}; + typename LifetimeMgrType::info_type cross_l0{}, cross_l1{}; +}; +} // namespace validation +} // namespace test +} // namespace arm_compute +#endif /* ARM_COMPUTE_TEST_UNIT_DYNAMIC_TENSOR */ -- cgit v1.2.1