1 /* 2 * Copyright (c) 2019-2021 Arm Limited. 3 * 4 * SPDX-License-Identifier: MIT 5 * 6 * Permission is hereby granted, free of charge, to any person obtaining a copy 7 * of this software and associated documentation files (the "Software"), to 8 * deal in the Software without restriction, including without limitation the 9 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 10 * sell copies of the Software, and to permit persons to whom the Software is 11 * furnished to do so, subject to the following conditions: 12 * 13 * The above copyright notice and this permission notice shall be included in all 14 * copies or substantial portions of the Software. 15 * 16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 * SOFTWARE. 23 */ 24 #ifndef ARM_COMPUTE_TEST_UNIT_DYNAMIC_TENSOR 25 #define ARM_COMPUTE_TEST_UNIT_DYNAMIC_TENSOR 26 27 #include "arm_compute/core/TensorShape.h" 28 #include "arm_compute/core/Types.h" 29 #include "tests/AssetsLibrary.h" 30 #include "tests/Globals.h" 31 #include "tests/IAccessor.h" 32 #include "tests/framework/Asserts.h" 33 #include "tests/framework/Fixture.h" 34 #include "tests/validation/Helpers.h" 35 #include "tests/validation/reference/ConvolutionLayer.h" 36 #include "tests/validation/reference/NormalizationLayer.h" 37 38 namespace arm_compute 39 { 40 namespace test 41 { 42 namespace validation 43 { 44 template <typename AllocatorType, 45 typename LifetimeMgrType, 46 typename PoolMgrType, 47 typename MemoryMgrType> 48 struct MemoryManagementService 49 { 50 public: 51 using LftMgrType = LifetimeMgrType; 52 53 public: MemoryManagementServiceMemoryManagementService54 MemoryManagementService() 55 : allocator(), lifetime_mgr(nullptr), pool_mgr(nullptr), mm(nullptr), mg(), num_pools(0) 56 { 57 lifetime_mgr = std::make_shared<LifetimeMgrType>(); 58 pool_mgr = std::make_shared<PoolMgrType>(); 59 mm = std::make_shared<MemoryMgrType>(lifetime_mgr, pool_mgr); 60 mg = MemoryGroup(mm); 61 } 62 populateMemoryManagementService63 void populate(size_t pools) 64 { 65 mm->populate(allocator, pools); 66 num_pools = pools; 67 } 68 clearMemoryManagementService69 void clear() 70 { 71 mm->clear(); 72 num_pools = 0; 73 } 74 validateMemoryManagementService75 void validate(bool validate_finalized) const 76 { 77 ARM_COMPUTE_ASSERT(mm->pool_manager() != nullptr); 78 ARM_COMPUTE_ASSERT(mm->lifetime_manager() != nullptr); 79 80 if(validate_finalized) 81 { 82 ARM_COMPUTE_ASSERT(mm->lifetime_manager()->are_all_finalized()); 83 } 84 ARM_COMPUTE_ASSERT(mm->pool_manager()->num_pools() == num_pools); 85 } 86 87 AllocatorType allocator; 88 std::shared_ptr<LifetimeMgrType> lifetime_mgr; 89 std::shared_ptr<PoolMgrType> pool_mgr; 90 std::shared_ptr<MemoryMgrType> mm; 91 MemoryGroup mg; 92 size_t num_pools; 93 }; 94 95 template <typename MemoryMgrType, typename FuncType, typename ITensorType> 96 class SimpleFunctionWrapper 97 { 98 public: SimpleFunctionWrapper(std::shared_ptr<MemoryMgrType> mm)99 SimpleFunctionWrapper(std::shared_ptr<MemoryMgrType> mm) 100 : _func(mm) 101 { 102 } configure(ITensorType * src,ITensorType * dst)103 void configure(ITensorType *src, ITensorType *dst) 104 { 105 ARM_COMPUTE_UNUSED(src, dst); 106 } run()107 void run() 108 { 109 _func.run(); 110 } 111 112 private: 113 FuncType _func; 114 }; 115 116 /** Simple test case to run a single function with different shapes twice. 117 * 118 * Runs a specified function twice, where the second time the size of the input/output is different 119 * Internal memory of the function and input/output are managed by different services 120 */ 121 template <typename TensorType, 122 typename AccessorType, 123 typename MemoryManagementServiceType, 124 typename SimpleFunctionWrapperType> 125 class DynamicTensorType3SingleFunction : public framework::Fixture 126 { 127 using T = float; 128 129 public: 130 template <typename...> setup(TensorShape input_level0,TensorShape input_level1)131 void setup(TensorShape input_level0, TensorShape input_level1) 132 { 133 input_l0 = input_level0; 134 input_l1 = input_level1; 135 run(); 136 } 137 138 protected: run()139 void run() 140 { 141 MemoryManagementServiceType serv_internal; 142 MemoryManagementServiceType serv_cross; 143 const size_t num_pools = 1; 144 const bool validate_finalized = true; 145 146 // Create Tensor shapes. 147 TensorShape level_0 = TensorShape(input_l0); 148 TensorShape level_1 = TensorShape(input_l1); 149 150 // Level 0 151 // Create tensors 152 TensorType src = create_tensor<TensorType>(level_0, DataType::F32, 1); 153 TensorType dst = create_tensor<TensorType>(level_0, DataType::F32, 1); 154 155 serv_cross.mg.manage(&src); 156 serv_cross.mg.manage(&dst); 157 158 // Create and configure function 159 SimpleFunctionWrapperType layer(serv_internal.mm); 160 layer.configure(&src, &dst); 161 162 ARM_COMPUTE_ASSERT(src.info()->is_resizable()); 163 ARM_COMPUTE_ASSERT(dst.info()->is_resizable()); 164 165 // Allocate tensors 166 src.allocator()->allocate(); 167 dst.allocator()->allocate(); 168 169 ARM_COMPUTE_ASSERT(!src.info()->is_resizable()); 170 ARM_COMPUTE_ASSERT(!dst.info()->is_resizable()); 171 172 // Populate and validate memory manager 173 serv_cross.populate(num_pools); 174 serv_internal.populate(num_pools); 175 serv_cross.validate(validate_finalized); 176 serv_internal.validate(validate_finalized); 177 178 // Extract lifetime manager meta-data information 179 internal_l0 = serv_internal.lifetime_mgr->info(); 180 cross_l0 = serv_cross.lifetime_mgr->info(); 181 182 // Acquire memory manager, fill tensors and compute functions 183 serv_cross.mg.acquire(); 184 arm_compute::test::library->fill_tensor_value(AccessorType(src), 12.f); 185 layer.run(); 186 serv_cross.mg.release(); 187 188 // Clear manager 189 serv_cross.clear(); 190 serv_internal.clear(); 191 serv_cross.validate(validate_finalized); 192 serv_internal.validate(validate_finalized); 193 194 // Level 1 195 // Update the tensor shapes 196 src.info()->set_tensor_shape(level_1); 197 dst.info()->set_tensor_shape(level_1); 198 src.info()->set_is_resizable(true); 199 dst.info()->set_is_resizable(true); 200 201 serv_cross.mg.manage(&src); 202 serv_cross.mg.manage(&dst); 203 204 // Re-configure the function 205 layer.configure(&src, &dst); 206 207 // Allocate tensors 208 src.allocator()->allocate(); 209 dst.allocator()->allocate(); 210 211 // Populate and validate memory manager 212 serv_cross.populate(num_pools); 213 serv_internal.populate(num_pools); 214 serv_cross.validate(validate_finalized); 215 serv_internal.validate(validate_finalized); 216 217 // Extract lifetime manager meta-data information 218 internal_l1 = serv_internal.lifetime_mgr->info(); 219 cross_l1 = serv_cross.lifetime_mgr->info(); 220 221 // Compute functions 222 serv_cross.mg.acquire(); 223 arm_compute::test::library->fill_tensor_value(AccessorType(src), 12.f); 224 layer.run(); 225 serv_cross.mg.release(); 226 227 // Clear manager 228 serv_cross.clear(); 229 serv_internal.clear(); 230 serv_cross.validate(validate_finalized); 231 serv_internal.validate(validate_finalized); 232 } 233 234 public: 235 TensorShape input_l0{}, input_l1{}; 236 typename MemoryManagementServiceType::LftMgrType::info_type internal_l0{}, internal_l1{}; 237 typename MemoryManagementServiceType::LftMgrType::info_type cross_l0{}, cross_l1{}; 238 }; 239 240 /** Simple test case to run a single function with different shapes twice. 241 * 242 * Runs a specified function twice, where the second time the size of the input/output is different 243 * Internal memory of the function and input/output are managed by different services 244 */ 245 template <typename TensorType, 246 typename AccessorType, 247 typename MemoryManagementServiceType, 248 typename ComplexFunctionType> 249 class DynamicTensorType3ComplexFunction : public framework::Fixture 250 { 251 using T = float; 252 253 public: 254 template <typename...> setup(std::vector<TensorShape> input_shapes,TensorShape weights_shape,TensorShape bias_shape,std::vector<TensorShape> output_shapes,PadStrideInfo info)255 void setup(std::vector<TensorShape> input_shapes, TensorShape weights_shape, TensorShape bias_shape, std::vector<TensorShape> output_shapes, PadStrideInfo info) 256 { 257 num_iterations = input_shapes.size(); 258 _data_type = DataType::F32; 259 _data_layout = DataLayout::NHWC; 260 _input_shapes = input_shapes; 261 _output_shapes = output_shapes; 262 _weights_shape = weights_shape; 263 _bias_shape = bias_shape; 264 _info = info; 265 266 // Create function 267 _f_target = std::make_unique<ComplexFunctionType>(_ms.mm); 268 } 269 run_iteration(unsigned int idx)270 void run_iteration(unsigned int idx) 271 { 272 auto input_shape = _input_shapes[idx]; 273 auto output_shape = _output_shapes[idx]; 274 275 dst_ref = run_reference(input_shape, _weights_shape, _bias_shape, output_shape, _info); 276 dst_target = run_target(input_shape, _weights_shape, _bias_shape, output_shape, _info, WeightsInfo()); 277 } 278 279 protected: 280 template <typename U> fill(U && tensor,int i)281 void fill(U &&tensor, int i) 282 { 283 switch(tensor.data_type()) 284 { 285 case DataType::F32: 286 { 287 std::uniform_real_distribution<> distribution(-1.0f, 1.0f); 288 library->fill(tensor, distribution, i); 289 break; 290 } 291 default: 292 library->fill_tensor_uniform(tensor, i); 293 } 294 } 295 run_target(TensorShape input_shape,TensorShape weights_shape,TensorShape bias_shape,TensorShape output_shape,PadStrideInfo info,WeightsInfo weights_info)296 TensorType run_target(TensorShape input_shape, TensorShape weights_shape, TensorShape bias_shape, TensorShape output_shape, 297 PadStrideInfo info, WeightsInfo weights_info) 298 { 299 if(_data_layout == DataLayout::NHWC) 300 { 301 permute(input_shape, PermutationVector(2U, 0U, 1U)); 302 permute(weights_shape, PermutationVector(2U, 0U, 1U)); 303 permute(output_shape, PermutationVector(2U, 0U, 1U)); 304 } 305 306 _weights_target = create_tensor<TensorType>(weights_shape, _data_type, 1, QuantizationInfo(), _data_layout); 307 _bias_target = create_tensor<TensorType>(bias_shape, _data_type, 1); 308 309 // Create tensors 310 TensorType src = create_tensor<TensorType>(input_shape, _data_type, 1, QuantizationInfo(), _data_layout); 311 TensorType dst = create_tensor<TensorType>(output_shape, _data_type, 1, QuantizationInfo(), _data_layout); 312 313 // Create and configure function 314 _f_target->configure(&src, &_weights_target, &_bias_target, &dst, info, weights_info); 315 316 ARM_COMPUTE_ASSERT(src.info()->is_resizable()); 317 ARM_COMPUTE_ASSERT(dst.info()->is_resizable()); 318 319 // Allocate tensors 320 src.allocator()->allocate(); 321 dst.allocator()->allocate(); 322 _weights_target.allocator()->allocate(); 323 _bias_target.allocator()->allocate(); 324 325 ARM_COMPUTE_ASSERT(!src.info()->is_resizable()); 326 ARM_COMPUTE_ASSERT(!dst.info()->is_resizable()); 327 328 // Fill tensors 329 fill(AccessorType(src), 0); 330 fill(AccessorType(_weights_target), 1); 331 fill(AccessorType(_bias_target), 2); 332 333 // Populate and validate memory manager 334 _ms.clear(); 335 _ms.populate(1); 336 _ms.mg.acquire(); 337 338 // Compute NEConvolutionLayer function 339 _f_target->run(); 340 _ms.mg.release(); 341 342 return dst; 343 } 344 run_reference(TensorShape input_shape,TensorShape weights_shape,TensorShape bias_shape,TensorShape output_shape,PadStrideInfo info)345 SimpleTensor<T> run_reference(TensorShape input_shape, TensorShape weights_shape, TensorShape bias_shape, TensorShape output_shape, PadStrideInfo info) 346 { 347 // Create reference 348 SimpleTensor<T> src{ input_shape, _data_type, 1 }; 349 SimpleTensor<T> weights{ weights_shape, _data_type, 1 }; 350 SimpleTensor<T> bias{ bias_shape, _data_type, 1 }; 351 352 // Fill reference 353 fill(src, 0); 354 fill(weights, 1); 355 fill(bias, 2); 356 357 return reference::convolution_layer<T>(src, weights, bias, output_shape, info); 358 } 359 360 public: 361 unsigned int num_iterations{ 0 }; 362 SimpleTensor<T> dst_ref{}; 363 TensorType dst_target{}; 364 365 private: 366 DataType _data_type{ DataType::UNKNOWN }; 367 DataLayout _data_layout{ DataLayout::UNKNOWN }; 368 PadStrideInfo _info{}; 369 std::vector<TensorShape> _input_shapes{}; 370 std::vector<TensorShape> _output_shapes{}; 371 TensorShape _weights_shape{}; 372 TensorShape _bias_shape{}; 373 MemoryManagementServiceType _ms{}; 374 TensorType _weights_target{}; 375 TensorType _bias_target{}; 376 std::unique_ptr<ComplexFunctionType> _f_target{}; 377 }; 378 379 /** Fixture that create a pipeline of Convolutions and changes the inputs dynamically 380 * 381 * Runs a list of convolutions and then resizes the inputs and reruns. 382 * Updates the memory manager and allocated memory. 383 */ 384 template <typename TensorType, 385 typename AccessorType, 386 typename MemoryManagementServiceType, 387 typename ComplexFunctionType> 388 class DynamicTensorType2PipelineFunction : public framework::Fixture 389 { 390 using T = float; 391 392 public: 393 template <typename...> setup(std::vector<TensorShape> input_shapes)394 void setup(std::vector<TensorShape> input_shapes) 395 { 396 _data_type = DataType::F32; 397 _data_layout = DataLayout::NHWC; 398 _input_shapes = input_shapes; 399 400 run(); 401 } 402 403 protected: 404 template <typename U> fill(U && tensor,int i)405 void fill(U &&tensor, int i) 406 { 407 switch(tensor.data_type()) 408 { 409 case DataType::F32: 410 { 411 std::uniform_real_distribution<float> distribution(-1.0f, 1.0f); 412 library->fill(tensor, distribution, i); 413 break; 414 } 415 default: 416 library->fill_tensor_uniform(tensor, i); 417 } 418 } 419 run()420 void run() 421 { 422 const unsigned int num_functions = 5; 423 const unsigned int num_tensors = num_functions + 1; 424 const unsigned int num_resizes = _input_shapes.size(); 425 426 for(unsigned int i = 0; i < num_functions; ++i) 427 { 428 _functions.emplace_back(std::make_unique<ComplexFunctionType>(_ms.mm)); 429 } 430 431 for(unsigned int i = 0; i < num_resizes; ++i) 432 { 433 TensorShape input_shape = _input_shapes[i]; 434 TensorShape weights_shape = TensorShape(3U, 3U, input_shape[2], input_shape[2]); 435 TensorShape output_shape = input_shape; 436 PadStrideInfo info(1U, 1U, 1U, 1U); 437 438 if(_data_layout == DataLayout::NHWC) 439 { 440 permute(input_shape, PermutationVector(2U, 0U, 1U)); 441 permute(weights_shape, PermutationVector(2U, 0U, 1U)); 442 permute(output_shape, PermutationVector(2U, 0U, 1U)); 443 } 444 445 std::vector<TensorType> tensors(num_tensors); 446 std::vector<TensorType> ws(num_functions); 447 std::vector<TensorType> bs(num_functions); 448 449 auto tensor_info = TensorInfo(input_shape, 1, _data_type); 450 auto weights_info = TensorInfo(weights_shape, 1, _data_type); 451 tensor_info.set_data_layout(_data_layout); 452 weights_info.set_data_layout(_data_layout); 453 454 tensors[0].allocator()->init(tensor_info); 455 for(unsigned int f = 0; f < num_functions; ++f) 456 { 457 tensors[f + 1].allocator()->init(tensor_info); 458 ws[f].allocator()->init(weights_info); 459 460 _functions[f]->configure(&tensors[f], &ws[f], nullptr, &tensors[f + 1], info); 461 462 // Allocate tensors 463 tensors[f].allocator()->allocate(); 464 ws[f].allocator()->allocate(); 465 } 466 tensors[num_functions].allocator()->allocate(); 467 468 // Populate and validate memory manager 469 _ms.clear(); 470 _ms.populate(1); 471 _ms.mg.acquire(); 472 473 // Run pipeline 474 for(unsigned int f = 0; f < num_functions; ++f) 475 { 476 _functions[f]->run(); 477 } 478 479 // Release memory group 480 _ms.mg.release(); 481 } 482 } 483 484 private: 485 DataType _data_type{ DataType::UNKNOWN }; 486 DataLayout _data_layout{ DataLayout::UNKNOWN }; 487 std::vector<TensorShape> _input_shapes{}; 488 MemoryManagementServiceType _ms{}; 489 std::vector<std::unique_ptr<ComplexFunctionType>> _functions{}; 490 }; 491 } // namespace validation 492 } // namespace test 493 } // namespace arm_compute 494 #endif /* ARM_COMPUTE_TEST_UNIT_DYNAMIC_TENSOR */ 495