Michael Butler | 616701d | 2020-01-07 14:52:44 -0800 | [diff] [blame] | 1 | /* |
| 2 | * Copyright (C) 2019 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 "1.0/Utils.h" |
| 18 | #include "1.3/Callbacks.h" |
| 19 | #include "1.3/Utils.h" |
| 20 | #include "GeneratedTestHarness.h" |
| 21 | #include "Utils.h" |
| 22 | |
| 23 | namespace android::hardware::neuralnetworks::V1_3::vts::functional { |
| 24 | |
| 25 | using implementation::ExecutionCallback; |
| 26 | using implementation::PreparedModelCallback; |
| 27 | using test_helper::TestBuffer; |
| 28 | using test_helper::TestModel; |
| 29 | using V1_1::ExecutionPreference; |
| 30 | using V1_2::MeasureTiming; |
| 31 | using V1_2::OutputShape; |
| 32 | using V1_2::Timing; |
| 33 | |
| 34 | using HidlToken = |
| 35 | hidl_array<uint8_t, static_cast<uint32_t>(V1_2::Constant::BYTE_SIZE_OF_CACHE_TOKEN)>; |
| 36 | |
| 37 | enum class DeadlineBoundType { NOW, UNLIMITED }; |
| 38 | constexpr std::array<DeadlineBoundType, 2> deadlineBounds = {DeadlineBoundType::NOW, |
| 39 | DeadlineBoundType::UNLIMITED}; |
| 40 | std::string toString(DeadlineBoundType type) { |
| 41 | switch (type) { |
| 42 | case DeadlineBoundType::NOW: |
| 43 | return "NOW"; |
| 44 | case DeadlineBoundType::UNLIMITED: |
| 45 | return "UNLIMITED"; |
| 46 | } |
| 47 | LOG(FATAL) << "Unrecognized DeadlineBoundType: " << static_cast<int>(type); |
| 48 | return {}; |
| 49 | } |
| 50 | |
| 51 | using Results = std::tuple<ErrorStatus, hidl_vec<OutputShape>, Timing>; |
| 52 | using MaybeResults = std::optional<Results>; |
| 53 | |
| 54 | using ExecutionFunction = |
| 55 | std::function<MaybeResults(const sp<IPreparedModel>& preparedModel, const Request& request, |
| 56 | DeadlineBoundType deadlineBound)>; |
| 57 | |
| 58 | static OptionalTimePoint makeOptionalTimePoint(DeadlineBoundType deadlineBoundType) { |
| 59 | OptionalTimePoint deadline; |
| 60 | switch (deadlineBoundType) { |
| 61 | case DeadlineBoundType::NOW: { |
| 62 | const auto currentTime = std::chrono::steady_clock::now(); |
| 63 | const auto currentTimeInNanoseconds = |
| 64 | std::chrono::time_point_cast<std::chrono::nanoseconds>(currentTime); |
| 65 | const uint64_t nanosecondsSinceEpoch = |
| 66 | currentTimeInNanoseconds.time_since_epoch().count(); |
Michael Butler | 6a4172c | 2020-02-04 16:15:04 -0800 | [diff] [blame] | 67 | deadline.nanosecondsSinceEpoch(nanosecondsSinceEpoch); |
Michael Butler | 616701d | 2020-01-07 14:52:44 -0800 | [diff] [blame] | 68 | } break; |
| 69 | case DeadlineBoundType::UNLIMITED: { |
| 70 | uint64_t unlimited = std::numeric_limits<uint64_t>::max(); |
Michael Butler | 6a4172c | 2020-02-04 16:15:04 -0800 | [diff] [blame] | 71 | deadline.nanosecondsSinceEpoch(unlimited); |
Michael Butler | 616701d | 2020-01-07 14:52:44 -0800 | [diff] [blame] | 72 | } break; |
| 73 | } |
| 74 | return deadline; |
| 75 | } |
| 76 | |
| 77 | void runPrepareModelTest(const sp<IDevice>& device, const Model& model, Priority priority, |
| 78 | std::optional<DeadlineBoundType> deadlineBound) { |
| 79 | OptionalTimePoint deadline; |
| 80 | if (deadlineBound.has_value()) { |
| 81 | deadline = makeOptionalTimePoint(deadlineBound.value()); |
| 82 | } |
| 83 | |
| 84 | // see if service can handle model |
| 85 | bool fullySupportsModel = false; |
| 86 | const Return<void> supportedCall = device->getSupportedOperations_1_3( |
| 87 | model, [&fullySupportsModel](ErrorStatus status, const hidl_vec<bool>& supported) { |
| 88 | ASSERT_EQ(ErrorStatus::NONE, status); |
| 89 | ASSERT_NE(0ul, supported.size()); |
| 90 | fullySupportsModel = std::all_of(supported.begin(), supported.end(), |
| 91 | [](bool valid) { return valid; }); |
| 92 | }); |
| 93 | ASSERT_TRUE(supportedCall.isOk()); |
| 94 | |
| 95 | // launch prepare model |
| 96 | const sp<PreparedModelCallback> preparedModelCallback = new PreparedModelCallback(); |
| 97 | const Return<ErrorStatus> prepareLaunchStatus = device->prepareModel_1_3( |
| 98 | model, ExecutionPreference::FAST_SINGLE_ANSWER, priority, deadline, |
| 99 | hidl_vec<hidl_handle>(), hidl_vec<hidl_handle>(), HidlToken(), preparedModelCallback); |
| 100 | ASSERT_TRUE(prepareLaunchStatus.isOk()); |
| 101 | ASSERT_EQ(ErrorStatus::NONE, static_cast<ErrorStatus>(prepareLaunchStatus)); |
| 102 | |
| 103 | // retrieve prepared model |
| 104 | preparedModelCallback->wait(); |
| 105 | const ErrorStatus prepareReturnStatus = preparedModelCallback->getStatus(); |
| 106 | const sp<V1_0::IPreparedModel> preparedModelV1_0 = preparedModelCallback->getPreparedModel(); |
| 107 | const sp<IPreparedModel> preparedModel = |
| 108 | IPreparedModel::castFrom(preparedModelV1_0).withDefault(nullptr); |
| 109 | |
| 110 | // The getSupportedOperations_1_3 call returns a list of operations that are |
| 111 | // guaranteed not to fail if prepareModel_1_3 is called, and |
| 112 | // 'fullySupportsModel' is true i.f.f. the entire model is guaranteed. |
| 113 | // If a driver has any doubt that it can prepare an operation, it must |
| 114 | // return false. So here, if a driver isn't sure if it can support an |
| 115 | // operation, but reports that it successfully prepared the model, the test |
| 116 | // can continue. |
| 117 | if (!fullySupportsModel && prepareReturnStatus != ErrorStatus::NONE) { |
| 118 | ASSERT_EQ(nullptr, preparedModel.get()); |
| 119 | return; |
| 120 | } |
| 121 | |
| 122 | // verify return status |
| 123 | if (!deadlineBound.has_value()) { |
| 124 | EXPECT_EQ(ErrorStatus::NONE, prepareReturnStatus); |
| 125 | } else { |
| 126 | switch (deadlineBound.value()) { |
| 127 | case DeadlineBoundType::NOW: |
| 128 | // If the execution was launched with a deadline of NOW, the |
| 129 | // deadline has already passed when the driver would launch the |
| 130 | // execution. In this case, the driver must return |
| 131 | // MISSED_DEADLINE_*. |
| 132 | EXPECT_TRUE(prepareReturnStatus == ErrorStatus::MISSED_DEADLINE_TRANSIENT || |
| 133 | prepareReturnStatus == ErrorStatus::MISSED_DEADLINE_PERSISTENT); |
| 134 | break; |
| 135 | case DeadlineBoundType::UNLIMITED: |
| 136 | // If an unlimited deadline is supplied, we expect the execution to |
| 137 | // proceed normally. In this case, check it normally by breaking out |
| 138 | // of the switch statement. |
| 139 | EXPECT_EQ(ErrorStatus::NONE, prepareReturnStatus); |
| 140 | break; |
| 141 | } |
| 142 | } |
| 143 | ASSERT_EQ(prepareReturnStatus == ErrorStatus::NONE, preparedModel.get() != nullptr); |
| 144 | } |
| 145 | |
| 146 | void runPrepareModelTests(const sp<IDevice>& device, const Model& model, |
| 147 | bool supportsPrepareModelDeadline) { |
| 148 | // test priority |
| 149 | for (auto priority : hidl_enum_range<Priority>{}) { |
| 150 | SCOPED_TRACE("priority: " + toString(priority)); |
| 151 | if (priority == kDefaultPriority) continue; |
| 152 | runPrepareModelTest(device, model, priority, {}); |
| 153 | } |
| 154 | |
| 155 | // test deadline |
| 156 | if (supportsPrepareModelDeadline) { |
| 157 | for (auto deadlineBound : deadlineBounds) { |
| 158 | SCOPED_TRACE("deadlineBound: " + toString(deadlineBound)); |
| 159 | runPrepareModelTest(device, model, kDefaultPriority, deadlineBound); |
| 160 | } |
| 161 | } |
| 162 | } |
| 163 | |
| 164 | static MaybeResults executeAsynchronously(const sp<IPreparedModel>& preparedModel, |
| 165 | const Request& request, DeadlineBoundType deadlineBound) { |
| 166 | SCOPED_TRACE("asynchronous"); |
| 167 | const MeasureTiming measure = MeasureTiming::NO; |
| 168 | const OptionalTimePoint deadline = makeOptionalTimePoint(deadlineBound); |
| 169 | |
| 170 | // launch execution |
| 171 | const sp<ExecutionCallback> callback = new ExecutionCallback(); |
| 172 | Return<ErrorStatus> ret = preparedModel->execute_1_3(request, measure, deadline, callback); |
| 173 | EXPECT_TRUE(ret.isOk()); |
| 174 | EXPECT_EQ(ErrorStatus::NONE, ret.withDefault(ErrorStatus::GENERAL_FAILURE)); |
| 175 | if (!ret.isOk() || ret != ErrorStatus::NONE) return std::nullopt; |
| 176 | |
| 177 | // retrieve execution results |
| 178 | callback->wait(); |
| 179 | const ErrorStatus status = callback->getStatus(); |
| 180 | hidl_vec<OutputShape> outputShapes = callback->getOutputShapes(); |
| 181 | const Timing timing = callback->getTiming(); |
| 182 | |
| 183 | // return results |
| 184 | return Results{status, std::move(outputShapes), timing}; |
| 185 | } |
| 186 | |
| 187 | static MaybeResults executeSynchronously(const sp<IPreparedModel>& preparedModel, |
| 188 | const Request& request, DeadlineBoundType deadlineBound) { |
| 189 | SCOPED_TRACE("synchronous"); |
| 190 | const MeasureTiming measure = MeasureTiming::NO; |
| 191 | const OptionalTimePoint deadline = makeOptionalTimePoint(deadlineBound); |
| 192 | |
| 193 | // configure results callback |
| 194 | MaybeResults results; |
| 195 | const auto cb = [&results](const auto&... args) { *results = {args...}; }; |
| 196 | |
| 197 | // run execution |
| 198 | const Return<void> ret = |
| 199 | preparedModel->executeSynchronously_1_3(request, measure, deadline, cb); |
| 200 | EXPECT_TRUE(ret.isOk()); |
| 201 | if (!ret.isOk()) return std::nullopt; |
| 202 | |
| 203 | // return results |
| 204 | return results; |
| 205 | } |
| 206 | |
| 207 | void runExecutionTest(const sp<IPreparedModel>& preparedModel, const TestModel& testModel, |
| 208 | const Request& request, bool synchronous, DeadlineBoundType deadlineBound) { |
| 209 | const ExecutionFunction execute = synchronous ? executeSynchronously : executeAsynchronously; |
| 210 | |
| 211 | // Perform execution and unpack results. |
| 212 | const auto results = execute(preparedModel, request, deadlineBound); |
| 213 | if (!results.has_value()) return; |
| 214 | const auto& [status, outputShapes, timing] = results.value(); |
| 215 | |
| 216 | // Verify no timing information was returned |
| 217 | EXPECT_EQ(UINT64_MAX, timing.timeOnDevice); |
| 218 | EXPECT_EQ(UINT64_MAX, timing.timeInDriver); |
| 219 | |
| 220 | // Validate deadline information if applicable. |
| 221 | switch (deadlineBound) { |
| 222 | case DeadlineBoundType::NOW: |
| 223 | // If the execution was launched with a deadline of NOW, the |
| 224 | // deadline has already passed when the driver would launch the |
| 225 | // execution. In this case, the driver must return |
| 226 | // MISSED_DEADLINE_*. |
| 227 | ASSERT_TRUE(status == ErrorStatus::MISSED_DEADLINE_TRANSIENT || |
| 228 | status == ErrorStatus::MISSED_DEADLINE_PERSISTENT); |
| 229 | return; |
| 230 | case DeadlineBoundType::UNLIMITED: |
| 231 | // If an unlimited deadline is supplied, we expect the execution to |
| 232 | // proceed normally. In this case, check it normally by breaking out |
| 233 | // of the switch statement. |
| 234 | ASSERT_EQ(ErrorStatus::NONE, status); |
| 235 | break; |
| 236 | } |
| 237 | |
| 238 | // If the model output operands are fully specified, outputShapes must be either |
| 239 | // either empty, or have the same number of elements as the number of outputs. |
| 240 | ASSERT_TRUE(outputShapes.size() == 0 || outputShapes.size() == testModel.outputIndexes.size()); |
| 241 | |
| 242 | // Go through all outputs, check returned output shapes. |
| 243 | for (uint32_t i = 0; i < outputShapes.size(); i++) { |
| 244 | EXPECT_TRUE(outputShapes[i].isSufficient); |
| 245 | const auto& expect = testModel.operands[testModel.outputIndexes[i]].dimensions; |
| 246 | const std::vector<uint32_t> actual = outputShapes[i].dimensions; |
| 247 | EXPECT_EQ(expect, actual); |
| 248 | } |
| 249 | |
| 250 | // Retrieve execution results. |
| 251 | ASSERT_TRUE(nn::compliantWithV1_0(request)); |
| 252 | const V1_0::Request request10 = nn::convertToV1_0(request); |
| 253 | const std::vector<TestBuffer> outputs = getOutputBuffers(request10); |
| 254 | |
| 255 | // We want "close-enough" results. |
| 256 | checkResults(testModel, outputs); |
| 257 | } |
| 258 | |
| 259 | void runExecutionTests(const sp<IPreparedModel>& preparedModel, const TestModel& testModel, |
| 260 | const Request& request) { |
| 261 | for (bool synchronous : {false, true}) { |
| 262 | for (auto deadlineBound : deadlineBounds) { |
| 263 | runExecutionTest(preparedModel, testModel, request, synchronous, deadlineBound); |
| 264 | } |
| 265 | } |
| 266 | } |
| 267 | |
| 268 | void runTests(const sp<IDevice>& device, const TestModel& testModel, |
| 269 | std::pair<bool, bool> supportsDeadlines) { |
| 270 | // setup |
| 271 | const auto [supportsPrepareModelDeadline, supportsExecutionDeadline] = supportsDeadlines; |
| 272 | if (!supportsPrepareModelDeadline && !supportsExecutionDeadline) return; |
| 273 | const Model model = createModel(testModel); |
| 274 | |
| 275 | // run prepare model tests |
| 276 | runPrepareModelTests(device, model, supportsPrepareModelDeadline); |
| 277 | |
| 278 | if (supportsExecutionDeadline) { |
| 279 | // prepare model |
| 280 | sp<IPreparedModel> preparedModel; |
| 281 | createPreparedModel(device, model, &preparedModel); |
| 282 | if (preparedModel == nullptr) return; |
| 283 | |
| 284 | // run execution tests |
| 285 | const Request request = nn::convertToV1_3(createRequest(testModel)); |
| 286 | runExecutionTests(preparedModel, testModel, request); |
| 287 | } |
| 288 | } |
| 289 | |
| 290 | class DeadlineTest : public GeneratedTestBase {}; |
| 291 | |
| 292 | TEST_P(DeadlineTest, Test) { |
| 293 | runTests(kDevice, kTestModel, mSupportsDeadlines); |
| 294 | } |
| 295 | |
| 296 | INSTANTIATE_GENERATED_TEST(DeadlineTest, |
| 297 | [](const TestModel& testModel) { return !testModel.expectFailure; }); |
| 298 | |
| 299 | } // namespace android::hardware::neuralnetworks::V1_3::vts::functional |