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