ExecutionBurstController.cpp 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584
  1. /*
  2. * Copyright (C) 2019 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. #define LOG_TAG "ExecutionBurstController"
  17. #include "ExecutionBurstController.h"
  18. #include <android-base/logging.h>
  19. #include <cstring>
  20. #include <limits>
  21. #include <string>
  22. #include "Tracing.h"
  23. namespace android::nn {
  24. namespace {
  25. using ::android::hardware::MQDescriptorSync;
  26. using FmqRequestDescriptor = MQDescriptorSync<FmqRequestDatum>;
  27. using FmqResultDescriptor = MQDescriptorSync<FmqResultDatum>;
  28. constexpr Timing kNoTiming = {std::numeric_limits<uint64_t>::max(),
  29. std::numeric_limits<uint64_t>::max()};
  30. class BurstContextDeathHandler : public hardware::hidl_death_recipient {
  31. public:
  32. using Callback = std::function<void()>;
  33. BurstContextDeathHandler(const Callback& onDeathCallback) : mOnDeathCallback(onDeathCallback) {
  34. CHECK(onDeathCallback != nullptr);
  35. }
  36. void serviceDied(uint64_t /*cookie*/, const wp<hidl::base::V1_0::IBase>& /*who*/) override {
  37. LOG(ERROR) << "BurstContextDeathHandler::serviceDied -- service unexpectedly died!";
  38. mOnDeathCallback();
  39. }
  40. private:
  41. const Callback mOnDeathCallback;
  42. };
  43. } // anonymous namespace
  44. // serialize a request into a packet
  45. std::vector<FmqRequestDatum> serialize(const Request& request, MeasureTiming measure,
  46. const std::vector<int32_t>& slots) {
  47. // count how many elements need to be sent for a request
  48. size_t count = 2 + request.inputs.size() + request.outputs.size() + request.pools.size();
  49. for (const auto& input : request.inputs) {
  50. count += input.dimensions.size();
  51. }
  52. for (const auto& output : request.outputs) {
  53. count += output.dimensions.size();
  54. }
  55. // create buffer to temporarily store elements
  56. std::vector<FmqRequestDatum> data;
  57. data.reserve(count);
  58. // package packetInfo
  59. {
  60. FmqRequestDatum datum;
  61. datum.packetInformation(
  62. {/*.packetSize=*/static_cast<uint32_t>(count),
  63. /*.numberOfInputOperands=*/static_cast<uint32_t>(request.inputs.size()),
  64. /*.numberOfOutputOperands=*/static_cast<uint32_t>(request.outputs.size()),
  65. /*.numberOfPools=*/static_cast<uint32_t>(request.pools.size())});
  66. data.push_back(datum);
  67. }
  68. // package input data
  69. for (const auto& input : request.inputs) {
  70. // package operand information
  71. FmqRequestDatum datum;
  72. datum.inputOperandInformation(
  73. {/*.hasNoValue=*/input.hasNoValue,
  74. /*.location=*/input.location,
  75. /*.numberOfDimensions=*/static_cast<uint32_t>(input.dimensions.size())});
  76. data.push_back(datum);
  77. // package operand dimensions
  78. for (uint32_t dimension : input.dimensions) {
  79. FmqRequestDatum datum;
  80. datum.inputOperandDimensionValue(dimension);
  81. data.push_back(datum);
  82. }
  83. }
  84. // package output data
  85. for (const auto& output : request.outputs) {
  86. // package operand information
  87. FmqRequestDatum datum;
  88. datum.outputOperandInformation(
  89. {/*.hasNoValue=*/output.hasNoValue,
  90. /*.location=*/output.location,
  91. /*.numberOfDimensions=*/static_cast<uint32_t>(output.dimensions.size())});
  92. data.push_back(datum);
  93. // package operand dimensions
  94. for (uint32_t dimension : output.dimensions) {
  95. FmqRequestDatum datum;
  96. datum.outputOperandDimensionValue(dimension);
  97. data.push_back(datum);
  98. }
  99. }
  100. // package pool identifier
  101. for (int32_t slot : slots) {
  102. FmqRequestDatum datum;
  103. datum.poolIdentifier(slot);
  104. data.push_back(datum);
  105. }
  106. // package measureTiming
  107. {
  108. FmqRequestDatum datum;
  109. datum.measureTiming(measure);
  110. data.push_back(datum);
  111. }
  112. // return packet
  113. return data;
  114. }
  115. // deserialize a packet into the result
  116. std::optional<std::tuple<ErrorStatus, std::vector<OutputShape>, Timing>> deserialize(
  117. const std::vector<FmqResultDatum>& data) {
  118. using discriminator = FmqResultDatum::hidl_discriminator;
  119. std::vector<OutputShape> outputShapes;
  120. size_t index = 0;
  121. // validate packet information
  122. if (data.size() == 0 || data[index].getDiscriminator() != discriminator::packetInformation) {
  123. LOG(ERROR) << "FMQ Result packet ill-formed";
  124. return std::nullopt;
  125. }
  126. // unpackage packet information
  127. const FmqResultDatum::PacketInformation& packetInfo = data[index].packetInformation();
  128. index++;
  129. const uint32_t packetSize = packetInfo.packetSize;
  130. const ErrorStatus errorStatus = packetInfo.errorStatus;
  131. const uint32_t numberOfOperands = packetInfo.numberOfOperands;
  132. // verify packet size
  133. if (data.size() != packetSize) {
  134. LOG(ERROR) << "FMQ Result packet ill-formed";
  135. return std::nullopt;
  136. }
  137. // unpackage operands
  138. for (size_t operand = 0; operand < numberOfOperands; ++operand) {
  139. // validate operand information
  140. if (data[index].getDiscriminator() != discriminator::operandInformation) {
  141. LOG(ERROR) << "FMQ Result packet ill-formed";
  142. return std::nullopt;
  143. }
  144. // unpackage operand information
  145. const FmqResultDatum::OperandInformation& operandInfo = data[index].operandInformation();
  146. index++;
  147. const bool isSufficient = operandInfo.isSufficient;
  148. const uint32_t numberOfDimensions = operandInfo.numberOfDimensions;
  149. // unpackage operand dimensions
  150. std::vector<uint32_t> dimensions;
  151. dimensions.reserve(numberOfDimensions);
  152. for (size_t i = 0; i < numberOfDimensions; ++i) {
  153. // validate dimension
  154. if (data[index].getDiscriminator() != discriminator::operandDimensionValue) {
  155. LOG(ERROR) << "FMQ Result packet ill-formed";
  156. return std::nullopt;
  157. }
  158. // unpackage dimension
  159. const uint32_t dimension = data[index].operandDimensionValue();
  160. index++;
  161. // store result
  162. dimensions.push_back(dimension);
  163. }
  164. // store result
  165. outputShapes.push_back({/*.dimensions=*/dimensions, /*.isSufficient=*/isSufficient});
  166. }
  167. // validate execution timing
  168. if (data[index].getDiscriminator() != discriminator::executionTiming) {
  169. LOG(ERROR) << "FMQ Result packet ill-formed";
  170. return std::nullopt;
  171. }
  172. // unpackage execution timing
  173. const Timing timing = data[index].executionTiming();
  174. index++;
  175. // validate packet information
  176. if (index != packetSize) {
  177. LOG(ERROR) << "FMQ Result packet ill-formed";
  178. return std::nullopt;
  179. }
  180. // return result
  181. return std::make_tuple(errorStatus, std::move(outputShapes), timing);
  182. }
  183. std::pair<std::unique_ptr<ResultChannelReceiver>, const FmqResultDescriptor*>
  184. ResultChannelReceiver::create(size_t channelLength, bool blocking) {
  185. std::unique_ptr<FmqResultChannel> fmqResultChannel =
  186. std::make_unique<FmqResultChannel>(channelLength, /*confEventFlag=*/blocking);
  187. if (!fmqResultChannel->isValid()) {
  188. LOG(ERROR) << "Unable to create ResultChannelReceiver";
  189. return {nullptr, nullptr};
  190. }
  191. const FmqResultDescriptor* descriptor = fmqResultChannel->getDesc();
  192. return std::make_pair(
  193. std::make_unique<ResultChannelReceiver>(std::move(fmqResultChannel), blocking),
  194. descriptor);
  195. }
  196. ResultChannelReceiver::ResultChannelReceiver(std::unique_ptr<FmqResultChannel> fmqResultChannel,
  197. bool blocking)
  198. : mFmqResultChannel(std::move(fmqResultChannel)), mBlocking(blocking) {}
  199. std::optional<std::tuple<ErrorStatus, std::vector<OutputShape>, Timing>>
  200. ResultChannelReceiver::getBlocking() {
  201. const auto packet = getPacketBlocking();
  202. if (!packet) {
  203. return std::nullopt;
  204. }
  205. return deserialize(*packet);
  206. }
  207. void ResultChannelReceiver::invalidate() {
  208. mValid = false;
  209. // force unblock
  210. // ExecutionBurstController waits on a result packet after sending a
  211. // request. If the driver containing ExecutionBurstServer crashes, the
  212. // controller will still be waiting on the futex (assuming mBlocking is
  213. // true). This force unblock wakes up any thread waiting on the futex.
  214. if (mBlocking) {
  215. // TODO: look for a different/better way to signal/notify the futex to
  216. // wake up any thread waiting on it
  217. FmqResultDatum datum;
  218. datum.packetInformation({/*.packetSize=*/0, /*.errorStatus=*/ErrorStatus::GENERAL_FAILURE,
  219. /*.numberOfOperands=*/0});
  220. mFmqResultChannel->writeBlocking(&datum, 1);
  221. }
  222. }
  223. std::optional<std::vector<FmqResultDatum>> ResultChannelReceiver::getPacketBlocking() {
  224. using discriminator = FmqResultDatum::hidl_discriminator;
  225. if (!mValid) {
  226. return std::nullopt;
  227. }
  228. // wait for result packet and read first element of result packet
  229. FmqResultDatum datum;
  230. bool success = true;
  231. if (mBlocking) {
  232. success = mFmqResultChannel->readBlocking(&datum, 1);
  233. } else {
  234. while ((success = mValid.load(std::memory_order_relaxed)) &&
  235. !mFmqResultChannel->read(&datum, 1)) {
  236. }
  237. }
  238. // retrieve remaining elements
  239. // NOTE: all of the data is already available at this point, so there's no
  240. // need to do a blocking wait to wait for more data. This is known because
  241. // in FMQ, all writes are published (made available) atomically. Currently,
  242. // the producer always publishes the entire packet in one function call, so
  243. // if the first element of the packet is available, the remaining elements
  244. // are also available.
  245. const size_t count = mFmqResultChannel->availableToRead();
  246. std::vector<FmqResultDatum> packet(count + 1);
  247. std::memcpy(&packet.front(), &datum, sizeof(datum));
  248. success &= mFmqResultChannel->read(packet.data() + 1, count);
  249. if (!mValid) {
  250. return std::nullopt;
  251. }
  252. // ensure packet was successfully received
  253. if (!success) {
  254. LOG(ERROR) << "Error receiving packet";
  255. return std::nullopt;
  256. }
  257. return std::make_optional(std::move(packet));
  258. }
  259. std::pair<std::unique_ptr<RequestChannelSender>, const FmqRequestDescriptor*>
  260. RequestChannelSender::create(size_t channelLength, bool blocking) {
  261. std::unique_ptr<FmqRequestChannel> fmqRequestChannel =
  262. std::make_unique<FmqRequestChannel>(channelLength, /*confEventFlag=*/blocking);
  263. if (!fmqRequestChannel->isValid()) {
  264. LOG(ERROR) << "Unable to create RequestChannelSender";
  265. return {nullptr, nullptr};
  266. }
  267. const FmqRequestDescriptor* descriptor = fmqRequestChannel->getDesc();
  268. return std::make_pair(
  269. std::make_unique<RequestChannelSender>(std::move(fmqRequestChannel), blocking),
  270. descriptor);
  271. }
  272. RequestChannelSender::RequestChannelSender(std::unique_ptr<FmqRequestChannel> fmqRequestChannel,
  273. bool blocking)
  274. : mFmqRequestChannel(std::move(fmqRequestChannel)), mBlocking(blocking) {}
  275. bool RequestChannelSender::send(const Request& request, MeasureTiming measure,
  276. const std::vector<int32_t>& slots) {
  277. const std::vector<FmqRequestDatum> serialized = serialize(request, measure, slots);
  278. return sendPacket(serialized);
  279. }
  280. bool RequestChannelSender::sendPacket(const std::vector<FmqRequestDatum>& packet) {
  281. if (!mValid) {
  282. return false;
  283. }
  284. if (packet.size() > mFmqRequestChannel->availableToWrite()) {
  285. LOG(ERROR)
  286. << "RequestChannelSender::sendPacket -- packet size exceeds size available in FMQ";
  287. return false;
  288. }
  289. if (mBlocking) {
  290. return mFmqRequestChannel->writeBlocking(packet.data(), packet.size());
  291. } else {
  292. return mFmqRequestChannel->write(packet.data(), packet.size());
  293. }
  294. }
  295. void RequestChannelSender::invalidate() {
  296. mValid = false;
  297. }
  298. Return<void> ExecutionBurstController::ExecutionBurstCallback::getMemories(
  299. const hidl_vec<int32_t>& slots, getMemories_cb cb) {
  300. std::lock_guard<std::mutex> guard(mMutex);
  301. // get all memories
  302. hidl_vec<hidl_memory> memories(slots.size());
  303. std::transform(slots.begin(), slots.end(), memories.begin(), [this](int32_t slot) {
  304. return slot < mMemoryCache.size() ? mMemoryCache[slot] : hidl_memory{};
  305. });
  306. // ensure all memories are valid
  307. if (!std::all_of(memories.begin(), memories.end(),
  308. [](const hidl_memory& memory) { return memory.valid(); })) {
  309. cb(ErrorStatus::INVALID_ARGUMENT, {});
  310. return Void();
  311. }
  312. // return successful
  313. cb(ErrorStatus::NONE, std::move(memories));
  314. return Void();
  315. }
  316. std::vector<int32_t> ExecutionBurstController::ExecutionBurstCallback::getSlots(
  317. const hidl_vec<hidl_memory>& memories, const std::vector<intptr_t>& keys) {
  318. std::lock_guard<std::mutex> guard(mMutex);
  319. // retrieve (or bind) all slots corresponding to memories
  320. std::vector<int32_t> slots;
  321. slots.reserve(memories.size());
  322. for (size_t i = 0; i < memories.size(); ++i) {
  323. slots.push_back(getSlotLocked(memories[i], keys[i]));
  324. }
  325. return slots;
  326. }
  327. std::pair<bool, int32_t> ExecutionBurstController::ExecutionBurstCallback::freeMemory(
  328. intptr_t key) {
  329. std::lock_guard<std::mutex> guard(mMutex);
  330. auto iter = mMemoryIdToSlot.find(key);
  331. if (iter == mMemoryIdToSlot.end()) {
  332. return {false, 0};
  333. }
  334. const int32_t slot = iter->second;
  335. mMemoryIdToSlot.erase(key);
  336. mMemoryCache[slot] = {};
  337. mFreeSlots.push(slot);
  338. return {true, slot};
  339. }
  340. int32_t ExecutionBurstController::ExecutionBurstCallback::getSlotLocked(const hidl_memory& memory,
  341. intptr_t key) {
  342. auto iter = mMemoryIdToSlot.find(key);
  343. if (iter == mMemoryIdToSlot.end()) {
  344. const int32_t slot = allocateSlotLocked();
  345. mMemoryIdToSlot[key] = slot;
  346. mMemoryCache[slot] = memory;
  347. return slot;
  348. } else {
  349. const int32_t slot = iter->second;
  350. return slot;
  351. }
  352. }
  353. int32_t ExecutionBurstController::ExecutionBurstCallback::allocateSlotLocked() {
  354. constexpr size_t kMaxNumberOfSlots = std::numeric_limits<int32_t>::max();
  355. // if there is a free slot, use it
  356. if (mFreeSlots.size() > 0) {
  357. const int32_t slot = mFreeSlots.top();
  358. mFreeSlots.pop();
  359. return slot;
  360. }
  361. // otherwise use a slot for the first time
  362. CHECK(mMemoryCache.size() < kMaxNumberOfSlots) << "Exceeded maximum number of slots!";
  363. const int32_t slot = static_cast<int32_t>(mMemoryCache.size());
  364. mMemoryCache.emplace_back();
  365. return slot;
  366. }
  367. std::unique_ptr<ExecutionBurstController> ExecutionBurstController::create(
  368. const sp<IPreparedModel>& preparedModel, bool blocking) {
  369. // check inputs
  370. if (preparedModel == nullptr) {
  371. LOG(ERROR) << "ExecutionBurstController::create passed a nullptr";
  372. return nullptr;
  373. }
  374. // create callback object
  375. sp<ExecutionBurstCallback> callback = new ExecutionBurstCallback();
  376. // create FMQ objects
  377. auto [requestChannelSenderTemp, requestChannelDescriptor] =
  378. RequestChannelSender::create(kExecutionBurstChannelLength, blocking);
  379. auto [resultChannelReceiverTemp, resultChannelDescriptor] =
  380. ResultChannelReceiver::create(kExecutionBurstChannelLength, blocking);
  381. std::shared_ptr<RequestChannelSender> requestChannelSender =
  382. std::move(requestChannelSenderTemp);
  383. std::shared_ptr<ResultChannelReceiver> resultChannelReceiver =
  384. std::move(resultChannelReceiverTemp);
  385. // check FMQ objects
  386. if (!requestChannelSender || !resultChannelReceiver || !requestChannelDescriptor ||
  387. !resultChannelDescriptor) {
  388. LOG(ERROR) << "ExecutionBurstController::create failed to create FastMessageQueue";
  389. return nullptr;
  390. }
  391. // configure burst
  392. ErrorStatus errorStatus;
  393. sp<IBurstContext> burstContext;
  394. const Return<void> ret = preparedModel->configureExecutionBurst(
  395. callback, *requestChannelDescriptor, *resultChannelDescriptor,
  396. [&errorStatus, &burstContext](ErrorStatus status, const sp<IBurstContext>& context) {
  397. errorStatus = status;
  398. burstContext = context;
  399. });
  400. // check burst
  401. if (!ret.isOk()) {
  402. LOG(ERROR) << "IPreparedModel::configureExecutionBurst failed with description "
  403. << ret.description();
  404. return nullptr;
  405. }
  406. if (errorStatus != ErrorStatus::NONE) {
  407. LOG(ERROR) << "IPreparedModel::configureExecutionBurst failed with status "
  408. << toString(errorStatus);
  409. return nullptr;
  410. }
  411. if (burstContext == nullptr) {
  412. LOG(ERROR) << "IPreparedModel::configureExecutionBurst returned nullptr for burst";
  413. return nullptr;
  414. }
  415. // create death handler object
  416. BurstContextDeathHandler::Callback onDeathCallback = [requestChannelSender,
  417. resultChannelReceiver] {
  418. requestChannelSender->invalidate();
  419. resultChannelReceiver->invalidate();
  420. };
  421. const sp<BurstContextDeathHandler> deathHandler = new BurstContextDeathHandler(onDeathCallback);
  422. // linkToDeath registers a callback that will be invoked on service death to
  423. // proactively handle service crashes. If the linkToDeath call fails,
  424. // asynchronous calls are susceptible to hangs if the service crashes before
  425. // providing the response.
  426. const Return<bool> deathHandlerRet = burstContext->linkToDeath(deathHandler, 0);
  427. if (!deathHandlerRet.isOk() || deathHandlerRet != true) {
  428. LOG(ERROR) << "ExecutionBurstController::create -- Failed to register a death recipient "
  429. "for the IBurstContext object.";
  430. return nullptr;
  431. }
  432. // make and return controller
  433. return std::make_unique<ExecutionBurstController>(requestChannelSender, resultChannelReceiver,
  434. burstContext, callback, deathHandler);
  435. }
  436. ExecutionBurstController::ExecutionBurstController(
  437. const std::shared_ptr<RequestChannelSender>& requestChannelSender,
  438. const std::shared_ptr<ResultChannelReceiver>& resultChannelReceiver,
  439. const sp<IBurstContext>& burstContext, const sp<ExecutionBurstCallback>& callback,
  440. const sp<hardware::hidl_death_recipient>& deathHandler)
  441. : mRequestChannelSender(requestChannelSender),
  442. mResultChannelReceiver(resultChannelReceiver),
  443. mBurstContext(burstContext),
  444. mMemoryCache(callback),
  445. mDeathHandler(deathHandler) {}
  446. ExecutionBurstController::~ExecutionBurstController() {
  447. // It is safe to ignore any errors resulting from this unlinkToDeath call
  448. // because the ExecutionBurstController object is already being destroyed
  449. // and its underlying IBurstContext object is no longer being used by the NN
  450. // runtime.
  451. if (mDeathHandler) {
  452. mBurstContext->unlinkToDeath(mDeathHandler).isOk();
  453. }
  454. }
  455. std::tuple<ErrorStatus, std::vector<OutputShape>, Timing> ExecutionBurstController::compute(
  456. const Request& request, MeasureTiming measure, const std::vector<intptr_t>& memoryIds) {
  457. auto [status, outputShapes, timing, fallback] = tryCompute(request, measure, memoryIds);
  458. (void)fallback; // ignore fallback field
  459. return {status, std::move(outputShapes), timing};
  460. }
  461. std::tuple<ErrorStatus, std::vector<OutputShape>, Timing, bool>
  462. ExecutionBurstController::tryCompute(const Request& request, MeasureTiming measure,
  463. const std::vector<intptr_t>& memoryIds) {
  464. NNTRACE_FULL(NNTRACE_LAYER_IPC, NNTRACE_PHASE_EXECUTION, "ExecutionBurstController::compute");
  465. std::lock_guard<std::mutex> guard(mMutex);
  466. // send request packet
  467. const std::vector<int32_t> slots = mMemoryCache->getSlots(request.pools, memoryIds);
  468. const bool success = mRequestChannelSender->send(request, measure, slots);
  469. if (!success) {
  470. LOG(ERROR) << "Error sending FMQ packet";
  471. // only use fallback execution path if the packet could not be sent
  472. return {ErrorStatus::GENERAL_FAILURE, {}, kNoTiming, /*fallback=*/true};
  473. }
  474. // get result packet
  475. const auto result = mResultChannelReceiver->getBlocking();
  476. if (!result) {
  477. LOG(ERROR) << "Error retrieving FMQ packet";
  478. // only use fallback execution path if the packet could not be sent
  479. return {ErrorStatus::GENERAL_FAILURE, {}, kNoTiming, /*fallback=*/false};
  480. }
  481. // unpack results and return (only use fallback execution path if the
  482. // packet could not be sent)
  483. auto [status, outputShapes, timing] = std::move(*result);
  484. return {status, std::move(outputShapes), timing, /*fallback=*/false};
  485. }
  486. void ExecutionBurstController::freeMemory(intptr_t key) {
  487. std::lock_guard<std::mutex> guard(mMutex);
  488. bool valid;
  489. int32_t slot;
  490. std::tie(valid, slot) = mMemoryCache->freeMemory(key);
  491. if (valid) {
  492. mBurstContext->freeMemory(slot).isOk();
  493. }
  494. }
  495. } // namespace android::nn