blob: 2f83c5c5bd4a874162e324b940e2674cf2f0ae09 [file] [log] [blame]
Michael Butler4b276a72020-08-06 23:22:35 -07001/*
2 * Copyright (C) 2020 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#include "ResilientDevice.h"
18
Michael Butler37600582020-11-19 20:46:58 -080019#include "InvalidBuffer.h"
20#include "InvalidDevice.h"
21#include "InvalidPreparedModel.h"
Michael Butler4b276a72020-08-06 23:22:35 -070022#include "ResilientBuffer.h"
23#include "ResilientPreparedModel.h"
24
25#include <android-base/logging.h>
26#include <nnapi/IBuffer.h>
27#include <nnapi/IDevice.h>
28#include <nnapi/IPreparedModel.h>
29#include <nnapi/Result.h>
30#include <nnapi/TypeUtils.h>
31#include <nnapi/Types.h>
32
33#include <algorithm>
34#include <memory>
35#include <string>
36#include <vector>
37
38namespace android::hardware::neuralnetworks::utils {
39namespace {
40
41template <typename FnType>
42auto protect(const ResilientDevice& resilientDevice, const FnType& fn, bool blocking)
43 -> decltype(fn(*resilientDevice.getDevice())) {
44 auto device = resilientDevice.getDevice();
45 auto result = fn(*device);
46
47 // Immediately return if device is not dead.
48 if (result.has_value() || result.error().code != nn::ErrorStatus::DEAD_OBJECT) {
49 return result;
50 }
51
52 device = resilientDevice.recover(device.get(), blocking);
53 return fn(*device);
54}
55
56} // namespace
57
58nn::GeneralResult<std::shared_ptr<const ResilientDevice>> ResilientDevice::create(
59 Factory makeDevice) {
60 if (makeDevice == nullptr) {
61 return NN_ERROR(nn::ErrorStatus::INVALID_ARGUMENT)
62 << "utils::ResilientDevice::create must have non-empty makeDevice";
63 }
64 auto device = NN_TRY(makeDevice(/*blocking=*/true));
65 CHECK(device != nullptr);
66
67 auto name = device->getName();
68 auto versionString = device->getVersionString();
69 auto extensions = device->getSupportedExtensions();
70 auto capabilities = device->getCapabilities();
71
72 return std::make_shared<ResilientDevice>(PrivateConstructorTag{}, std::move(makeDevice),
73 std::move(name), std::move(versionString),
74 std::move(extensions), std::move(capabilities),
75 std::move(device));
76}
77
78ResilientDevice::ResilientDevice(PrivateConstructorTag /*tag*/, Factory makeDevice,
79 std::string name, std::string versionString,
80 std::vector<nn::Extension> extensions,
81 nn::Capabilities capabilities, nn::SharedDevice device)
82 : kMakeDevice(std::move(makeDevice)),
83 kName(std::move(name)),
84 kVersionString(std::move(versionString)),
85 kExtensions(std::move(extensions)),
86 kCapabilities(std::move(capabilities)),
87 mDevice(std::move(device)) {
88 CHECK(kMakeDevice != nullptr);
89 CHECK(mDevice != nullptr);
90}
91
92nn::SharedDevice ResilientDevice::getDevice() const {
93 std::lock_guard guard(mMutex);
94 return mDevice;
95}
96
97nn::SharedDevice ResilientDevice::recover(const nn::IDevice* failingDevice, bool blocking) const {
98 std::lock_guard guard(mMutex);
99
100 // Another caller updated the failing device.
101 if (mDevice.get() != failingDevice) {
102 return mDevice;
103 }
104
105 auto maybeDevice = kMakeDevice(blocking);
106 if (!maybeDevice.has_value()) {
107 const auto& [message, code] = maybeDevice.error();
108 LOG(ERROR) << "Failed to recover dead device with error " << code << ": " << message;
109 return mDevice;
110 }
111 auto device = std::move(maybeDevice).value();
112
Michael Butler37600582020-11-19 20:46:58 -0800113 // If recovered device has different metadata than what is cached (i.e., because it was
114 // updated), mark the device as invalid and preserve the cached data.
115 auto compare = [this, &device](auto fn) REQUIRES(mMutex) {
116 return std::invoke(fn, mDevice) != std::invoke(fn, device);
117 };
118 if (compare(&IDevice::getName) || compare(&IDevice::getVersionString) ||
119 compare(&IDevice::getFeatureLevel) || compare(&IDevice::getType) ||
120 compare(&IDevice::getSupportedExtensions) || compare(&IDevice::getCapabilities)) {
121 LOG(ERROR) << "Recovered device has different metadata than what is cached. Marking "
122 "IDevice object as invalid.";
123 device = std::make_shared<const InvalidDevice>(
124 kName, kVersionString, mDevice->getFeatureLevel(), mDevice->getType(), kExtensions,
125 kCapabilities, mDevice->getNumberOfCacheFilesNeeded());
126 mIsValid = false;
127 }
Michael Butler4b276a72020-08-06 23:22:35 -0700128
129 mDevice = std::move(device);
130 return mDevice;
131}
132
133const std::string& ResilientDevice::getName() const {
134 return kName;
135}
136
137const std::string& ResilientDevice::getVersionString() const {
138 return kVersionString;
139}
140
141nn::Version ResilientDevice::getFeatureLevel() const {
142 return getDevice()->getFeatureLevel();
143}
144
145nn::DeviceType ResilientDevice::getType() const {
146 return getDevice()->getType();
147}
148
149const std::vector<nn::Extension>& ResilientDevice::getSupportedExtensions() const {
150 return kExtensions;
151}
152
153const nn::Capabilities& ResilientDevice::getCapabilities() const {
154 return kCapabilities;
155}
156
157std::pair<uint32_t, uint32_t> ResilientDevice::getNumberOfCacheFilesNeeded() const {
158 return getDevice()->getNumberOfCacheFilesNeeded();
159}
160
161nn::GeneralResult<void> ResilientDevice::wait() const {
162 const auto fn = [](const nn::IDevice& device) { return device.wait(); };
163 return protect(*this, fn, /*blocking=*/true);
164}
165
166nn::GeneralResult<std::vector<bool>> ResilientDevice::getSupportedOperations(
167 const nn::Model& model) const {
168 const auto fn = [&model](const nn::IDevice& device) {
169 return device.getSupportedOperations(model);
170 };
171 return protect(*this, fn, /*blocking=*/false);
172}
173
174nn::GeneralResult<nn::SharedPreparedModel> ResilientDevice::prepareModel(
175 const nn::Model& model, nn::ExecutionPreference preference, nn::Priority priority,
Slava Shklyaev49817a02020-10-27 18:44:01 +0000176 nn::OptionalTimePoint deadline, const std::vector<nn::SharedHandle>& modelCache,
177 const std::vector<nn::SharedHandle>& dataCache, const nn::CacheToken& token) const {
Michael Butler4b276a72020-08-06 23:22:35 -0700178 auto self = shared_from_this();
179 ResilientPreparedModel::Factory makePreparedModel =
180 [device = std::move(self), model, preference, priority, deadline, modelCache, dataCache,
181 token](bool blocking) -> nn::GeneralResult<nn::SharedPreparedModel> {
182 return device->prepareModelInternal(blocking, model, preference, priority, deadline,
183 modelCache, dataCache, token);
184 };
185 return ResilientPreparedModel::create(std::move(makePreparedModel));
186}
187
188nn::GeneralResult<nn::SharedPreparedModel> ResilientDevice::prepareModelFromCache(
Slava Shklyaev49817a02020-10-27 18:44:01 +0000189 nn::OptionalTimePoint deadline, const std::vector<nn::SharedHandle>& modelCache,
190 const std::vector<nn::SharedHandle>& dataCache, const nn::CacheToken& token) const {
Michael Butler4b276a72020-08-06 23:22:35 -0700191 auto self = shared_from_this();
192 ResilientPreparedModel::Factory makePreparedModel =
193 [device = std::move(self), deadline, modelCache, dataCache,
194 token](bool blocking) -> nn::GeneralResult<nn::SharedPreparedModel> {
195 return device->prepareModelFromCacheInternal(blocking, deadline, modelCache, dataCache,
196 token);
197 };
198 return ResilientPreparedModel::create(std::move(makePreparedModel));
199}
200
201nn::GeneralResult<nn::SharedBuffer> ResilientDevice::allocate(
202 const nn::BufferDesc& desc, const std::vector<nn::SharedPreparedModel>& preparedModels,
203 const std::vector<nn::BufferRole>& inputRoles,
204 const std::vector<nn::BufferRole>& outputRoles) const {
205 auto self = shared_from_this();
206 ResilientBuffer::Factory makeBuffer =
207 [device = std::move(self), desc, preparedModels, inputRoles,
208 outputRoles](bool blocking) -> nn::GeneralResult<nn::SharedBuffer> {
209 return device->allocateInternal(blocking, desc, preparedModels, inputRoles, outputRoles);
210 };
211 return ResilientBuffer::create(std::move(makeBuffer));
212}
213
Michael Butler37600582020-11-19 20:46:58 -0800214bool ResilientDevice::isValidInternal() const {
215 std::lock_guard hold(mMutex);
216 return mIsValid;
217}
218
Michael Butler4b276a72020-08-06 23:22:35 -0700219nn::GeneralResult<nn::SharedPreparedModel> ResilientDevice::prepareModelInternal(
220 bool blocking, const nn::Model& model, nn::ExecutionPreference preference,
221 nn::Priority priority, nn::OptionalTimePoint deadline,
Slava Shklyaev49817a02020-10-27 18:44:01 +0000222 const std::vector<nn::SharedHandle>& modelCache,
223 const std::vector<nn::SharedHandle>& dataCache, const nn::CacheToken& token) const {
Michael Butler37600582020-11-19 20:46:58 -0800224 if (!isValidInternal()) {
225 return std::make_shared<const InvalidPreparedModel>();
226 }
Michael Butler4b276a72020-08-06 23:22:35 -0700227 const auto fn = [&model, preference, priority, deadline, &modelCache, &dataCache,
228 token](const nn::IDevice& device) {
229 return device.prepareModel(model, preference, priority, deadline, modelCache, dataCache,
230 token);
231 };
232 return protect(*this, fn, blocking);
233}
234
235nn::GeneralResult<nn::SharedPreparedModel> ResilientDevice::prepareModelFromCacheInternal(
236 bool blocking, nn::OptionalTimePoint deadline,
Slava Shklyaev49817a02020-10-27 18:44:01 +0000237 const std::vector<nn::SharedHandle>& modelCache,
238 const std::vector<nn::SharedHandle>& dataCache, const nn::CacheToken& token) const {
Michael Butler37600582020-11-19 20:46:58 -0800239 if (!isValidInternal()) {
240 return std::make_shared<const InvalidPreparedModel>();
241 }
Michael Butler4b276a72020-08-06 23:22:35 -0700242 const auto fn = [deadline, &modelCache, &dataCache, token](const nn::IDevice& device) {
243 return device.prepareModelFromCache(deadline, modelCache, dataCache, token);
244 };
245 return protect(*this, fn, blocking);
246}
247
248nn::GeneralResult<nn::SharedBuffer> ResilientDevice::allocateInternal(
249 bool blocking, const nn::BufferDesc& desc,
250 const std::vector<nn::SharedPreparedModel>& preparedModels,
251 const std::vector<nn::BufferRole>& inputRoles,
252 const std::vector<nn::BufferRole>& outputRoles) const {
Michael Butler37600582020-11-19 20:46:58 -0800253 if (!isValidInternal()) {
254 return std::make_shared<const InvalidBuffer>();
255 }
Michael Butler4b276a72020-08-06 23:22:35 -0700256 const auto fn = [&desc, &preparedModels, &inputRoles, &outputRoles](const nn::IDevice& device) {
257 return device.allocate(desc, preparedModels, inputRoles, outputRoles);
258 };
259 return protect(*this, fn, blocking);
260}
261
262} // namespace android::hardware::neuralnetworks::utils