No CPU fallback will be provided when using introspection API
ANeuralNetworksCompilation_createForDevices.
Bug: 120796109
Bug: 120443043
Test: mm
Test: NeuralNetworksTest_static
Change-Id: I5088caac03cabde63a5447934364172882e6a16c
diff --git a/runtime/ModelBuilder.cpp b/runtime/ModelBuilder.cpp
index c82aa3f..6d3373e 100644
--- a/runtime/ModelBuilder.cpp
+++ b/runtime/ModelBuilder.cpp
@@ -20,6 +20,7 @@
#include "CompilationBuilder.h"
#include "GraphDump.h"
+#include "Manager.h"
#include "Utils.h"
#include "ValidateHal.h"
@@ -459,13 +460,17 @@
}
int ModelBuilder::createCompilation(CompilationBuilder** compilation,
- const std::vector<std::shared_ptr<Device>>& devices) {
+ const std::vector<std::shared_ptr<Device>>& devices,
+ bool forceNoFallback) {
if (!mCompletedModel || mInvalidModel) {
LOG(ERROR) << "ANeuralNetworksCompilation_create passed an unfinished or invalid model";
*compilation = nullptr;
return ANEURALNETWORKS_BAD_STATE;
}
*compilation = new (std::nothrow) CompilationBuilder(this, devices);
+ if (forceNoFallback) {
+ (*compilation)->setPartitioning(DeviceManager::kPartitioningWithoutFallback);
+ }
return (*compilation ? ANEURALNETWORKS_NO_ERROR : ANEURALNETWORKS_OUT_OF_MEMORY);
}
diff --git a/runtime/ModelBuilder.h b/runtime/ModelBuilder.h
index 3a8e0c1..e5cd468 100644
--- a/runtime/ModelBuilder.h
+++ b/runtime/ModelBuilder.h
@@ -65,7 +65,8 @@
bool hasExtensionOperation() const { return mHasExtensionOperation; }
int createCompilation(CompilationBuilder** compilation,
- const std::vector<std::shared_ptr<Device>>& devices);
+ const std::vector<std::shared_ptr<Device>>& devices,
+ bool forceNoFallback = false);
void setHidlModel(Model* model) const;
diff --git a/runtime/NeuralNetworks.cpp b/runtime/NeuralNetworks.cpp
index d244935..a85c6e8 100644
--- a/runtime/NeuralNetworks.cpp
+++ b/runtime/NeuralNetworks.cpp
@@ -496,7 +496,8 @@
}
ModelBuilder* m = reinterpret_cast<ModelBuilder*>(model);
CompilationBuilder* c = nullptr;
- int result = m->createCompilation(&c, selectedDevices);
+ // No CPU fallback when user specifies the list of devices manually.
+ int result = m->createCompilation(&c, selectedDevices, /* forceNoFallback */ true);
*compilation = reinterpret_cast<ANeuralNetworksCompilation*>(c);
return result;
}
diff --git a/runtime/include/NeuralNetworks.h b/runtime/include/NeuralNetworks.h
index ffa059e..3024392 100644
--- a/runtime/include/NeuralNetworks.h
+++ b/runtime/include/NeuralNetworks.h
@@ -5094,6 +5094,11 @@
* ANeuralNetworksModel_getSupportedOperationsForDevices() must have returned true for every
* operation for that model/devices pair.
*
+ * The user must handle all compilation and execution failures from the
+ * specified set of devices. This is in contrast to a use of {@link
+ * ANeuralNetworksCompilation_create}, where the runtime will attempt to recover
+ * from such failures.
+ *
* @param model The {@link ANeuralNetworksModel} to be compiled.
* @param devices The set of devices. Must not contain duplicates.
* @param numDevices The number of devices in the set.
diff --git a/runtime/test/TestExecution.cpp b/runtime/test/TestExecution.cpp
index 92e5e58..c4f1575 100644
--- a/runtime/test/TestExecution.cpp
+++ b/runtime/test/TestExecution.cpp
@@ -335,21 +335,69 @@
}
};
-template<class DriverClass>
-class ExecutionTestTemplate :
- public ::testing::TestWithParam<std::tuple<ErrorStatus, Result>> {
-public:
- ExecutionTestTemplate() :
- kName(toString(std::get<0>(GetParam()))),
- kForceErrorStatus(std::get<0>(GetParam())),
- kExpectResult(std::get<1>(GetParam())),
- mModel(makeModel()),
- mCompilation(&mModel, kName, kForceErrorStatus) {}
+// This class has roughly the same functionality as TestCompilation class.
+// The major difference is that Introspection API is used to select the device.
+template <typename DriverClass>
+class TestIntrospectionCompilation : public WrapperCompilation {
+ public:
+ TestIntrospectionCompilation(const WrapperModel* model, const std::string& deviceName) {
+ std::vector<ANeuralNetworksDevice*> mDevices;
+ uint32_t numDevices = 0;
+ EXPECT_EQ(ANeuralNetworks_getDeviceCount(&numDevices), ANEURALNETWORKS_NO_ERROR);
+ EXPECT_GE(numDevices, (uint32_t)1);
-protected:
+ for (uint32_t i = 0; i < numDevices; i++) {
+ ANeuralNetworksDevice* device = nullptr;
+ EXPECT_EQ(ANeuralNetworks_getDevice(i, &device), ANEURALNETWORKS_NO_ERROR);
+ const char* buffer = nullptr;
+ int result = ANeuralNetworksDevice_getName(device, &buffer);
+ if (result == ANEURALNETWORKS_NO_ERROR && deviceName.compare(buffer) == 0) {
+ mDevices.push_back(device);
+ }
+ }
+ // In CPU only mode, DeviceManager::getDrivers() will not be able to
+ // provide the actual device list. We will not be able to find the test
+ // driver with specified deviceName.
+ if (!DeviceManager::get()->getUseCpuOnly()) {
+ EXPECT_EQ(mDevices.size(), (uint32_t)1);
+
+ int result = ANeuralNetworksCompilation_createForDevices(
+ model->getHandle(), mDevices.data(), mDevices.size(), &mCompilation);
+ EXPECT_EQ(result, ANEURALNETWORKS_NO_ERROR);
+ }
+ }
+};
+
+template <class DriverClass>
+class ExecutionTestTemplate
+ : public ::testing::TestWithParam<std::tuple<ErrorStatus, Result, bool>> {
+ public:
+ ExecutionTestTemplate()
+ : kName(toString(std::get<0>(GetParam()))),
+ kForceErrorStatus(std::get<0>(GetParam())),
+ kExpectResult(std::get<1>(GetParam())),
+ kUseIntrospectionAPI(std::get<2>(GetParam())),
+ mModel(makeModel()) {
+ if (kUseIntrospectionAPI) {
+ DeviceManager::get()->forTest_registerDevice(kName.c_str(),
+ new DriverClass(kName, kForceErrorStatus));
+ mCompilation = TestIntrospectionCompilation<DriverClass>(&mModel, kName);
+ } else {
+ mCompilation = TestCompilation<DriverClass>(&mModel, kName, kForceErrorStatus);
+ }
+ }
+
+ protected:
// Unit test method
void TestWait();
+ virtual void TearDown() {
+ // Reinitialize the device list since Introspection API path altered it.
+ if (kUseIntrospectionAPI) {
+ DeviceManager::get()->forTest_reInitializeDeviceList();
+ }
+ }
+
const std::string kName;
// Allow dummying up the error status for execution. If
@@ -363,8 +411,11 @@
// equivalent of kForceErrorStatus.)
const Result kExpectResult;
+ // Whether mCompilation is created via Introspection API or not.
+ const bool kUseIntrospectionAPI;
+
WrapperModel mModel;
- TestCompilation<DriverClass> mCompilation;
+ WrapperCompilation mCompilation;
void setInputOutput(WrapperExecution* execution) {
mInputBuffer = kInputBuffer;
@@ -397,6 +448,11 @@
template<class DriverClass> void ExecutionTestTemplate<DriverClass>::TestWait() {
SCOPED_TRACE(kName);
+ // Skip Introspection API tests when CPU only flag is forced on.
+ if (kUseIntrospectionAPI && DeviceManager::get()->getUseCpuOnly()) {
+ GTEST_SKIP();
+ }
+
ASSERT_EQ(mCompilation.finish(), Result::NO_ERROR);
{
@@ -446,11 +502,15 @@
}
auto kTestValues = ::testing::Values(
- std::make_tuple(ErrorStatus::NONE, Result::NO_ERROR),
- std::make_tuple(ErrorStatus::DEVICE_UNAVAILABLE, Result::UNAVAILABLE_DEVICE),
- std::make_tuple(ErrorStatus::GENERAL_FAILURE, Result::OP_FAILED),
- std::make_tuple(ErrorStatus::OUTPUT_INSUFFICIENT_SIZE, Result::OUTPUT_INSUFFICIENT_SIZE),
- std::make_tuple(ErrorStatus::INVALID_ARGUMENT, Result::BAD_DATA));
+ std::make_tuple(ErrorStatus::NONE, Result::NO_ERROR, /* kUseIntrospectionAPI */ false),
+ std::make_tuple(ErrorStatus::DEVICE_UNAVAILABLE, Result::UNAVAILABLE_DEVICE,
+ /* kUseIntrospectionAPI */ false),
+ std::make_tuple(ErrorStatus::GENERAL_FAILURE, Result::OP_FAILED,
+ /* kUseIntrospectionAPI */ false),
+ std::make_tuple(ErrorStatus::OUTPUT_INSUFFICIENT_SIZE, Result::OUTPUT_INSUFFICIENT_SIZE,
+ /* kUseIntrospectionAPI */ false),
+ std::make_tuple(ErrorStatus::INVALID_ARGUMENT, Result::BAD_DATA,
+ /* kUseIntrospectionAPI */ false));
class ExecutionTest12 : public ExecutionTestTemplate<TestDriver12> {};
TEST_P(ExecutionTest12, Wait) {
@@ -472,5 +532,18 @@
}
INSTANTIATE_TEST_CASE_P(Flavor, ExecutionTest10, kTestValues);
+auto kIntrospectionTestValues = ::testing::Values(
+ std::make_tuple(ErrorStatus::NONE, Result::NO_ERROR, /* kUseIntrospectionAPI */ true),
+ std::make_tuple(ErrorStatus::DEVICE_UNAVAILABLE, Result::UNAVAILABLE_DEVICE,
+ /* kUseIntrospectionAPI */ true),
+ std::make_tuple(ErrorStatus::GENERAL_FAILURE, Result::OP_FAILED,
+ /* kUseIntrospectionAPI */ true),
+ std::make_tuple(ErrorStatus::OUTPUT_INSUFFICIENT_SIZE, Result::OUTPUT_INSUFFICIENT_SIZE,
+ /* kUseIntrospectionAPI */ true),
+ std::make_tuple(ErrorStatus::INVALID_ARGUMENT, Result::BAD_DATA,
+ /* kUseIntrospectionAPI */ true));
+
+INSTANTIATE_TEST_CASE_P(IntrospectionFlavor, ExecutionTest12, kIntrospectionTestValues);
+
} // namespace
} // namespace android
diff --git a/runtime/test/TestIntrospectionControl.cpp b/runtime/test/TestIntrospectionControl.cpp
index 03ab21e..3bf5e77 100644
--- a/runtime/test/TestIntrospectionControl.cpp
+++ b/runtime/test/TestIntrospectionControl.cpp
@@ -264,6 +264,8 @@
ASSERT_TRUE(model->isValid());
}
+// TODO(miaowang): add a test to make sure ANNCompilation_create() has CPU
+// fallback.
// This test verifies that a device that could only handle ADD would correctly report that an
// ADD->MUL model could not be fully supported.
TEST_F(IntrospectionControlTest, PartialModelNotSupported) {
@@ -287,6 +289,14 @@
EXPECT_TRUE(selectDeviceByName(addOnlyDriver));
EXPECT_TRUE(isSupportedOpListExpected({true, false}));
+
+ ANeuralNetworksModel* modelHandle = mModel.getHandle();
+ EXPECT_EQ(ANeuralNetworksCompilation_createForDevices(modelHandle, mDevices.data(),
+ mDevices.size(), &mCompilation),
+ ANEURALNETWORKS_NO_ERROR);
+ // The compilation must fail as there is no fallback when using
+ // Introspection API.
+ EXPECT_NE(ANeuralNetworksCompilation_finish(mCompilation), ANEURALNETWORKS_NO_ERROR);
}
// This test verifies that a device that could only handle ADD would correctly report that an
@@ -364,5 +374,4 @@
EXPECT_EQ(output[0], kSimpleMultiplier * (input1[0] + input2[0]));
EXPECT_EQ(output[1], kSimpleMultiplier * (input1[1] + input2[1]));
}
-
} // namespace