1 /*
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 <MemoryUtils.h>
18 #include <SampleDriverFull.h>
19 #include <Utils.h>
20 #include <android-base/logging.h>
21 #include <android/hardware/neuralnetworks/1.3/IDevice.h>
22 #include <android/hardware/neuralnetworks/1.3/IPreparedModel.h>
23 #include <android/hardware/neuralnetworks/1.3/types.h>
24
25 #include <algorithm>
26 #include <cstdlib>
27 #include <limits>
28 #include <optional>
29 #include <utility>
30 #include <vector>
31
32 #include "TestHarness.h"
33
34 namespace {
35
36 using ::android::hidl::memory::V1_0::IMemory;
37 using ::test_helper::TestModel;
38 using namespace test_helper;
39 using namespace android;
40 using namespace android::hardware;
41 namespace V1_0 = neuralnetworks::V1_0;
42 namespace V1_1 = neuralnetworks::V1_1;
43 namespace V1_2 = neuralnetworks::V1_2;
44 namespace V1_3 = neuralnetworks::V1_3;
45 using V1_0::DataLocation;
46
getDevice()47 sp<V1_3::IDevice> getDevice() {
48 /**
49 * TODO: INSERT CUSTOM DEVICE HERE
50 */
51 static const sp<V1_3::IDevice> device = new nn::sample_driver::SampleDriverFull(
52 "example-driver", V1_0::PerformanceInfo{.execTime = 1.0f, .powerUsage = 1.0f});
53 return device;
54 }
55
createSubgraph(const TestSubgraph & testSubgraph,uint32_t * constCopySize,std::vector<const TestBuffer * > * constCopies,uint32_t * constRefSize,std::vector<const TestBuffer * > * constReferences)56 V1_3::Subgraph createSubgraph(const TestSubgraph& testSubgraph, uint32_t* constCopySize,
57 std::vector<const TestBuffer*>* constCopies, uint32_t* constRefSize,
58 std::vector<const TestBuffer*>* constReferences) {
59 CHECK(constCopySize != nullptr);
60 CHECK(constCopies != nullptr);
61 CHECK(constRefSize != nullptr);
62 CHECK(constReferences != nullptr);
63
64 // Operands.
65 hidl_vec<V1_3::Operand> operands(testSubgraph.operands.size());
66 for (uint32_t i = 0; i < testSubgraph.operands.size(); i++) {
67 const auto& op = testSubgraph.operands[i];
68
69 DataLocation loc = {};
70 if (op.lifetime == TestOperandLifeTime::CONSTANT_COPY) {
71 loc = {
72 .poolIndex = 0,
73 .offset = *constCopySize,
74 .length = static_cast<uint32_t>(op.data.size()),
75 };
76 constCopies->push_back(&op.data);
77 *constCopySize += op.data.alignedSize();
78 } else if (op.lifetime == TestOperandLifeTime::CONSTANT_REFERENCE) {
79 loc = {
80 .poolIndex = 0,
81 .offset = *constRefSize,
82 .length = static_cast<uint32_t>(op.data.size()),
83 };
84 constReferences->push_back(&op.data);
85 *constRefSize += op.data.alignedSize();
86 } else if (op.lifetime == TestOperandLifeTime::SUBGRAPH) {
87 loc = {
88 .poolIndex = 0,
89 .offset = *op.data.get<uint32_t>(),
90 .length = 0,
91 };
92 }
93
94 V1_2::Operand::ExtraParams extraParams;
95 if (op.type == TestOperandType::TENSOR_QUANT8_SYMM_PER_CHANNEL) {
96 extraParams.channelQuant(V1_2::SymmPerChannelQuantParams{
97 .scales = op.channelQuant.scales, .channelDim = op.channelQuant.channelDim});
98 }
99
100 operands[i] = {.type = static_cast<V1_3::OperandType>(op.type),
101 .dimensions = op.dimensions,
102 .numberOfConsumers = op.numberOfConsumers,
103 .scale = op.scale,
104 .zeroPoint = op.zeroPoint,
105 .lifetime = static_cast<V1_3::OperandLifeTime>(op.lifetime),
106 .location = loc,
107 .extraParams = std::move(extraParams)};
108 }
109
110 // Operations.
111 hidl_vec<V1_3::Operation> operations(testSubgraph.operations.size());
112 std::transform(testSubgraph.operations.begin(), testSubgraph.operations.end(),
113 operations.begin(), [](const TestOperation& op) -> V1_3::Operation {
114 return {.type = static_cast<V1_3::OperationType>(op.type),
115 .inputs = op.inputs,
116 .outputs = op.outputs};
117 });
118
119 return {.operands = std::move(operands),
120 .operations = std::move(operations),
121 .inputIndexes = testSubgraph.inputIndexes,
122 .outputIndexes = testSubgraph.outputIndexes};
123 }
124
copyTestBuffers(const std::vector<const TestBuffer * > & buffers,uint8_t * output)125 void copyTestBuffers(const std::vector<const TestBuffer*>& buffers, uint8_t* output) {
126 uint32_t offset = 0;
127 for (const TestBuffer* buffer : buffers) {
128 const uint8_t* begin = buffer->get<uint8_t>();
129 const uint8_t* end = begin + buffer->size();
130 std::copy(begin, end, output + offset);
131 offset += buffer->alignedSize();
132 }
133 }
134
createModel(const TestModel & testModel)135 V1_3::Model createModel(const TestModel& testModel) {
136 uint32_t constCopySize = 0;
137 uint32_t constRefSize = 0;
138 std::vector<const TestBuffer*> constCopies;
139 std::vector<const TestBuffer*> constReferences;
140
141 V1_3::Subgraph mainSubgraph = createSubgraph(testModel.main, &constCopySize, &constCopies,
142 &constRefSize, &constReferences);
143 hidl_vec<V1_3::Subgraph> refSubgraphs(testModel.referenced.size());
144 std::transform(testModel.referenced.begin(), testModel.referenced.end(), refSubgraphs.begin(),
145 [&constCopySize, &constCopies, &constRefSize,
146 &constReferences](const TestSubgraph& testSubgraph) {
147 return createSubgraph(testSubgraph, &constCopySize, &constCopies,
148 &constRefSize, &constReferences);
149 });
150
151 // Constant copies.
152 hidl_vec<uint8_t> operandValues(constCopySize);
153 copyTestBuffers(constCopies, operandValues.data());
154
155 // Shared memory.
156 std::vector<hidl_memory> pools = {};
157 if (constRefSize > 0) {
158 pools.push_back(nn::allocateSharedMemory(constRefSize));
159 CHECK_NE(pools.back().size(), 0u);
160
161 // load data
162 sp<IMemory> mappedMemory = mapMemory(pools[0]);
163 CHECK(mappedMemory.get() != nullptr);
164 uint8_t* mappedPtr =
165 reinterpret_cast<uint8_t*>(static_cast<void*>(mappedMemory->getPointer()));
166 CHECK(mappedPtr != nullptr);
167
168 copyTestBuffers(constReferences, mappedPtr);
169 }
170
171 return {.main = std::move(mainSubgraph),
172 .referenced = std::move(refSubgraphs),
173 .operandValues = std::move(operandValues),
174 .pools = pools,
175 .relaxComputationFloat32toFloat16 = testModel.isRelaxed};
176 }
177
createRequest(const TestModel & testModel)178 V1_3::Request createRequest(const TestModel& testModel) {
179 static constexpr uint32_t kInputPoolIndex = 0;
180 static constexpr uint32_t kOutputPoolIndex = 1;
181
182 // Model inputs.
183 hidl_vec<V1_0::RequestArgument> inputs(testModel.main.inputIndexes.size());
184 size_t inputSize = 0;
185 for (uint32_t i = 0; i < testModel.main.inputIndexes.size(); i++) {
186 const auto& op = testModel.main.operands[testModel.main.inputIndexes[i]];
187 if (op.data.size() == 0) {
188 // Omitted input.
189 inputs[i] = {.hasNoValue = true};
190 } else {
191 DataLocation loc = {.poolIndex = kInputPoolIndex,
192 .offset = static_cast<uint32_t>(inputSize),
193 .length = static_cast<uint32_t>(op.data.size())};
194 inputSize += op.data.alignedSize();
195 inputs[i] = {.hasNoValue = false, .location = loc, .dimensions = {}};
196 }
197 }
198
199 // Model outputs.
200 hidl_vec<V1_0::RequestArgument> outputs(testModel.main.outputIndexes.size());
201 size_t outputSize = 0;
202 for (uint32_t i = 0; i < testModel.main.outputIndexes.size(); i++) {
203 const auto& op = testModel.main.operands[testModel.main.outputIndexes[i]];
204
205 // In the case of zero-sized output, we should at least provide a one-byte buffer.
206 // This is because zero-sized tensors are only supported internally to the driver, or
207 // reported in output shapes. It is illegal for the client to pre-specify a zero-sized
208 // tensor as model output. Otherwise, we will have two semantic conflicts:
209 // - "Zero dimension" conflicts with "unspecified dimension".
210 // - "Omitted operand buffer" conflicts with "zero-sized operand buffer".
211 size_t bufferSize = std::max<size_t>(op.data.size(), 1);
212
213 DataLocation loc = {.poolIndex = kOutputPoolIndex,
214 .offset = static_cast<uint32_t>(outputSize),
215 .length = static_cast<uint32_t>(bufferSize)};
216 outputSize += op.data.size() == 0 ? TestBuffer::kAlignment : op.data.alignedSize();
217 outputs[i] = {.hasNoValue = false, .location = loc, .dimensions = {}};
218 }
219
220 // Allocate memory pools.
221 inputSize = std::max<size_t>(inputSize, 1);
222 auto inputMemory = nn::allocateSharedMemory(inputSize);
223 CHECK(inputMemory.valid());
224 outputSize = std::max<size_t>(outputSize, 1);
225 auto outputMemory = nn::allocateSharedMemory(outputSize);
226 CHECK(outputMemory.valid());
227 hidl_vec<V1_3::Request::MemoryPool> pools(2);
228 pools[kInputPoolIndex].hidlMemory(inputMemory);
229 pools[kOutputPoolIndex].hidlMemory(outputMemory);
230
231 // Map input memory pool.
232 const auto mappedInput = mapMemory(inputMemory);
233 CHECK(mappedInput.get() != nullptr);
234 uint8_t* const inputPtr = static_cast<uint8_t*>(static_cast<void*>(mappedInput->getPointer()));
235 CHECK(inputPtr != nullptr);
236
237 // Copy input data to the memory pool.
238 for (uint32_t i = 0; i < testModel.main.inputIndexes.size(); i++) {
239 const auto& op = testModel.main.operands[testModel.main.inputIndexes[i]];
240 if (op.data.size() > 0) {
241 const uint8_t* begin = op.data.get<uint8_t>();
242 const uint8_t* end = begin + op.data.size();
243 std::copy(begin, end, inputPtr + inputs[i].location.offset);
244 }
245 }
246
247 return {.inputs = std::move(inputs), .outputs = std::move(outputs), .pools = std::move(pools)};
248 }
249
250 class PreparedModelCallback : public V1_3::IPreparedModelCallback {
251 public:
notify(V1_0::ErrorStatus,const sp<V1_0::IPreparedModel> &)252 Return<void> notify(V1_0::ErrorStatus /*status*/,
253 const sp<V1_0::IPreparedModel>& /*preparedModel*/) override {
254 LOG(FATAL) << "not implemented";
255 return Void();
256 }
notify_1_2(V1_0::ErrorStatus,const sp<V1_2::IPreparedModel> &)257 Return<void> notify_1_2(V1_0::ErrorStatus /*status*/,
258 const sp<V1_2::IPreparedModel>& /*preparedModel*/) override {
259 LOG(FATAL) << "not implemented";
260 return Void();
261 }
notify_1_3(V1_3::ErrorStatus status,const sp<V1_3::IPreparedModel> & preparedModel)262 Return<void> notify_1_3(V1_3::ErrorStatus status,
263 const sp<V1_3::IPreparedModel>& preparedModel) override {
264 const sp<V1_3::IPreparedModel> result =
265 (status == V1_3::ErrorStatus::NONE ? preparedModel : nullptr);
266 {
267 std::lock_guard guard(mMutex);
268 if (mResults.has_value()) return Void();
269 mResults.emplace(result);
270 }
271 mCondition.notify_all();
272 return Void();
273 }
274
getResults() const275 const sp<V1_3::IPreparedModel>& getResults() const {
276 {
277 std::unique_lock lock(mMutex);
278 mCondition.wait(lock, [this] { return mResults.has_value(); });
279 }
280 return mResults.value();
281 }
282
283 private:
284 mutable std::mutex mMutex;
285 mutable std::condition_variable mCondition;
286 std::optional<const sp<V1_3::IPreparedModel>> mResults;
287 };
288
prepareModel(const sp<V1_3::IDevice> & device,const V1_3::Model & model)289 sp<V1_3::IPreparedModel> prepareModel(const sp<V1_3::IDevice>& device, const V1_3::Model& model) {
290 const sp<PreparedModelCallback> callback = new PreparedModelCallback();
291 device->prepareModel_1_3(model, V1_1::ExecutionPreference::FAST_SINGLE_ANSWER,
292 V1_3::Priority::MEDIUM, {}, {}, {}, {}, callback);
293 return callback->getResults();
294 }
295
execute(const sp<V1_3::IPreparedModel> & preparedModel,const V1_3::Request & request)296 void execute(const sp<V1_3::IPreparedModel>& preparedModel, const V1_3::Request& request) {
297 const auto cb = [](V1_3::ErrorStatus /*status*/,
298 const hidl_vec<V1_2::OutputShape>& /*outputShapes*/,
299 V1_2::Timing /*timing*/) {
300 // TODO: CHECK VTS requirements?
301 };
302 preparedModel->executeSynchronously_1_3(request, V1_2::MeasureTiming::YES, {}, {}, cb);
303 }
304
305 } // anonymous namespace
306
nnapiFuzzTest(const TestModel & testModel)307 void nnapiFuzzTest(const TestModel& testModel) {
308 // Set up device.
309 const auto device = getDevice();
310 CHECK(device != nullptr);
311
312 // Set up model.
313 const auto model = createModel(testModel);
314
315 // Attempt to prepare the model.
316 const auto preparedModel = prepareModel(device, model);
317 if (preparedModel == nullptr) return;
318
319 // Set up request.
320 const auto request = createRequest(testModel);
321
322 // Perform execution.
323 execute(preparedModel, request);
324 }
325