1 /* 2 * Copyright (C) 2017 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 // Provides C++ classes to more easily use the Neural Networks API. 18 // TODO(b/117845862): this should be auto generated from NeuralNetworksWrapper.h. 19 20 #ifndef ANDROID_FRAMEWORKS_ML_NN_RUNTIME_TEST_TEST_NEURAL_NETWORKS_WRAPPER_H 21 #define ANDROID_FRAMEWORKS_ML_NN_RUNTIME_TEST_TEST_NEURAL_NETWORKS_WRAPPER_H 22 23 #include <math.h> 24 25 #include <algorithm> 26 #include <memory> 27 #include <optional> 28 #include <string> 29 #include <utility> 30 #include <vector> 31 32 #include "NeuralNetworks.h" 33 #include "NeuralNetworksWrapper.h" 34 #include "NeuralNetworksWrapperExtensions.h" 35 36 #ifndef __NNAPI_FL5_MIN_ANDROID_API__ 37 #define __NNAPI_FL5_MIN_ANDROID_API__ __ANDROID_API_FUTURE__ 38 #endif 39 40 namespace android { 41 namespace nn { 42 namespace test_wrapper { 43 44 using wrapper::Event; 45 using wrapper::ExecutePreference; 46 using wrapper::ExecutePriority; 47 using wrapper::ExtensionModel; 48 using wrapper::ExtensionOperandParams; 49 using wrapper::ExtensionOperandType; 50 using wrapper::OperandType; 51 using wrapper::Result; 52 using wrapper::SymmPerChannelQuantParams; 53 using wrapper::Type; 54 55 class Memory { 56 public: 57 // Takes ownership of a ANeuralNetworksMemory Memory(ANeuralNetworksMemory * memory)58 Memory(ANeuralNetworksMemory* memory) : mMemory(memory) {} 59 Memory(size_t size,int protect,int fd,size_t offset)60 Memory(size_t size, int protect, int fd, size_t offset) { 61 mValid = ANeuralNetworksMemory_createFromFd(size, protect, fd, offset, &mMemory) == 62 ANEURALNETWORKS_NO_ERROR; 63 } 64 Memory(AHardwareBuffer * buffer)65 Memory(AHardwareBuffer* buffer) { 66 mValid = ANeuralNetworksMemory_createFromAHardwareBuffer(buffer, &mMemory) == 67 ANEURALNETWORKS_NO_ERROR; 68 } 69 ~Memory()70 virtual ~Memory() { ANeuralNetworksMemory_free(mMemory); } 71 72 // Disallow copy semantics to ensure the runtime object can only be freed 73 // once. Copy semantics could be enabled if some sort of reference counting 74 // or deep-copy system for runtime objects is added later. 75 Memory(const Memory&) = delete; 76 Memory& operator=(const Memory&) = delete; 77 78 // Move semantics to remove access to the runtime object from the wrapper 79 // object that is being moved. This ensures the runtime object will be 80 // freed only once. Memory(Memory && other)81 Memory(Memory&& other) { *this = std::move(other); } 82 Memory& operator=(Memory&& other) { 83 if (this != &other) { 84 ANeuralNetworksMemory_free(mMemory); 85 mMemory = other.mMemory; 86 mValid = other.mValid; 87 other.mMemory = nullptr; 88 other.mValid = false; 89 } 90 return *this; 91 } 92 get()93 ANeuralNetworksMemory* get() const { return mMemory; } isValid()94 bool isValid() const { return mValid; } 95 96 private: 97 ANeuralNetworksMemory* mMemory = nullptr; 98 bool mValid = true; 99 }; 100 101 class Model { 102 public: Model()103 Model() { 104 // TODO handle the value returned by this call 105 ANeuralNetworksModel_create(&mModel); 106 } ~Model()107 ~Model() { ANeuralNetworksModel_free(mModel); } 108 109 // Disallow copy semantics to ensure the runtime object can only be freed 110 // once. Copy semantics could be enabled if some sort of reference counting 111 // or deep-copy system for runtime objects is added later. 112 Model(const Model&) = delete; 113 Model& operator=(const Model&) = delete; 114 115 // Move semantics to remove access to the runtime object from the wrapper 116 // object that is being moved. This ensures the runtime object will be 117 // freed only once. Model(Model && other)118 Model(Model&& other) { *this = std::move(other); } 119 Model& operator=(Model&& other) { 120 if (this != &other) { 121 ANeuralNetworksModel_free(mModel); 122 mModel = other.mModel; 123 mNextOperandId = other.mNextOperandId; 124 mValid = other.mValid; 125 mRelaxed = other.mRelaxed; 126 mFinished = other.mFinished; 127 other.mModel = nullptr; 128 other.mNextOperandId = 0; 129 other.mValid = false; 130 other.mRelaxed = false; 131 other.mFinished = false; 132 } 133 return *this; 134 } 135 finish()136 Result finish() { 137 if (mValid) { 138 auto result = static_cast<Result>(ANeuralNetworksModel_finish(mModel)); 139 if (result != Result::NO_ERROR) { 140 mValid = false; 141 } 142 mFinished = true; 143 return result; 144 } else { 145 return Result::BAD_STATE; 146 } 147 } 148 addOperand(const OperandType * type)149 uint32_t addOperand(const OperandType* type) { 150 if (ANeuralNetworksModel_addOperand(mModel, &(type->operandType)) != 151 ANEURALNETWORKS_NO_ERROR) { 152 mValid = false; 153 } 154 if (type->channelQuant) { 155 if (ANeuralNetworksModel_setOperandSymmPerChannelQuantParams( 156 mModel, mNextOperandId, &type->channelQuant.value().params) != 157 ANEURALNETWORKS_NO_ERROR) { 158 mValid = false; 159 } 160 } 161 return mNextOperandId++; 162 } 163 164 template <typename T> addConstantOperand(const OperandType * type,const T & value)165 uint32_t addConstantOperand(const OperandType* type, const T& value) { 166 static_assert(sizeof(T) <= ANEURALNETWORKS_MAX_SIZE_OF_IMMEDIATELY_COPIED_VALUES, 167 "Values larger than ANEURALNETWORKS_MAX_SIZE_OF_IMMEDIATELY_COPIED_VALUES " 168 "not supported"); 169 uint32_t index = addOperand(type); 170 setOperandValue(index, &value); 171 return index; 172 } 173 addModelOperand(const Model * value)174 uint32_t addModelOperand(const Model* value) { 175 OperandType operandType(Type::MODEL, {}); 176 uint32_t operand = addOperand(&operandType); 177 setOperandValueFromModel(operand, value); 178 return operand; 179 } 180 setOperandValue(uint32_t index,const void * buffer,size_t length)181 void setOperandValue(uint32_t index, const void* buffer, size_t length) { 182 if (ANeuralNetworksModel_setOperandValue(mModel, index, buffer, length) != 183 ANEURALNETWORKS_NO_ERROR) { 184 mValid = false; 185 } 186 } 187 188 template <typename T> setOperandValue(uint32_t index,const T * value)189 void setOperandValue(uint32_t index, const T* value) { 190 static_assert(!std::is_pointer<T>(), "No operand may have a pointer as its value"); 191 return setOperandValue(index, value, sizeof(T)); 192 } 193 setOperandValueFromMemory(uint32_t index,const Memory * memory,uint32_t offset,size_t length)194 void setOperandValueFromMemory(uint32_t index, const Memory* memory, uint32_t offset, 195 size_t length) { 196 if (ANeuralNetworksModel_setOperandValueFromMemory(mModel, index, memory->get(), offset, 197 length) != ANEURALNETWORKS_NO_ERROR) { 198 mValid = false; 199 } 200 } 201 setOperandValueFromModel(uint32_t index,const Model * value)202 void setOperandValueFromModel(uint32_t index, const Model* value) { 203 if (ANeuralNetworksModel_setOperandValueFromModel(mModel, index, value->mModel) != 204 ANEURALNETWORKS_NO_ERROR) { 205 mValid = false; 206 } 207 } 208 addOperation(ANeuralNetworksOperationType type,const std::vector<uint32_t> & inputs,const std::vector<uint32_t> & outputs)209 void addOperation(ANeuralNetworksOperationType type, const std::vector<uint32_t>& inputs, 210 const std::vector<uint32_t>& outputs) { 211 if (ANeuralNetworksModel_addOperation(mModel, type, static_cast<uint32_t>(inputs.size()), 212 inputs.data(), static_cast<uint32_t>(outputs.size()), 213 outputs.data()) != ANEURALNETWORKS_NO_ERROR) { 214 mValid = false; 215 } 216 } identifyInputsAndOutputs(const std::vector<uint32_t> & inputs,const std::vector<uint32_t> & outputs)217 void identifyInputsAndOutputs(const std::vector<uint32_t>& inputs, 218 const std::vector<uint32_t>& outputs) { 219 if (ANeuralNetworksModel_identifyInputsAndOutputs( 220 mModel, static_cast<uint32_t>(inputs.size()), inputs.data(), 221 static_cast<uint32_t>(outputs.size()), 222 outputs.data()) != ANEURALNETWORKS_NO_ERROR) { 223 mValid = false; 224 } 225 } 226 relaxComputationFloat32toFloat16(bool isRelax)227 void relaxComputationFloat32toFloat16(bool isRelax) { 228 if (ANeuralNetworksModel_relaxComputationFloat32toFloat16(mModel, isRelax) == 229 ANEURALNETWORKS_NO_ERROR) { 230 mRelaxed = isRelax; 231 } 232 } 233 getHandle()234 ANeuralNetworksModel* getHandle() const { return mModel; } isValid()235 bool isValid() const { return mValid; } isRelaxed()236 bool isRelaxed() const { return mRelaxed; } isFinished()237 bool isFinished() const { return mFinished; } 238 239 protected: 240 ANeuralNetworksModel* mModel = nullptr; 241 // We keep track of the operand ID as a convenience to the caller. 242 uint32_t mNextOperandId = 0; 243 bool mValid = true; 244 bool mRelaxed = false; 245 bool mFinished = false; 246 }; 247 248 class Compilation { 249 public: 250 // On success, createForDevice(s) will return Result::NO_ERROR and the created compilation; 251 // otherwise, it will return the error code and Compilation object wrapping a nullptr handle. createForDevice(const Model * model,const ANeuralNetworksDevice * device)252 static std::pair<Result, Compilation> createForDevice(const Model* model, 253 const ANeuralNetworksDevice* device) { 254 return createForDevices(model, {device}); 255 } createForDevices(const Model * model,const std::vector<const ANeuralNetworksDevice * > & devices)256 static std::pair<Result, Compilation> createForDevices( 257 const Model* model, const std::vector<const ANeuralNetworksDevice*>& devices) { 258 ANeuralNetworksCompilation* compilation = nullptr; 259 const Result result = static_cast<Result>(ANeuralNetworksCompilation_createForDevices( 260 model->getHandle(), devices.empty() ? nullptr : devices.data(), devices.size(), 261 &compilation)); 262 return {result, Compilation(compilation)}; 263 } 264 Compilation(const Model * model)265 Compilation(const Model* model) { 266 int result = ANeuralNetworksCompilation_create(model->getHandle(), &mCompilation); 267 if (result != 0) { 268 // TODO Handle the error 269 } 270 } 271 Compilation()272 Compilation() {} 273 ~Compilation()274 ~Compilation() { ANeuralNetworksCompilation_free(mCompilation); } 275 276 // Disallow copy semantics to ensure the runtime object can only be freed 277 // once. Copy semantics could be enabled if some sort of reference counting 278 // or deep-copy system for runtime objects is added later. 279 Compilation(const Compilation&) = delete; 280 Compilation& operator=(const Compilation&) = delete; 281 282 // Move semantics to remove access to the runtime object from the wrapper 283 // object that is being moved. This ensures the runtime object will be 284 // freed only once. Compilation(Compilation && other)285 Compilation(Compilation&& other) { *this = std::move(other); } 286 Compilation& operator=(Compilation&& other) { 287 if (this != &other) { 288 ANeuralNetworksCompilation_free(mCompilation); 289 mCompilation = other.mCompilation; 290 other.mCompilation = nullptr; 291 } 292 return *this; 293 } 294 setPreference(ExecutePreference preference)295 Result setPreference(ExecutePreference preference) { 296 return static_cast<Result>(ANeuralNetworksCompilation_setPreference( 297 mCompilation, static_cast<int32_t>(preference))); 298 } 299 setPriority(ExecutePriority priority)300 Result setPriority(ExecutePriority priority) { 301 return static_cast<Result>(ANeuralNetworksCompilation_setPriority( 302 mCompilation, static_cast<int32_t>(priority))); 303 } 304 setCaching(const std::string & cacheDir,const std::vector<uint8_t> & token)305 Result setCaching(const std::string& cacheDir, const std::vector<uint8_t>& token) { 306 if (token.size() != ANEURALNETWORKS_BYTE_SIZE_OF_CACHE_TOKEN) { 307 return Result::BAD_DATA; 308 } 309 return static_cast<Result>(ANeuralNetworksCompilation_setCaching( 310 mCompilation, cacheDir.c_str(), token.data())); 311 } 312 finish()313 Result finish() { return static_cast<Result>(ANeuralNetworksCompilation_finish(mCompilation)); } 314 getPreferredMemoryAlignmentForInput(uint32_t index,uint32_t * alignment)315 Result getPreferredMemoryAlignmentForInput(uint32_t index, uint32_t* alignment) const { 316 if (__builtin_available(android __NNAPI_FL5_MIN_ANDROID_API__, *)) { 317 return static_cast<Result>( 318 NNAPI_CALL(ANeuralNetworksCompilation_getPreferredMemoryAlignmentForInput( 319 mCompilation, index, alignment))); 320 } else { 321 return Result::FEATURE_LEVEL_TOO_LOW; 322 } 323 }; 324 getPreferredMemoryPaddingForInput(uint32_t index,uint32_t * padding)325 Result getPreferredMemoryPaddingForInput(uint32_t index, uint32_t* padding) const { 326 if (__builtin_available(android __NNAPI_FL5_MIN_ANDROID_API__, *)) { 327 return static_cast<Result>( 328 NNAPI_CALL(ANeuralNetworksCompilation_getPreferredMemoryPaddingForInput( 329 mCompilation, index, padding))); 330 } else { 331 return Result::FEATURE_LEVEL_TOO_LOW; 332 } 333 }; 334 getPreferredMemoryAlignmentForOutput(uint32_t index,uint32_t * alignment)335 Result getPreferredMemoryAlignmentForOutput(uint32_t index, uint32_t* alignment) const { 336 if (__builtin_available(android __NNAPI_FL5_MIN_ANDROID_API__, *)) { 337 return static_cast<Result>( 338 NNAPI_CALL(ANeuralNetworksCompilation_getPreferredMemoryAlignmentForOutput( 339 mCompilation, index, alignment))); 340 } else { 341 return Result::FEATURE_LEVEL_TOO_LOW; 342 } 343 }; 344 getPreferredMemoryPaddingForOutput(uint32_t index,uint32_t * padding)345 Result getPreferredMemoryPaddingForOutput(uint32_t index, uint32_t* padding) const { 346 if (__builtin_available(android __NNAPI_FL5_MIN_ANDROID_API__, *)) { 347 return static_cast<Result>( 348 NNAPI_CALL(ANeuralNetworksCompilation_getPreferredMemoryPaddingForOutput( 349 mCompilation, index, padding))); 350 } else { 351 return Result::FEATURE_LEVEL_TOO_LOW; 352 } 353 }; 354 getHandle()355 ANeuralNetworksCompilation* getHandle() const { return mCompilation; } 356 357 protected: 358 // Takes the ownership of ANeuralNetworksCompilation. Compilation(ANeuralNetworksCompilation * compilation)359 Compilation(ANeuralNetworksCompilation* compilation) : mCompilation(compilation) {} 360 361 ANeuralNetworksCompilation* mCompilation = nullptr; 362 }; 363 364 class Execution { 365 public: Execution(const Compilation * compilation)366 Execution(const Compilation* compilation) : mCompilation(compilation->getHandle()) { 367 int result = ANeuralNetworksExecution_create(compilation->getHandle(), &mExecution); 368 if (result != 0) { 369 // TODO Handle the error 370 } 371 } 372 ~Execution()373 ~Execution() { ANeuralNetworksExecution_free(mExecution); } 374 375 // Disallow copy semantics to ensure the runtime object can only be freed 376 // once. Copy semantics could be enabled if some sort of reference counting 377 // or deep-copy system for runtime objects is added later. 378 Execution(const Execution&) = delete; 379 Execution& operator=(const Execution&) = delete; 380 381 // Move semantics to remove access to the runtime object from the wrapper 382 // object that is being moved. This ensures the runtime object will be 383 // freed only once. Execution(Execution && other)384 Execution(Execution&& other) { *this = std::move(other); } 385 Execution& operator=(Execution&& other) { 386 if (this != &other) { 387 ANeuralNetworksExecution_free(mExecution); 388 mCompilation = other.mCompilation; 389 other.mCompilation = nullptr; 390 mExecution = other.mExecution; 391 other.mExecution = nullptr; 392 } 393 return *this; 394 } 395 396 Result setInput(uint32_t index, const void* buffer, size_t length, 397 const ANeuralNetworksOperandType* type = nullptr) { 398 return static_cast<Result>( 399 ANeuralNetworksExecution_setInput(mExecution, index, type, buffer, length)); 400 } 401 402 template <typename T> 403 Result setInput(uint32_t index, const T* value, 404 const ANeuralNetworksOperandType* type = nullptr) { 405 static_assert(!std::is_pointer<T>(), "No operand may have a pointer as its value"); 406 return setInput(index, value, sizeof(T), type); 407 } 408 409 Result setInputFromMemory(uint32_t index, const Memory* memory, uint32_t offset, 410 uint32_t length, const ANeuralNetworksOperandType* type = nullptr) { 411 return static_cast<Result>(ANeuralNetworksExecution_setInputFromMemory( 412 mExecution, index, type, memory->get(), offset, length)); 413 } 414 415 Result setOutput(uint32_t index, void* buffer, size_t length, 416 const ANeuralNetworksOperandType* type = nullptr) { 417 return static_cast<Result>( 418 ANeuralNetworksExecution_setOutput(mExecution, index, type, buffer, length)); 419 } 420 421 template <typename T> 422 Result setOutput(uint32_t index, T* value, const ANeuralNetworksOperandType* type = nullptr) { 423 static_assert(!std::is_pointer<T>(), "No operand may have a pointer as its value"); 424 return setOutput(index, value, sizeof(T), type); 425 } 426 427 Result setOutputFromMemory(uint32_t index, const Memory* memory, uint32_t offset, 428 uint32_t length, const ANeuralNetworksOperandType* type = nullptr) { 429 return static_cast<Result>(ANeuralNetworksExecution_setOutputFromMemory( 430 mExecution, index, type, memory->get(), offset, length)); 431 } 432 setLoopTimeout(uint64_t duration)433 Result setLoopTimeout(uint64_t duration) { 434 return static_cast<Result>(ANeuralNetworksExecution_setLoopTimeout(mExecution, duration)); 435 } 436 enableInputAndOutputPadding(bool enable)437 Result enableInputAndOutputPadding(bool enable) { 438 if (__builtin_available(android __NNAPI_FL5_MIN_ANDROID_API__, *)) { 439 return static_cast<Result>( 440 ANeuralNetworksExecution_enableInputAndOutputPadding(mExecution, enable)); 441 } else { 442 return Result::FEATURE_LEVEL_TOO_LOW; 443 } 444 } 445 setReusable(bool reusable)446 Result setReusable(bool reusable) { 447 if (__builtin_available(android __NNAPI_FL5_MIN_ANDROID_API__, *)) { 448 return static_cast<Result>( 449 NNAPI_CALL(ANeuralNetworksExecution_setReusable(mExecution, reusable))); 450 } else { 451 return Result::FEATURE_LEVEL_TOO_LOW; 452 } 453 } 454 startCompute(Event * event)455 Result startCompute(Event* event) { 456 ANeuralNetworksEvent* ev = nullptr; 457 Result result = static_cast<Result>(ANeuralNetworksExecution_startCompute(mExecution, &ev)); 458 event->set(ev); 459 return result; 460 } 461 startComputeWithDependencies(const std::vector<const Event * > & dependencies,uint64_t duration,Event * event)462 Result startComputeWithDependencies(const std::vector<const Event*>& dependencies, 463 uint64_t duration, Event* event) { 464 std::vector<const ANeuralNetworksEvent*> deps(dependencies.size()); 465 std::transform(dependencies.begin(), dependencies.end(), deps.begin(), 466 [](const Event* e) { return e->getHandle(); }); 467 ANeuralNetworksEvent* ev = nullptr; 468 Result result = static_cast<Result>(ANeuralNetworksExecution_startComputeWithDependencies( 469 mExecution, deps.data(), deps.size(), duration, &ev)); 470 event->set(ev); 471 return result; 472 } 473 474 // By default, compute() uses the synchronous API. Either an argument or 475 // setComputeMode() can be used to change the behavior of compute() to 476 // either: 477 // - use the asynchronous or fenced API and then wait for computation to complete 478 // or 479 // - use the burst API 480 // Returns the previous ComputeMode. 481 enum class ComputeMode { SYNC, ASYNC, BURST, FENCED }; setComputeMode(ComputeMode mode)482 static ComputeMode setComputeMode(ComputeMode mode) { 483 ComputeMode oldComputeMode = mComputeMode; 484 mComputeMode = mode; 485 return oldComputeMode; 486 } getComputeMode()487 static ComputeMode getComputeMode() { return mComputeMode; } 488 489 Result compute(ComputeMode computeMode = mComputeMode) { 490 switch (computeMode) { 491 case ComputeMode::SYNC: { 492 return static_cast<Result>(ANeuralNetworksExecution_compute(mExecution)); 493 } 494 case ComputeMode::ASYNC: { 495 ANeuralNetworksEvent* event = nullptr; 496 Result result = static_cast<Result>( 497 ANeuralNetworksExecution_startCompute(mExecution, &event)); 498 if (result != Result::NO_ERROR) { 499 return result; 500 } 501 // TODO how to manage the lifetime of events when multiple waiters is not 502 // clear. 503 result = static_cast<Result>(ANeuralNetworksEvent_wait(event)); 504 ANeuralNetworksEvent_free(event); 505 return result; 506 } 507 case ComputeMode::BURST: { 508 ANeuralNetworksBurst* burst = nullptr; 509 Result result = 510 static_cast<Result>(ANeuralNetworksBurst_create(mCompilation, &burst)); 511 if (result != Result::NO_ERROR) { 512 return result; 513 } 514 result = static_cast<Result>( 515 ANeuralNetworksExecution_burstCompute(mExecution, burst)); 516 ANeuralNetworksBurst_free(burst); 517 return result; 518 } 519 case ComputeMode::FENCED: { 520 ANeuralNetworksEvent* event = nullptr; 521 Result result = 522 static_cast<Result>(ANeuralNetworksExecution_startComputeWithDependencies( 523 mExecution, nullptr, 0, 0, &event)); 524 if (result != Result::NO_ERROR) { 525 return result; 526 } 527 result = static_cast<Result>(ANeuralNetworksEvent_wait(event)); 528 ANeuralNetworksEvent_free(event); 529 return result; 530 } 531 } 532 return Result::BAD_DATA; 533 } 534 getOutputOperandDimensions(uint32_t index,std::vector<uint32_t> * dimensions)535 Result getOutputOperandDimensions(uint32_t index, std::vector<uint32_t>* dimensions) { 536 uint32_t rank = 0; 537 Result result = static_cast<Result>( 538 ANeuralNetworksExecution_getOutputOperandRank(mExecution, index, &rank)); 539 dimensions->resize(rank); 540 if ((result != Result::NO_ERROR && result != Result::OUTPUT_INSUFFICIENT_SIZE) || 541 rank == 0) { 542 return result; 543 } 544 result = static_cast<Result>(ANeuralNetworksExecution_getOutputOperandDimensions( 545 mExecution, index, dimensions->data())); 546 return result; 547 } 548 getHandle()549 ANeuralNetworksExecution* getHandle() { return mExecution; }; 550 551 private: 552 ANeuralNetworksCompilation* mCompilation = nullptr; 553 ANeuralNetworksExecution* mExecution = nullptr; 554 555 // Initialized to ComputeMode::SYNC in TestNeuralNetworksWrapper.cpp. 556 static ComputeMode mComputeMode; 557 }; 558 559 } // namespace test_wrapper 560 } // namespace nn 561 } // namespace android 562 563 #endif // ANDROID_FRAMEWORKS_ML_NN_RUNTIME_TEST_TEST_NEURAL_NETWORKS_WRAPPER_H 564