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 "PreparedModel.h"
18
19 #include "Callbacks.h"
20 #include "Conversions.h"
21 #include "Execution.h"
22 #include "Utils.h"
23
24 #include <android/hardware/neuralnetworks/1.0/types.h>
25 #include <android/hardware/neuralnetworks/1.1/types.h>
26 #include <android/hardware/neuralnetworks/1.2/types.h>
27 #include <android/hardware/neuralnetworks/1.3/IPreparedModel.h>
28 #include <android/hardware/neuralnetworks/1.3/types.h>
29 #include <nnapi/IPreparedModel.h>
30 #include <nnapi/Result.h>
31 #include <nnapi/TypeUtils.h>
32 #include <nnapi/Types.h>
33 #include <nnapi/hal/1.0/HandleError.h>
34 #include <nnapi/hal/1.0/ProtectCallback.h>
35 #include <nnapi/hal/1.2/Burst.h>
36 #include <nnapi/hal/1.2/BurstUtils.h>
37 #include <nnapi/hal/1.2/Conversions.h>
38 #include <nnapi/hal/CommonUtils.h>
39
40 #include <memory>
41 #include <tuple>
42 #include <utility>
43 #include <vector>
44
45 // See hardware/interfaces/neuralnetworks/utils/README.md for more information on HIDL interface
46 // lifetimes across processes and for protecting asynchronous calls across HIDL.
47
48 namespace android::hardware::neuralnetworks::V1_3::utils {
49 namespace {
50
convertFencedExecutionCallbackResults(ErrorStatus status,const V1_2::Timing & timingLaunched,const V1_2::Timing & timingFenced)51 nn::GeneralResult<std::pair<nn::Timing, nn::Timing>> convertFencedExecutionCallbackResults(
52 ErrorStatus status, const V1_2::Timing& timingLaunched, const V1_2::Timing& timingFenced) {
53 HANDLE_STATUS_HIDL(status) << "fenced execution callback info failed with " << toString(status);
54 return std::make_pair(NN_TRY(nn::convert(timingLaunched)), NN_TRY(nn::convert(timingFenced)));
55 }
56
fencedExecutionCallback(ErrorStatus status,const hidl_handle & syncFence,const sp<IFencedExecutionCallback> & callback)57 nn::GeneralResult<std::pair<nn::SyncFence, nn::ExecuteFencedInfoCallback>> fencedExecutionCallback(
58 ErrorStatus status, const hidl_handle& syncFence,
59 const sp<IFencedExecutionCallback>& callback) {
60 HANDLE_STATUS_HIDL(status) << "fenced execution failed with " << toString(status);
61
62 auto resultSyncFence = nn::SyncFence::createAsSignaled();
63 if (syncFence.getNativeHandle() != nullptr) {
64 auto sharedHandle = NN_TRY(nn::convert(syncFence));
65 resultSyncFence = NN_TRY(nn::SyncFence::create(std::move(sharedHandle)));
66 }
67
68 if (callback == nullptr) {
69 return NN_ERROR(nn::ErrorStatus::GENERAL_FAILURE) << "callback is null";
70 }
71
72 // Create callback which can be used to retrieve the execution error status and timings.
73 nn::ExecuteFencedInfoCallback resultCallback =
74 [callback]() -> nn::GeneralResult<std::pair<nn::Timing, nn::Timing>> {
75 auto cb = hal::utils::CallbackValue(convertFencedExecutionCallbackResults);
76
77 const auto ret = callback->getExecutionInfo(cb);
78 HANDLE_TRANSPORT_FAILURE(ret);
79
80 return cb.take();
81 };
82
83 return std::make_pair(std::move(resultSyncFence), std::move(resultCallback));
84 }
85
86 } // namespace
87
create(sp<V1_3::IPreparedModel> preparedModel,bool executeSynchronously)88 nn::GeneralResult<std::shared_ptr<const PreparedModel>> PreparedModel::create(
89 sp<V1_3::IPreparedModel> preparedModel, bool executeSynchronously) {
90 if (preparedModel == nullptr) {
91 return NN_ERROR() << "V1_3::utils::PreparedModel::create must have non-null preparedModel";
92 }
93
94 auto deathHandler = NN_TRY(hal::utils::DeathHandler::create(preparedModel));
95 return std::make_shared<const PreparedModel>(PrivateConstructorTag{}, executeSynchronously,
96 std::move(preparedModel), std::move(deathHandler));
97 }
98
PreparedModel(PrivateConstructorTag,bool executeSynchronously,sp<V1_3::IPreparedModel> preparedModel,hal::utils::DeathHandler deathHandler)99 PreparedModel::PreparedModel(PrivateConstructorTag /*tag*/, bool executeSynchronously,
100 sp<V1_3::IPreparedModel> preparedModel,
101 hal::utils::DeathHandler deathHandler)
102 : kExecuteSynchronously(executeSynchronously),
103 kPreparedModel(std::move(preparedModel)),
104 kDeathHandler(std::move(deathHandler)) {}
105
106 nn::ExecutionResult<std::pair<std::vector<nn::OutputShape>, nn::Timing>>
executeSynchronously(const Request & request,V1_2::MeasureTiming measure,const OptionalTimePoint & deadline,const OptionalTimeoutDuration & loopTimeoutDuration) const107 PreparedModel::executeSynchronously(const Request& request, V1_2::MeasureTiming measure,
108 const OptionalTimePoint& deadline,
109 const OptionalTimeoutDuration& loopTimeoutDuration) const {
110 auto cb = hal::utils::CallbackValue(executionCallback);
111
112 const auto ret = kPreparedModel->executeSynchronously_1_3(request, measure, deadline,
113 loopTimeoutDuration, cb);
114 HANDLE_TRANSPORT_FAILURE(ret);
115
116 return cb.take();
117 }
118
119 nn::ExecutionResult<std::pair<std::vector<nn::OutputShape>, nn::Timing>>
executeAsynchronously(const Request & request,V1_2::MeasureTiming measure,const OptionalTimePoint & deadline,const OptionalTimeoutDuration & loopTimeoutDuration) const120 PreparedModel::executeAsynchronously(const Request& request, V1_2::MeasureTiming measure,
121 const OptionalTimePoint& deadline,
122 const OptionalTimeoutDuration& loopTimeoutDuration) const {
123 const auto cb = sp<ExecutionCallback>::make();
124 const auto scoped = kDeathHandler.protectCallback(cb.get());
125
126 const auto ret =
127 kPreparedModel->execute_1_3(request, measure, deadline, loopTimeoutDuration, cb);
128 const auto status = HANDLE_TRANSPORT_FAILURE(ret);
129 if (status != ErrorStatus::OUTPUT_INSUFFICIENT_SIZE) {
130 HANDLE_STATUS_HIDL(status) << "execution failed with " << toString(status);
131 }
132
133 return cb->get();
134 }
135
execute(const nn::Request & request,nn::MeasureTiming measure,const nn::OptionalTimePoint & deadline,const nn::OptionalDuration & loopTimeoutDuration,const std::vector<nn::TokenValuePair> &,const std::vector<nn::ExtensionNameAndPrefix> &) const136 nn::ExecutionResult<std::pair<std::vector<nn::OutputShape>, nn::Timing>> PreparedModel::execute(
137 const nn::Request& request, nn::MeasureTiming measure,
138 const nn::OptionalTimePoint& deadline, const nn::OptionalDuration& loopTimeoutDuration,
139 const std::vector<nn::TokenValuePair>& /*hints*/,
140 const std::vector<nn::ExtensionNameAndPrefix>& /*extensionNameToPrefix*/) const {
141 // Ensure that request is ready for IPC.
142 std::optional<nn::Request> maybeRequestInShared;
143 hal::utils::RequestRelocation relocation;
144 const nn::Request& requestInShared = NN_TRY(hal::utils::convertRequestFromPointerToShared(
145 &request, nn::kDefaultRequestMemoryAlignment, nn::kMinMemoryPadding,
146 &maybeRequestInShared, &relocation));
147
148 const auto hidlRequest = NN_TRY(convert(requestInShared));
149 const auto hidlMeasure = NN_TRY(convert(measure));
150 const auto hidlDeadline = NN_TRY(convert(deadline));
151 const auto hidlLoopTimeoutDuration = NN_TRY(convert(loopTimeoutDuration));
152
153 return executeInternal(hidlRequest, hidlMeasure, hidlDeadline, hidlLoopTimeoutDuration,
154 relocation);
155 }
156
157 nn::ExecutionResult<std::pair<std::vector<nn::OutputShape>, nn::Timing>>
executeInternal(const Request & request,V1_2::MeasureTiming measure,const OptionalTimePoint & deadline,const OptionalTimeoutDuration & loopTimeoutDuration,const hal::utils::RequestRelocation & relocation) const158 PreparedModel::executeInternal(const Request& request, V1_2::MeasureTiming measure,
159 const OptionalTimePoint& deadline,
160 const OptionalTimeoutDuration& loopTimeoutDuration,
161 const hal::utils::RequestRelocation& relocation) const {
162 if (relocation.input) {
163 relocation.input->flush();
164 }
165
166 auto result = kExecuteSynchronously
167 ? executeSynchronously(request, measure, deadline, loopTimeoutDuration)
168 : executeAsynchronously(request, measure, deadline, loopTimeoutDuration);
169 auto [outputShapes, timing] = NN_TRY(std::move(result));
170
171 if (relocation.output) {
172 relocation.output->flush();
173 }
174 return std::make_pair(std::move(outputShapes), timing);
175 }
176
177 nn::GeneralResult<std::pair<nn::SyncFence, nn::ExecuteFencedInfoCallback>>
executeFenced(const nn::Request & request,const std::vector<nn::SyncFence> & waitFor,nn::MeasureTiming measure,const nn::OptionalTimePoint & deadline,const nn::OptionalDuration & loopTimeoutDuration,const nn::OptionalDuration & timeoutDurationAfterFence,const std::vector<nn::TokenValuePair> &,const std::vector<nn::ExtensionNameAndPrefix> &) const178 PreparedModel::executeFenced(
179 const nn::Request& request, const std::vector<nn::SyncFence>& waitFor,
180 nn::MeasureTiming measure, const nn::OptionalTimePoint& deadline,
181 const nn::OptionalDuration& loopTimeoutDuration,
182 const nn::OptionalDuration& timeoutDurationAfterFence,
183 const std::vector<nn::TokenValuePair>& /*hints*/,
184 const std::vector<nn::ExtensionNameAndPrefix>& /*extensionNameToPrefix*/) const {
185 // Ensure that request is ready for IPC.
186 std::optional<nn::Request> maybeRequestInShared;
187 hal::utils::RequestRelocation relocation;
188 const nn::Request& requestInShared = NN_TRY(hal::utils::convertRequestFromPointerToShared(
189 &request, nn::kDefaultRequestMemoryAlignment, nn::kMinMemoryPadding,
190 &maybeRequestInShared, &relocation));
191
192 const auto hidlRequest = NN_TRY(convert(requestInShared));
193 const auto hidlWaitFor = NN_TRY(convertSyncFences(waitFor));
194 const auto hidlMeasure = NN_TRY(convert(measure));
195 const auto hidlDeadline = NN_TRY(convert(deadline));
196 const auto hidlLoopTimeoutDuration = NN_TRY(convert(loopTimeoutDuration));
197 const auto hidlTimeoutDurationAfterFence = NN_TRY(convert(timeoutDurationAfterFence));
198
199 return executeFencedInternal(hidlRequest, hidlWaitFor, hidlMeasure, hidlDeadline,
200 hidlLoopTimeoutDuration, hidlTimeoutDurationAfterFence,
201 relocation);
202 }
203
204 nn::GeneralResult<std::pair<nn::SyncFence, nn::ExecuteFencedInfoCallback>>
executeFencedInternal(const Request & request,const hidl_vec<hidl_handle> & waitFor,V1_2::MeasureTiming measure,const OptionalTimePoint & deadline,const OptionalTimeoutDuration & loopTimeoutDuration,const OptionalTimeoutDuration & timeoutDurationAfterFence,const hal::utils::RequestRelocation & relocation) const205 PreparedModel::executeFencedInternal(const Request& request, const hidl_vec<hidl_handle>& waitFor,
206 V1_2::MeasureTiming measure, const OptionalTimePoint& deadline,
207 const OptionalTimeoutDuration& loopTimeoutDuration,
208 const OptionalTimeoutDuration& timeoutDurationAfterFence,
209 const hal::utils::RequestRelocation& relocation) const {
210 if (relocation.input) {
211 relocation.input->flush();
212 }
213
214 auto cb = hal::utils::CallbackValue(fencedExecutionCallback);
215
216 const auto ret =
217 kPreparedModel->executeFenced(request, waitFor, measure, deadline, loopTimeoutDuration,
218 timeoutDurationAfterFence, cb);
219 HANDLE_TRANSPORT_FAILURE(ret);
220 auto [syncFence, callback] = NN_TRY(cb.take());
221
222 // If executeFenced required the request memory to be moved into shared memory, block here until
223 // the fenced execution has completed and flush the memory back.
224 if (relocation.output) {
225 const auto state = syncFence.syncWait({});
226 if (state != nn::SyncFence::FenceState::SIGNALED) {
227 return NN_ERROR() << "syncWait failed with " << state;
228 }
229 relocation.output->flush();
230 }
231
232 return std::make_pair(std::move(syncFence), std::move(callback));
233 }
234
createReusableExecution(const nn::Request & request,nn::MeasureTiming measure,const nn::OptionalDuration & loopTimeoutDuration,const std::vector<nn::TokenValuePair> &,const std::vector<nn::ExtensionNameAndPrefix> &) const235 nn::GeneralResult<nn::SharedExecution> PreparedModel::createReusableExecution(
236 const nn::Request& request, nn::MeasureTiming measure,
237 const nn::OptionalDuration& loopTimeoutDuration,
238 const std::vector<nn::TokenValuePair>& /*hints*/,
239 const std::vector<nn::ExtensionNameAndPrefix>& /*extensionNameToPrefix*/) const {
240 // Ensure that request is ready for IPC.
241 std::optional<nn::Request> maybeRequestInShared;
242 hal::utils::RequestRelocation relocation;
243 const nn::Request& requestInShared = NN_TRY(hal::utils::convertRequestFromPointerToShared(
244 &request, nn::kDefaultRequestMemoryAlignment, nn::kMinMemoryPadding,
245 &maybeRequestInShared, &relocation));
246
247 auto hidlRequest = NN_TRY(convert(requestInShared));
248 auto hidlMeasure = NN_TRY(convert(measure));
249 auto hidlLoopTimeoutDuration = NN_TRY(convert(loopTimeoutDuration));
250 return Execution::create(shared_from_this(), std::move(hidlRequest), std::move(relocation),
251 hidlMeasure, std::move(hidlLoopTimeoutDuration));
252 }
253
configureExecutionBurst() const254 nn::GeneralResult<nn::SharedBurst> PreparedModel::configureExecutionBurst() const {
255 const auto pollingTimeWindow = V1_2::utils::getBurstControllerPollingTimeWindow();
256 return V1_2::utils::Burst::create(shared_from_this(), kPreparedModel, pollingTimeWindow);
257 }
258
getUnderlyingResource() const259 std::any PreparedModel::getUnderlyingResource() const {
260 sp<V1_3::IPreparedModel> resource = kPreparedModel;
261 return resource;
262 }
263
264 } // namespace android::hardware::neuralnetworks::V1_3::utils
265