• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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