1 /*
2 * Copyright (C) 2021 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 "LimitedSupportDevice.h"
18
19 #include <android-base/logging.h>
20 #include <nnapi/IBuffer.h>
21 #include <nnapi/IDevice.h>
22 #include <nnapi/IPreparedModel.h>
23 #include <nnapi/OperandTypes.h>
24 #include <nnapi/Result.h>
25 #include <nnapi/TypeUtils.h>
26 #include <nnapi/Types.h>
27 #include <nnapi/Validation.h>
28
29 #include <algorithm>
30 #include <any>
31 #include <chrono>
32 #include <functional>
33 #include <iterator>
34 #include <memory>
35 #include <optional>
36 #include <set>
37 #include <string>
38 #include <utility>
39 #include <vector>
40
41 #include "CanonicalDevice.h"
42
43 namespace android::nn::sample {
44 namespace {
45
makeCapabilitiesFloatFast()46 Capabilities makeCapabilitiesFloatFast() {
47 const Capabilities::PerformanceInfo defaultInfo = {.execTime = 1.0f, .powerUsage = 1.0f};
48 const Capabilities::PerformanceInfo float32Info = {.execTime = 0.8f, .powerUsage = 1.2f};
49 const Capabilities::PerformanceInfo relaxedInfo = {.execTime = 0.7f, .powerUsage = 1.1f};
50 return makeCapabilities(defaultInfo, float32Info, relaxedInfo);
51 }
52
makeCapabilitiesFloatSlow()53 Capabilities makeCapabilitiesFloatSlow() {
54 const Capabilities::PerformanceInfo defaultInfo = {.execTime = 1.0f, .powerUsage = 1.0f};
55 const Capabilities::PerformanceInfo float32Info = {.execTime = 1.3f, .powerUsage = 0.7f};
56 const Capabilities::PerformanceInfo relaxedInfo = {.execTime = 1.2f, .powerUsage = 0.6f};
57 return makeCapabilities(defaultInfo, float32Info, relaxedInfo);
58 }
59
makeCapabilitiesMinimal()60 Capabilities makeCapabilitiesMinimal() {
61 const Capabilities::PerformanceInfo defaultInfo = {.execTime = 1.0f, .powerUsage = 1.0f};
62 const Capabilities::PerformanceInfo float32Info = {.execTime = 0.4f, .powerUsage = 0.5f};
63 const Capabilities::PerformanceInfo relaxedInfo = {.execTime = 0.4f, .powerUsage = 0.5f};
64 return makeCapabilities(defaultInfo, float32Info, relaxedInfo);
65 }
66
makeCapabilitiesQuant()67 Capabilities makeCapabilitiesQuant() {
68 const Capabilities::PerformanceInfo info = {.execTime = 50.0f, .powerUsage = 1.0f};
69 return makeCapabilities(info, info, info);
70 }
71
getSupportedOperationsFloat(const Model & model)72 GeneralResult<std::vector<bool>> getSupportedOperationsFloat(const Model& model) {
73 const size_t count = model.main.operations.size();
74 std::vector<bool> supported(count);
75 for (size_t i = 0; i < count; i++) {
76 const Operation& operation = model.main.operations[i];
77 if (!isExtension(operation.type) && !operation.inputs.empty()) {
78 const Operand& firstOperand = model.main.operands[operation.inputs[0]];
79 supported[i] = firstOperand.type == OperandType::TENSOR_FLOAT32;
80 }
81 }
82 return supported;
83 }
84
getSupportedOperationsMinimal(const Model & model)85 GeneralResult<std::vector<bool>> getSupportedOperationsMinimal(const Model& model) {
86 const size_t count = model.main.operations.size();
87 std::vector<bool> supported(count);
88 // Simulate supporting just a few ops
89 for (size_t i = 0; i < count; i++) {
90 supported[i] = false;
91 const Operation& operation = model.main.operations[i];
92 switch (operation.type) {
93 case OperationType::ADD:
94 case OperationType::CONCATENATION:
95 case OperationType::CONV_2D: {
96 const Operand& firstOperand = model.main.operands[operation.inputs[0]];
97 if (firstOperand.type == OperandType::TENSOR_FLOAT32) {
98 supported[i] = true;
99 }
100 break;
101 }
102 default:
103 break;
104 }
105 }
106 return supported;
107 }
108
isQuantized(OperandType opType)109 bool isQuantized(OperandType opType) {
110 return opType == OperandType::TENSOR_QUANT8_ASYMM ||
111 opType == OperandType::TENSOR_QUANT8_ASYMM_SIGNED;
112 }
113
getSupportedOperationsQuant(const Model & model)114 GeneralResult<std::vector<bool>> getSupportedOperationsQuant(const Model& model) {
115 const size_t count = model.main.operations.size();
116 std::vector<bool> supported(count);
117 for (size_t i = 0; i < count; i++) {
118 const Operation& operation = model.main.operations[i];
119 if (!isExtension(operation.type) && !operation.inputs.empty()) {
120 const Operand& firstOperand = model.main.operands[operation.inputs[0]];
121 supported[i] = isQuantized(firstOperand.type);
122 if (operation.type == OperationType::SELECT) {
123 const Operand& secondOperand = model.main.operands[operation.inputs[1]];
124 supported[i] = isQuantized(secondOperand.type);
125 }
126 }
127 }
128 return supported;
129 }
130
makeDevice(std::string name,Capabilities capabilities,LimitedSupportDevice::SupportedOperationsFunction getSupportedOperations)131 SharedDevice makeDevice(std::string name, Capabilities capabilities,
132 LimitedSupportDevice::SupportedOperationsFunction getSupportedOperations) {
133 auto device = std::make_shared<const Device>(std::move(name));
134 auto limitedDevice = std::make_shared<const LimitedSupportDevice>(
135 std::move(device), std::move(capabilities), std::move(getSupportedOperations));
136 return limitedDevice;
137 }
138
139 } // namespace
140
LimitedSupportDevice(SharedDevice device,Capabilities capabilities,SupportedOperationsFunction supportedOperationsFunction)141 LimitedSupportDevice::LimitedSupportDevice(SharedDevice device, Capabilities capabilities,
142 SupportedOperationsFunction supportedOperationsFunction)
143 : kDevice(std::move(device)),
144 kCapabilities(std::move(capabilities)),
145 kSupportedOperationsFunction(std::move(supportedOperationsFunction)) {
146 CHECK(kDevice != nullptr);
147 CHECK(kSupportedOperationsFunction != nullptr);
148 const auto result = validate(kCapabilities);
149 CHECK(result.has_value()) << result.error();
150 }
151
getName() const152 const std::string& LimitedSupportDevice::getName() const {
153 return kDevice->getName();
154 }
155
getVersionString() const156 const std::string& LimitedSupportDevice::getVersionString() const {
157 return kDevice->getVersionString();
158 }
159
getFeatureLevel() const160 Version LimitedSupportDevice::getFeatureLevel() const {
161 return kDevice->getFeatureLevel();
162 }
163
getType() const164 DeviceType LimitedSupportDevice::getType() const {
165 return kDevice->getType();
166 }
167
getSupportedExtensions() const168 const std::vector<Extension>& LimitedSupportDevice::getSupportedExtensions() const {
169 return kDevice->getSupportedExtensions();
170 }
171
getCapabilities() const172 const Capabilities& LimitedSupportDevice::getCapabilities() const {
173 return kCapabilities;
174 }
175
getNumberOfCacheFilesNeeded() const176 std::pair<uint32_t, uint32_t> LimitedSupportDevice::getNumberOfCacheFilesNeeded() const {
177 return kDevice->getNumberOfCacheFilesNeeded();
178 }
179
wait() const180 GeneralResult<void> LimitedSupportDevice::wait() const {
181 return kDevice->wait();
182 }
183
getSupportedOperations(const Model & model) const184 GeneralResult<std::vector<bool>> LimitedSupportDevice::getSupportedOperations(
185 const Model& model) const {
186 return kSupportedOperationsFunction(model);
187 }
188
prepareModel(const Model & model,ExecutionPreference preference,Priority priority,OptionalTimePoint deadline,const std::vector<SharedHandle> & modelCache,const std::vector<SharedHandle> & dataCache,const CacheToken & token,const std::vector<TokenValuePair> &,const std::vector<ExtensionNameAndPrefix> &) const189 GeneralResult<SharedPreparedModel> LimitedSupportDevice::prepareModel(
190 const Model& model, ExecutionPreference preference, Priority priority,
191 OptionalTimePoint deadline, const std::vector<SharedHandle>& modelCache,
192 const std::vector<SharedHandle>& dataCache, const CacheToken& token,
193 const std::vector<TokenValuePair>& /*hints*/,
194 const std::vector<ExtensionNameAndPrefix>& /*extensionNameToPrefix*/) const {
195 const auto supportedOperations = NN_TRY(kSupportedOperationsFunction(model));
196 constexpr auto id = [](auto v) { return v; };
197 if (!std::all_of(supportedOperations.begin(), supportedOperations.end(), id)) {
198 return NN_ERROR(nn::ErrorStatus::INVALID_ARGUMENT) << "Not all operations are supported";
199 }
200 return kDevice->prepareModel(model, preference, priority, deadline, modelCache, dataCache,
201 token, {}, {});
202 }
203
prepareModelFromCache(OptionalTimePoint deadline,const std::vector<SharedHandle> & modelCache,const std::vector<SharedHandle> & dataCache,const CacheToken & token) const204 GeneralResult<SharedPreparedModel> LimitedSupportDevice::prepareModelFromCache(
205 OptionalTimePoint deadline, const std::vector<SharedHandle>& modelCache,
206 const std::vector<SharedHandle>& dataCache, const CacheToken& token) const {
207 return kDevice->prepareModelFromCache(deadline, modelCache, dataCache, token);
208 }
209
allocate(const BufferDesc & desc,const std::vector<SharedPreparedModel> & preparedModels,const std::vector<BufferRole> & inputRoles,const std::vector<BufferRole> & outputRoles) const210 GeneralResult<SharedBuffer> LimitedSupportDevice::allocate(
211 const BufferDesc& desc, const std::vector<SharedPreparedModel>& preparedModels,
212 const std::vector<BufferRole>& inputRoles,
213 const std::vector<BufferRole>& outputRoles) const {
214 return kDevice->allocate(desc, preparedModels, inputRoles, outputRoles);
215 }
216
getExampleLimitedDevices()217 std::vector<SharedDevice> getExampleLimitedDevices() {
218 SharedDevice device;
219 std::vector<SharedDevice> devices;
220 devices.reserve(4);
221
222 device = makeDevice("nnapi-sample_float_fast", makeCapabilitiesFloatFast(),
223 getSupportedOperationsFloat);
224 devices.push_back(std::move(device));
225
226 device = makeDevice("nnapi-sample_float_slow", makeCapabilitiesFloatSlow(),
227 getSupportedOperationsFloat);
228 devices.push_back(std::move(device));
229
230 device = makeDevice("nnapi-sample_minimal", makeCapabilitiesMinimal(),
231 getSupportedOperationsMinimal);
232 devices.push_back(std::move(device));
233
234 device = makeDevice("nnapi-sample_quant", makeCapabilitiesQuant(), getSupportedOperationsQuant);
235 devices.push_back(std::move(device));
236
237 return devices;
238 }
239
240 } // namespace android::nn::sample
241