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 "CanonicalDevice.h"
18 
19 #include <Tracing.h>
20 #include <android-base/logging.h>
21 #include <nnapi/IBuffer.h>
22 #include <nnapi/IDevice.h>
23 #include <nnapi/IPreparedModel.h>
24 #include <nnapi/OperandTypes.h>
25 #include <nnapi/Result.h>
26 #include <nnapi/Types.h>
27 #include <nnapi/Validation.h>
28 
29 #include <algorithm>
30 #include <any>
31 #include <functional>
32 #include <iterator>
33 #include <memory>
34 #include <optional>
35 #include <set>
36 #include <string>
37 #include <utility>
38 #include <vector>
39 
40 #include "CanonicalBuffer.h"
41 #include "CanonicalPreparedModel.h"
42 
43 namespace android::nn::sample {
44 namespace {
45 
makeCapabilities()46 Capabilities makeCapabilities() {
47     constexpr float kPerf = 1.0f;
48     const Capabilities::PerformanceInfo kPerfInfo = {.execTime = kPerf, .powerUsage = kPerf};
49 
50     constexpr OperandType kOperandsTypes[] = {
51             OperandType::FLOAT32,
52             OperandType::INT32,
53             OperandType::UINT32,
54             OperandType::TENSOR_FLOAT32,
55             OperandType::TENSOR_INT32,
56             OperandType::TENSOR_QUANT8_ASYMM,
57             OperandType::BOOL,
58             OperandType::TENSOR_QUANT16_SYMM,
59             OperandType::TENSOR_FLOAT16,
60             OperandType::TENSOR_BOOL8,
61             OperandType::FLOAT16,
62             OperandType::TENSOR_QUANT8_SYMM_PER_CHANNEL,
63             OperandType::TENSOR_QUANT16_ASYMM,
64             OperandType::TENSOR_QUANT8_SYMM,
65             OperandType::TENSOR_QUANT8_ASYMM_SIGNED,
66     };
67 
68     std::vector<Capabilities::OperandPerformance> operandPerformance;
69     operandPerformance.reserve(std::size(kOperandsTypes));
70     std::transform(std::begin(kOperandsTypes), std::end(kOperandsTypes),
71                    std::back_inserter(operandPerformance), [kPerfInfo](OperandType op) {
72                        return Capabilities::OperandPerformance{.type = op, .info = kPerfInfo};
73                    });
74     auto table =
75             Capabilities::OperandPerformanceTable::create(std::move(operandPerformance)).value();
76 
77     return {.relaxedFloat32toFloat16PerformanceScalar = kPerfInfo,
78             .relaxedFloat32toFloat16PerformanceTensor = kPerfInfo,
79             .operandPerformance = std::move(table),
80             .ifPerformance = kPerfInfo,
81             .whilePerformance = kPerfInfo};
82 }
83 
toString(const Dimensions & dimensions)84 std::string toString(const Dimensions& dimensions) {
85     std::ostringstream oss;
86     oss << "[";
87     for (size_t i = 0; i < dimensions.size(); ++i) {
88         if (i != 0) oss << ", ";
89         oss << dimensions[i];
90     }
91     oss << "]";
92     return oss.str();
93 }
94 
95 }  // namespace
96 
Device(std::string name,const IOperationResolver * operationResolver)97 Device::Device(std::string name, const IOperationResolver* operationResolver)
98     : kName(std::move(name)), kOperationResolver(*operationResolver) {
99     CHECK(operationResolver != nullptr);
100     initVLogMask();
101 }
102 
getName() const103 const std::string& Device::getName() const {
104     return kName;
105 }
106 
getVersionString() const107 const std::string& Device::getVersionString() const {
108     static const std::string kVersionString = "JUST_AN_EXAMPLE";
109     return kVersionString;
110 }
111 
getFeatureLevel() const112 Version Device::getFeatureLevel() const {
113     return kVersionFeatureLevel8;
114 }
115 
getType() const116 DeviceType Device::getType() const {
117     return DeviceType::CPU;
118 }
119 
getSupportedExtensions() const120 const std::vector<Extension>& Device::getSupportedExtensions() const {
121     static const std::vector<Extension> kExtensions = {/* No extensions. */};
122     return kExtensions;
123 }
124 
getCapabilities() const125 const Capabilities& Device::getCapabilities() const {
126     static const Capabilities kCapabilities = makeCapabilities();
127     return kCapabilities;
128 }
129 
getNumberOfCacheFilesNeeded() const130 std::pair<uint32_t, uint32_t> Device::getNumberOfCacheFilesNeeded() const {
131     return std::make_pair(/*numModelCache=*/0, /*numDataCache=*/0);
132 }
133 
wait() const134 GeneralResult<void> Device::wait() const {
135     return {};
136 }
137 
getSupportedOperations(const Model & model) const138 GeneralResult<std::vector<bool>> Device::getSupportedOperations(const Model& model) const {
139     VLOG(DRIVER) << "sample::Device::getSupportedOperations";
140 
141     // Validate arguments.
142     if (const auto result = validate(model); !result.ok()) {
143         return NN_ERROR(ErrorStatus::INVALID_ARGUMENT) << result.error();
144     }
145 
146     // Mark all operations except extension operations as supported.
147     std::vector<bool> supported;
148     supported.reserve(model.main.operations.size());
149     std::transform(model.main.operations.begin(), model.main.operations.end(),
150                    std::back_inserter(supported), [](const Operation& operation) {
151                        return !isExtensionOperationType(operation.type) &&
152                               operation.type != OperationType::OEM_OPERATION;
153                    });
154 
155     return supported;
156 }
157 
prepareModel(const Model & model,ExecutionPreference preference,Priority priority,OptionalTimePoint deadline,const std::vector<SharedHandle> &,const std::vector<SharedHandle> &,const CacheToken &,const std::vector<TokenValuePair> &,const std::vector<ExtensionNameAndPrefix> &) const158 GeneralResult<SharedPreparedModel> Device::prepareModel(
159         const Model& model, ExecutionPreference preference, Priority priority,
160         OptionalTimePoint deadline, const std::vector<SharedHandle>& /*modelCache*/,
161         const std::vector<SharedHandle>& /*dataCache*/, const CacheToken& /*token*/,
162         const std::vector<TokenValuePair>& /*hints*/,
163         const std::vector<ExtensionNameAndPrefix>& /*extensionNameToPrefix*/) const {
164     if (VLOG_IS_ON(DRIVER)) {
165         VLOG(DRIVER) << "sample::Device::prepareModel";
166         logModelToInfo(model);
167     }
168 
169     // Validate arguments.
170     if (const auto result = validate(model); !result.ok()) {
171         return NN_ERROR(ErrorStatus::INVALID_ARGUMENT) << "Invalid Model: " << result.error();
172     }
173     if (const auto result = validate(preference); !result.ok()) {
174         return NN_ERROR(ErrorStatus::INVALID_ARGUMENT)
175                << "Invalid ExecutionPreference: " << result.error();
176     }
177     if (const auto result = validate(priority); !result.ok()) {
178         return NN_ERROR(ErrorStatus::INVALID_ARGUMENT) << "Invalid Priority: " << result.error();
179     }
180 
181     // Check if deadline has passed.
182     if (hasDeadlinePassed(deadline)) {
183         return NN_ERROR(ErrorStatus::MISSED_DEADLINE_PERSISTENT);
184     }
185 
186     std::vector<RunTimePoolInfo> poolInfos;
187     if (!setRunTimePoolInfosFromCanonicalMemories(&poolInfos, model.pools)) {
188         return NN_ERROR() << "setRunTimePoolInfosFromCanonicalMemories failed";
189     }
190 
191     // Create the prepared model.
192     return std::make_shared<const PreparedModel>(model, preference, priority, &kOperationResolver,
193                                                  kBufferTracker, std::move(poolInfos));
194 }
195 
prepareModelFromCache(OptionalTimePoint,const std::vector<SharedHandle> &,const std::vector<SharedHandle> &,const CacheToken &) const196 GeneralResult<SharedPreparedModel> Device::prepareModelFromCache(
197         OptionalTimePoint /*deadline*/, const std::vector<SharedHandle>& /*modelCache*/,
198         const std::vector<SharedHandle>& /*dataCache*/, const CacheToken& /*token*/) const {
199     NNTRACE_FULL(NNTRACE_LAYER_DRIVER, NNTRACE_PHASE_COMPILATION,
200                  "sample::Device::prepareModelFromCache");
201     return NN_ERROR(ErrorStatus::GENERAL_FAILURE)
202            << "prepareModelFromCache not supported on sample::Device::prepareModelFromCache("
203            << kName << ")";
204 }
205 
allocate(const BufferDesc & desc,const std::vector<SharedPreparedModel> & preparedModels,const std::vector<BufferRole> & inputRoles,const std::vector<BufferRole> & outputRoles) const206 GeneralResult<SharedBuffer> Device::allocate(const BufferDesc& desc,
207                                              const std::vector<SharedPreparedModel>& preparedModels,
208                                              const std::vector<BufferRole>& inputRoles,
209                                              const std::vector<BufferRole>& outputRoles) const {
210     VLOG(DRIVER) << "sample::Device::allocate";
211     std::set<PreparedModelRole> roles;
212     Operand operand;
213     auto getModel = [](const SharedPreparedModel& preparedModel) -> const Model* {
214         std::any resource = preparedModel->getUnderlyingResource();
215         const Model** maybeModel = std::any_cast<const Model*>(&resource);
216         if (maybeModel == nullptr) {
217             LOG(ERROR) << "sample::Device::allocate -- unknown remote IPreparedModel.";
218             return nullptr;
219         }
220         return *maybeModel;
221     };
222     if (const auto result = validateMemoryDesc(desc, preparedModels, inputRoles, outputRoles,
223                                                getModel, &roles, &operand);
224         !result.ok()) {
225         return NN_ERROR(ErrorStatus::INVALID_ARGUMENT)
226                << "sample::Device::allocate -- validation failed: " << result.error();
227     }
228 
229     if (isExtensionOperandType(operand.type)) {
230         return NN_ERROR(ErrorStatus::GENERAL_FAILURE)
231                << "sample::Device::allocate -- does not support extension type.";
232     }
233 
234     // TODO(xusongw): Support allocating buffers with unknown dimensions or rank.
235     uint32_t size = nonExtensionOperandSizeOfData(operand.type, operand.dimensions);
236     VLOG(DRIVER) << "sample::Device::allocate -- type = " << operand.type
237                  << ", dimensions = " << toString(operand.dimensions) << ", size = " << size;
238     if (size == 0) {
239         return NN_ERROR(ErrorStatus::GENERAL_FAILURE)
240                << "sample::Device::allocate -- does not support dynamic output shape.";
241     }
242 
243     auto bufferWrapper = ManagedBuffer::create(size, std::move(roles), operand);
244     if (bufferWrapper == nullptr) {
245         return NN_ERROR(ErrorStatus::GENERAL_FAILURE)
246                << "sample::Device::allocate -- not enough memory.";
247     }
248 
249     auto token = kBufferTracker->add(bufferWrapper);
250     if (token == nullptr) {
251         return NN_ERROR(ErrorStatus::GENERAL_FAILURE)
252                << "sample::Device::allocate -- BufferTracker returned invalid token.";
253     }
254 
255     auto sampleBuffer = std::make_shared<const Buffer>(std::move(bufferWrapper), std::move(token));
256     VLOG(DRIVER) << "sample::Device::allocate -- successfully allocates the requested memory";
257     return sampleBuffer;
258 }
259 
260 }  // namespace android::nn::sample
261