summaryrefslogtreecommitdiff
path: root/nn/runtime/test/TestPartitioningRandom.cpp
diff options
context:
space:
mode:
authorDavid Gross <dgross@google.com>2019-05-09 15:55:53 -0700
committerDavid Gross <dgross@google.com>2019-05-10 15:01:47 -0700
commite5a8e1e5ac2575af29c5d848b317dbd1cdbf8a4e (patch)
tree07e7936269e7a8f0631e8a26016af0c774b60616 /nn/runtime/test/TestPartitioningRandom.cpp
parent164c1c5bbd9a549b2a1fe3c35a20f4323ca8100f (diff)
downloadml-e5a8e1e5ac2575af29c5d848b317dbd1cdbf8a4e.tar.gz
Support partitions of V1_x::Model executing on V1_y::IDevice (x > y).
The old behavior is that none of a model can be executed on a device whose HAL interface is too old for the model -- e.g., if the model contains at least one V1_2 operation, then we cannot execute any of the model on a V1_1 or V1_0 device, even V1_1 or V1_0 operations from that model. This CL removes that limitation: Any of a model's operations that can be represented in a device's HAL interface are eligible for execution on that device (subject to the usual constraints about the device acknowledging that it supports an operation, and the partitioning algorithm deciding which device is the best choice for a given operation). Also: - Fix bug in TestPartitioning.cpp model comparison. In the various lists of different kinds of model input and output operand indexes, we need to do topological rather than numerical comparisons, just as we do when topological rather than numerical comparisons of the model graphs. - Add some V1_1 and V1_2 operations to random partition testing. Bug: 79165035 Test: NeuralNetworksTest_static Test: NeuralNetworksTest_static --gtest_filter=Seed/RandomPartitioningTest.Test/* running over ~50K seeds Change-Id: I69f5e4b77ee0e8d4a8d503b166b44d6ed1a3be6c Merged-In: I69f5e4b77ee0e8d4a8d503b166b44d6ed1a3be6c (cherry picked from commit 475926a99f53bb09fbdedeb9a9ed2115cbf677c8)
Diffstat (limited to 'nn/runtime/test/TestPartitioningRandom.cpp')
-rw-r--r--nn/runtime/test/TestPartitioningRandom.cpp270
1 files changed, 224 insertions, 46 deletions
diff --git a/nn/runtime/test/TestPartitioningRandom.cpp b/nn/runtime/test/TestPartitioningRandom.cpp
index 8ff5958dd..fdc54a1b7 100644
--- a/nn/runtime/test/TestPartitioningRandom.cpp
+++ b/nn/runtime/test/TestPartitioningRandom.cpp
@@ -28,6 +28,7 @@
#include <algorithm>
#include <cassert>
#include <cstdio>
+#include <iterator>
#include <random>
#include <set>
#include <tuple>
@@ -92,6 +93,7 @@ using CompilationBuilder = nn::CompilationBuilder;
using Device = nn::Device;
using DeviceManager = nn::DeviceManager;
using ExecutionPlan = nn::ExecutionPlan;
+using HalVersion = nn::HalVersion;
using HidlModel = hardware::neuralnetworks::V1_2::Model;
using HidlToken =
::android::hardware::hidl_array<uint8_t, ANEURALNETWORKS_BYTE_SIZE_OF_CACHE_TOKEN>;
@@ -166,7 +168,7 @@ public:
}
const std::vector<uint32_t>& getOperationOutputs(uint32_t index) const {
- assert(index < mOperations.size());
+ CHECK(index < mOperations.size());
return mOperations[index];
}
@@ -183,8 +185,15 @@ public:
}
}
+ void setOperandValue(uint32_t index, const std::vector<int32_t>& value) {
+ const size_t length = value.size() * sizeof(int32_t);
+
+ CHECK(length <= ANEURALNETWORKS_MAX_SIZE_OF_IMMEDIATELY_COPIED_VALUES);
+ WrapperModel::setOperandValue(index, value.data(), length);
+ }
+
void setOperandValue(uint32_t index, int32_t value) {
- assert(sizeof(value) <= ANEURALNETWORKS_MAX_SIZE_OF_IMMEDIATELY_COPIED_VALUES);
+ CHECK(sizeof(value) <= ANEURALNETWORKS_MAX_SIZE_OF_IMMEDIATELY_COPIED_VALUES);
WrapperModel::setOperandValue(index, &value, sizeof(value));
}
@@ -260,7 +269,7 @@ public:
TestMemories& operator=(const TestMemories&) = delete;
unsigned addMemory() {
- assert(!mLayoutDone);
+ CHECK(!mLayoutDone);
mMemorySizes.push_back(0);
return memoryCount() - 1;
}
@@ -269,8 +278,8 @@ public:
}
unsigned addRegion(unsigned memoryIndex, uint32_t length) {
- assert(!mLayoutDone);
- assert(memoryIndex < memoryCount());
+ CHECK(!mLayoutDone);
+ CHECK(memoryIndex < memoryCount());
uint32_t& memorySize = mMemorySizes[memoryIndex];
auto desc = std::make_tuple(memoryIndex, (uint32_t)memorySize, length);
mRegions.push_back(desc);
@@ -285,8 +294,8 @@ public:
void* getRegion(unsigned regionIndex,
const WrapperMemory** pMemory, uint32_t* pOffset, uint32_t* pLength) {
- assert(mLayoutDone);
- assert(regionIndex < regionCount());
+ CHECK(mLayoutDone);
+ CHECK(regionIndex < regionCount());
const auto& regionDescriptor = mRegions[regionIndex];
const WrapperMemory* memory = &mMemorys[std::get<0>(regionDescriptor)];
uint32_t offset = std::get<1>(regionDescriptor);
@@ -295,7 +304,7 @@ public:
uint8_t* buffer;
if (reinterpret_cast<MemoryBuilder*>(memory->get())->getPointer(&buffer) !=
ANEURALNETWORKS_NO_ERROR) {
- assert(0);
+ CHECK(0);
}
if (pMemory) *pMemory = memory;
@@ -327,10 +336,10 @@ private:
};
void TestMemories::layout() {
- assert(!mLayoutDone);
+ CHECK(!mLayoutDone);
for (uint32_t memorySize : mMemorySizes) {
const int fd = ASharedMemory_create(nullptr, memorySize);
- assert(fd >= 0);
+ CHECK(fd >= 0);
mMemorys.emplace_back(memorySize, PROT_READ | PROT_WRITE, fd, 0);
mFDs.push_back(fd);
}
@@ -350,12 +359,17 @@ public:
static Signature getSignature(const HidlModel& model, const Operation& operation);
protected:
- bool randBool() {
- return randUInt(2) == 1;
- }
+ static V1_0::IDevice* makeTestDriver(HalVersion version, const char* name,
+ std::set<Signature> signatures);
- double randFrac() { // [0.0, 1.0)
- return mRandNumUnitDist(mRandNumEng);
+ static HalVersion getMinHalVersion(ANeuralNetworksOperationType type);
+
+ static std::string to_string(HalVersion version);
+
+ bool randBool() { return randUInt(2) == 1; }
+
+ double randFrac() { // [0.0, 1.0)
+ return mRandNumUnitDist(mRandNumEng);
}
unsigned randUInt(unsigned limit) { // [0, limit)
@@ -371,6 +385,7 @@ protected:
// - There be at least one input operand that is neither an
// activation function nor "special".
struct OperationPattern {
+ HalVersion mMinHalVersion;
int mOperationType;
unsigned mNumInputs;
unsigned mNumOutputs;
@@ -387,6 +402,9 @@ protected:
static const OperationPattern kOperationPatterns[];
+ // See OperationPattern::mMakeSpecialInput. This function is used to
+ // manufacture an RNN input operand that doesn't fit the general operand
+ // pattern known to the graph generator infrastructure.
int makeRnnSpecialInput(unsigned problemSize, TestModel* model, unsigned inputIndex) {
if (inputIndex != 3) {
return -1;
@@ -402,6 +420,23 @@ protected:
return int(operandIndex);
}
+ // See OperationPattern::mMakeSpecialInput. This function is used to
+ // manufacture a TRANSPOSE input operand that doesn't fit the general operand
+ // pattern known to the graph generator infrastructure.
+ int makeTransposeSpecialInput(unsigned /* problemSize */, TestModel* model,
+ unsigned inputIndex) {
+ if (inputIndex != 1) {
+ return -1;
+ }
+
+ // input operand 1 is perm, a 1-D tensor
+ const WrapperOperandType permType(WrapperType::TENSOR_INT32, {2});
+ const uint32_t operandIndex = model->addOperand(&permType);
+ std::vector<int32_t> permValue = {1, 0};
+ model->setOperandValue(operandIndex, permValue);
+ return int(operandIndex);
+ }
+
#ifdef VERBOSE
class ModelStats {
public:
@@ -424,6 +459,15 @@ protected:
private:
const ModelBuilder* mBuilder;
};
+
+ template <typename T_iterator>
+ static void dump(T_iterator I, T_iterator E) {
+ std::cout << "{";
+ for (; I != E; I++) {
+ std::cout << " " << *I;
+ }
+ std::cout << " }" << std::endl;
+ }
#endif
std::mt19937 mRandNumEng;
@@ -433,15 +477,36 @@ protected:
};
const RandomPartitioningTest::OperationPattern RandomPartitioningTest::kOperationPatterns[] = {
- { ANEURALNETWORKS_ADD, 3, 1, 2, nullptr },
- { ANEURALNETWORKS_LOGISTIC, 1, 1, -1, nullptr },
- { ANEURALNETWORKS_MUL, 3, 1, 2, nullptr },
- { ANEURALNETWORKS_RNN, 6, 2, 5, &RandomPartitioningTest::makeRnnSpecialInput },
- { ANEURALNETWORKS_TANH, 1, 1, -1, nullptr },
+ {HalVersion::V1_0, ANEURALNETWORKS_ADD, 3, 1, 2, nullptr},
+ {HalVersion::V1_0, ANEURALNETWORKS_LOGISTIC, 1, 1, -1, nullptr},
+ {HalVersion::V1_0, ANEURALNETWORKS_MUL, 3, 1, 2, nullptr},
+ {HalVersion::V1_0, ANEURALNETWORKS_RNN, 6, 2, 5,
+ &RandomPartitioningTest::makeRnnSpecialInput},
+ {HalVersion::V1_0, ANEURALNETWORKS_TANH, 1, 1, -1, nullptr},
+
+ {HalVersion::V1_1, ANEURALNETWORKS_SUB, 3, 1, 2, nullptr},
+ {HalVersion::V1_1, ANEURALNETWORKS_TRANSPOSE, 2, 1, -1,
+ &RandomPartitioningTest::makeTransposeSpecialInput},
+
+ {HalVersion::V1_2, ANEURALNETWORKS_MAXIMUM, 2, 1, -1, nullptr},
+ {HalVersion::V1_2, ANEURALNETWORKS_NEG, 1, 1, -1, nullptr},
+ {HalVersion::V1_2, ANEURALNETWORKS_SIN, 1, 1, -1, nullptr},
};
+HalVersion RandomPartitioningTest::getMinHalVersion(ANeuralNetworksOperationType type) {
+ static const auto kOperationToVersion = [] {
+ std::map<ANeuralNetworksOperationType, HalVersion> result;
+ for (const auto& pattern : kOperationPatterns) {
+ result[pattern.mOperationType] = pattern.mMinHalVersion;
+ }
+ return result;
+ }();
+
+ return kOperationToVersion.at(type);
+}
+
Signature RandomPartitioningTest::getSignature(const HidlModel& model, const Operation& operation) {
- static const std::map<ANeuralNetworksOperationType, int> kOperationToActivation = []() {
+ static const auto kOperationToActivation = [] {
std::map<ANeuralNetworksOperationType, int> result;
for (const auto& pattern : kOperationPatterns) {
result[pattern.mOperationType] = pattern.mActivationFunctionInputIndex;
@@ -457,8 +522,8 @@ Signature RandomPartitioningTest::getSignature(const HidlModel& model, const Ope
}
const Operand& operand = model.operands[operation.inputs[activationFunctionInputIndex]];
- assert(operand.lifetime == OperandLifeTime::CONSTANT_COPY);
- assert(operand.type == OperandType::INT32);
+ CHECK(operand.lifetime == OperandLifeTime::CONSTANT_COPY);
+ CHECK(operand.type == OperandType::INT32);
int32_t value;
memcpy(&value,
&model.operandValues[operand.location.offset],
@@ -466,6 +531,19 @@ Signature RandomPartitioningTest::getSignature(const HidlModel& model, const Ope
return Signature(operationType, value);
}
+std::string RandomPartitioningTest::to_string(HalVersion version) {
+ switch (version) {
+ case HalVersion::V1_0:
+ return "V1_0";
+ case HalVersion::V1_1:
+ return "V1_1";
+ case HalVersion::V1_2:
+ return "V1_2";
+ default:
+ return "V_UNKNOWN";
+ }
+};
+
class TestDriver : public SampleDriver {
public:
// Behaves like SampleDriver, except that it only supports
@@ -534,6 +612,79 @@ private:
const std::set<Signature> mSignatures;
};
+// Like TestDriver, but implementing 1.1
+class TestDriverV1_1 : public V1_1::IDevice {
+ public:
+ TestDriverV1_1(const char* name, std::set<Signature> signatures)
+ : mDriverV1_2(new TestDriver(name, std::move(signatures))) {}
+ Return<void> getCapabilities_1_1(getCapabilities_1_1_cb _hidl_cb) override {
+ return mDriverV1_2->getCapabilities_1_1(_hidl_cb);
+ }
+ Return<void> getSupportedOperations_1_1(const V1_1::Model& model,
+ getSupportedOperations_1_1_cb _hidl_cb) override {
+ return mDriverV1_2->getSupportedOperations_1_1(model, _hidl_cb);
+ }
+ Return<ErrorStatus> prepareModel_1_1(
+ const V1_1::Model& model, ExecutionPreference preference,
+ const sp<V1_0::IPreparedModelCallback>& actualCallback) override {
+ return mDriverV1_2->prepareModel_1_1(model, preference, actualCallback);
+ }
+ Return<DeviceStatus> getStatus() override { return mDriverV1_2->getStatus(); }
+ Return<void> getCapabilities(getCapabilities_cb _hidl_cb) override {
+ return mDriverV1_2->getCapabilities(_hidl_cb);
+ }
+ Return<void> getSupportedOperations(const V1_0::Model& model,
+ getSupportedOperations_cb _hidl_cb) override {
+ return mDriverV1_2->getSupportedOperations(model, _hidl_cb);
+ }
+ Return<ErrorStatus> prepareModel(
+ const V1_0::Model& model,
+ const sp<V1_0::IPreparedModelCallback>& actualCallback) override {
+ return mDriverV1_2->prepareModel(model, actualCallback);
+ }
+
+ private:
+ const sp<V1_2::IDevice> mDriverV1_2;
+};
+
+// Like TestDriver, but implementing 1.0
+class TestDriverV1_0 : public V1_0::IDevice {
+ public:
+ TestDriverV1_0(const char* name, std::set<Signature> signatures)
+ : mDriverV1_2(new TestDriver(name, std::move(signatures))) {}
+ Return<void> getCapabilities(getCapabilities_cb _hidl_cb) override {
+ return mDriverV1_2->getCapabilities(_hidl_cb);
+ }
+ Return<void> getSupportedOperations(const V1_0::Model& model,
+ getSupportedOperations_cb _hidl_cb) override {
+ return mDriverV1_2->getSupportedOperations(model, _hidl_cb);
+ }
+ Return<ErrorStatus> prepareModel(
+ const V1_0::Model& model,
+ const sp<V1_0::IPreparedModelCallback>& actualCallback) override {
+ return mDriverV1_2->prepareModel(model, actualCallback);
+ }
+ Return<DeviceStatus> getStatus() override { return mDriverV1_2->getStatus(); }
+
+ private:
+ const sp<V1_2::IDevice> mDriverV1_2;
+};
+
+V1_0::IDevice* RandomPartitioningTest::makeTestDriver(HalVersion version, const char* name,
+ std::set<Signature> signatures) {
+ switch (version) {
+ case HalVersion::V1_0:
+ return new TestDriverV1_0(name, std::move(signatures));
+ case HalVersion::V1_1:
+ return new TestDriverV1_1(name, std::move(signatures));
+ case HalVersion::V1_2:
+ return new TestDriver(name, std::move(signatures));
+ default:
+ ADD_FAILURE() << "Unexpected HalVersion " << static_cast<int32_t>(version);
+ return nullptr;
+ }
+}
+
INSTANTIATE_TEST_CASE_P(Seed, RandomPartitioningTest,
::testing::Range(kFirstSeed, kFirstSeed + kNumTestCases));
@@ -605,8 +756,7 @@ TEST_P(RandomPartitioningTest, Test) {
// Generate operations.
for (unsigned i = 0; i < numOperations; i++) {
- const unsigned operationPatternIndex =
- randUInt(sizeof(kOperationPatterns)/sizeof(kOperationPatterns[0]));
+ const unsigned operationPatternIndex = randUInt(std::size(kOperationPatterns));
const auto& operationPattern = kOperationPatterns[operationPatternIndex];
// INPUTS //////////////////////////////////////////////////////////////////////////////////
@@ -640,7 +790,7 @@ TEST_P(RandomPartitioningTest, Test) {
}
normalOperationInputIndexes.push_back(operationInputIndex);
}
- assert(!normalOperationInputIndexes.empty());
+ CHECK(!normalOperationInputIndexes.empty());
signatures.insert(Signature(operationPattern.mOperationType, activationFunction));
// A (normal) operation input can be one of:
@@ -709,7 +859,7 @@ TEST_P(RandomPartitioningTest, Test) {
force(IK_OPERATION_OUTPUT);
}
if (modelInputs.empty()) {
- assert(model.operationCount() == 0);
+ CHECK(model.operationCount() == 0);
force(IK_MODEL_INPUT);
}
@@ -740,8 +890,8 @@ TEST_P(RandomPartitioningTest, Test) {
operandIndex =
existingOperationOutputs[randUInt(existingOperationOutputs.size())];
deadOperandI = deadOperands.find(operandIndex);
- assert(deadOperandI == deadOperands.end() ||
- deadOperandI->second == existingOperationIndex);
+ CHECK(deadOperandI == deadOperands.end() ||
+ deadOperandI->second == existingOperationIndex);
}
if (deadOperandI != deadOperands.end()) {
const uint32_t correspondingOperation = deadOperandI->second;
@@ -832,7 +982,7 @@ TEST_P(RandomPartitioningTest, Test) {
uint32_t offset, length;
float* region =
static_cast<float*>(weights.getRegion(regionIndex, &memory, &offset, &length));
- assert(length == problemSize * problemSize * sizeof(float));
+ CHECK(length == problemSize * problemSize * sizeof(float));
std::generate(region, region + problemSize * problemSize, [this]{ return randFrac(); });
model.setOperandValueFromMemory(operandIndex, memory, offset, length);
}
@@ -911,7 +1061,7 @@ TEST_P(RandomPartitioningTest, Test) {
ASSERT_EQ(c.finish(), Result::NO_ERROR);
// Create some drivers for partitioned compilation.
- assert(!signatures.empty());
+ CHECK(!signatures.empty());
std::vector<std::set<Signature>> signaturesForDriver(signatures.size());
// First assign each signature to a random driver (a driver is
// just represented as an entry in the signaturesForDriver
@@ -929,9 +1079,28 @@ TEST_P(RandomPartitioningTest, Test) {
// Now actually create the drivers.
std::vector<std::shared_ptr<Device>> devices;
for (unsigned i = 0; i < signaturesForDriver.size(); i++) {
- const std::string name = "TestDriver(" + std::to_string(i) + ")";
+ const auto& signaturesForThisDriver = signaturesForDriver[i];
+ // Minimum HAL version for this driver is highest minimum HAL version of
+ // any operation supported by this driver.
+ const HalVersion minHalVersion = getMinHalVersion(
+ std::max_element(signaturesForThisDriver.begin(), signaturesForThisDriver.end(),
+ [](const Signature& a, const Signature& b) {
+ return getMinHalVersion(a.first) < getMinHalVersion(b.first);
+ })
+ ->first);
+ const HalVersion actualHalVersion =
+ static_cast<HalVersion>(static_cast<int32_t>(minHalVersion) +
+ randUInt(static_cast<int32_t>(HalVersion::LATEST) -
+ static_cast<int32_t>(minHalVersion) + 1));
+ const std::string name =
+ "TestDriver(" + std::to_string(i) + "){" + to_string(actualHalVersion) + "}";
+#ifdef VERBOSE
+ std::cout << "Creating " + name + " for collection of signatures that requires HAL " +
+ to_string(minHalVersion)
+ << std::endl;
+#endif
auto device = DeviceManager::forTest_makeDriverDevice(
- name, new TestDriver(name.c_str(), signaturesForDriver[i]));
+ name, makeTestDriver(actualHalVersion, name.c_str(), signaturesForThisDriver));
devices.push_back(device);
}
// CPU fallback device
@@ -980,8 +1149,8 @@ TEST_P(RandomPartitioningTest, Test) {
std::cout << "plan: compound, " << steps.size() << " steps over "
<< devicesInPlan.size() << " devices" << std::endl;
for (unsigned i = 0; i < steps.size(); i++) {
- std::cout << "Step " << i << ": "
- << ModelStats(steps[i]->getSubModel()) << std::endl;
+ std::cout << "Step " << i << ": " << ModelStats(steps[i]->getSubModel())
+ << ", device = " << steps[i]->getDevice()->getName() << std::endl;
}
break;
}
@@ -1018,6 +1187,12 @@ TEST_P(RandomPartitioningTest, Test) {
// outputs anyway.
std::vector<float> masterInputs(problemSize * problemSize * model.inputCount());
std::generate(masterInputs.begin(), masterInputs.end(), [this]{ return randFrac(); });
+#ifdef VERBOSE
+ {
+ std::cout << "flat inputs = ";
+ dump(masterInputs.begin(), masterInputs.end());
+ }
+#endif
const float masterOutput = randFrac();
// Create the memory for the actual inputs and outputs.
@@ -1096,7 +1271,7 @@ TEST_P(RandomPartitioningTest, Test) {
float* region =
static_cast<float*>(ioMemories.getRegion(desc.mMemoryRegion,
&memory, &offset, &length));
- assert(length == problemSize * problemSize * sizeof(float));
+ CHECK(length == problemSize * problemSize * sizeof(float));
if (desc.mKind == InputOutputDescriptor::INPUT) {
const size_t inputOffset = inputIndex * problemSize * problemSize;
std::copy(masterInputs.begin() + inputOffset,
@@ -1112,8 +1287,8 @@ TEST_P(RandomPartitioningTest, Test) {
}
}
};
- assert(inputIndex == model.inputCount());
- assert(outputIndex == model.outputCount());
+ CHECK(inputIndex == model.inputCount());
+ CHECK(outputIndex == model.outputCount());
};
// Non-partitioned execution.
@@ -1142,14 +1317,9 @@ TEST_P(RandomPartitioningTest, Test) {
}
#ifdef VERBOSE
{
- std::cout << "output[" << outputIndex << "] = {";
- for (auto I = nonPartitionedOutputs.begin() + outputOffset,
- E = nonPartitionedOutputs.begin() +
- outputOffset + problemSize * problemSize;
- I != E; I++) {
- std::cout << " " << *I;
- }
- std::cout << " }" << std::endl;
+ std::cout << "nonpartitioned output[" << outputIndex << "] = ";
+ dump(nonPartitionedOutputs.begin() + outputOffset,
+ nonPartitionedOutputs.begin() + outputOffset + problemSize * problemSize);
}
#endif
outputIndex++;
@@ -1172,11 +1342,19 @@ TEST_P(RandomPartitioningTest, Test) {
SCOPED_TRACE(outputIndex);
const size_t outputOffset = outputIndex * problemSize * problemSize;
if (desc.getLocation() == InputOutputDescriptor::VECTOR) {
+#ifdef VERBOSE
+ std::cout << " partitioned output[" << outputIndex << "] = ";
+ dump(desc.mVector.begin(), desc.mVector.end());
+#endif
ASSERT_TRUE(std::equal(desc.mVector.begin(),
desc.mVector.end(),
nonPartitionedOutputs.begin() + outputOffset));
} else {
float* region = static_cast<float*>(ioMemories.getRegion(desc.mMemoryRegion));
+#ifdef VERBOSE
+ std::cout << "part output[" << outputIndex << "] = ";
+ dump(region, region + problemSize * problemSize);
+#endif
ASSERT_TRUE(std::equal(region,
region + problemSize * problemSize,
nonPartitionedOutputs.begin() + outputOffset));