C++程序  |  251行  |  9.91 KB

/*
 * Copyright (C) 2018 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#undef NDEBUG

#include "Callbacks.h"
#include "CompilationBuilder.h"
#include "Manager.h"
#include "ModelBuilder.h"
#include "NeuralNetworks.h"
#include "NeuralNetworksWrapper.h"
#include "SampleDriver.h"
#include "ValidateHal.h"

#include <algorithm>
#include <cassert>
#include <vector>

#include <gtest/gtest.h>

namespace android {

using CompilationBuilder = nn::CompilationBuilder;
using Device = nn::Device;
using DeviceManager = nn::DeviceManager;
using HidlModel = hardware::neuralnetworks::V1_1::Model;
using PreparedModelCallback = hardware::neuralnetworks::V1_0::implementation::PreparedModelCallback;
using Result = nn::wrapper::Result;
using SampleDriver = nn::sample_driver::SampleDriver;
using WrapperCompilation = nn::wrapper::Compilation;
using WrapperEvent = nn::wrapper::Event;
using WrapperExecution = nn::wrapper::Execution;
using WrapperModel = nn::wrapper::Model;
using WrapperOperandType = nn::wrapper::OperandType;
using WrapperType = nn::wrapper::Type;

namespace {

// Wraps an IPreparedModel to allow dummying up the execution status.
class TestPreparedModel : public IPreparedModel {
public:
    // If errorStatus is NONE, then execute behaves normally (and sends back
    // the actual execution status).  Otherwise, don't bother to execute, and
    // just send back errorStatus (as the execution status, not the launch
    // status).
    TestPreparedModel(sp<IPreparedModel> preparedModel, ErrorStatus errorStatus) :
            mPreparedModel(preparedModel), mErrorStatus(errorStatus) {}

    Return<ErrorStatus> execute(const Request& request,
                                const sp<IExecutionCallback>& callback) override {
        if (mErrorStatus == ErrorStatus::NONE) {
            return mPreparedModel->execute(request, callback);
        } else {
            callback->notify(mErrorStatus);
            return ErrorStatus::NONE;
        }
    }
private:
    sp<IPreparedModel> mPreparedModel;
    ErrorStatus mErrorStatus;
};

// Behaves like SampleDriver, except that it produces wrapped IPreparedModel.
class TestDriver : public SampleDriver {
public:
    // Allow dummying up the error status for execution of all models
    // prepared from this driver.  If errorStatus is NONE, then
    // execute behaves normally (and sends back the actual execution
    // status).  Otherwise, don't bother to execute, and just send
    // back errorStatus (as the execution status, not the launch
    // status).
    TestDriver(const std::string& name, ErrorStatus errorStatus) :
            SampleDriver(name.c_str()), mErrorStatus(errorStatus) { }

    Return<void> getCapabilities_1_1(getCapabilities_1_1_cb _hidl_cb) override {
        android::nn::initVLogMask();
        Capabilities capabilities =
                {.float32Performance = {.execTime = 0.75f, .powerUsage = 0.75f},
                 .quantized8Performance = {.execTime = 0.75f, .powerUsage = 0.75f},
                 .relaxedFloat32toFloat16Performance = {.execTime = 0.75f, .powerUsage = 0.75f}};
        _hidl_cb(ErrorStatus::NONE, capabilities);
        return Void();
    }

    Return<void> getSupportedOperations_1_1(const HidlModel& model,
                                            getSupportedOperations_cb cb) override {
        if (nn::validateModel(model)) {
            std::vector<bool> supported(model.operations.size(), true);
            cb(ErrorStatus::NONE, supported);
        } else {
            std::vector<bool> supported;
            cb(ErrorStatus::INVALID_ARGUMENT, supported);
        }
        return Void();
    }

    Return<ErrorStatus> prepareModel_1_1(
        const HidlModel& model,
        ExecutionPreference preference,
        const sp<IPreparedModelCallback>& actualCallback) override {

        sp<PreparedModelCallback> localCallback = new PreparedModelCallback;
        Return<ErrorStatus> prepareModelReturn =
                SampleDriver::prepareModel_1_1(model, preference, localCallback);
        if (!prepareModelReturn.isOkUnchecked()) {
            return prepareModelReturn;
        }
        if (prepareModelReturn != ErrorStatus::NONE) {
            actualCallback->notify(localCallback->getStatus(), localCallback->getPreparedModel());
            return prepareModelReturn;
        }
        localCallback->wait();
        if (localCallback->getStatus() != ErrorStatus::NONE) {
            actualCallback->notify(localCallback->getStatus(), localCallback->getPreparedModel());
        } else {
            actualCallback->notify(ErrorStatus::NONE,
                                   new TestPreparedModel(localCallback->getPreparedModel(),
                                                         mErrorStatus));
        }
        return prepareModelReturn;
    }

private:
    ErrorStatus mErrorStatus;
};

// This class adds some simple utilities on top of
// ::android::nn::wrapper::Compilation in order to provide access to
// certain features from CompilationBuilder that are not exposed by
// the base class.
class TestCompilation : public WrapperCompilation {
public:
    TestCompilation(const WrapperModel* model) : WrapperCompilation(model) {
        // We need to ensure that we use our TestDriver and do not
        // fall back to CPU.  (If we allow CPU fallback, then when our
        // TestDriver reports an execution failure, we'll re-execute
        // on CPU, and will not see the failure.)
        builder()->setPartitioning(DeviceManager::kPartitioningWithoutFallback);
    }

    // Allow dummying up the error status for all executions from this
    // compilation.  If errorStatus is NONE, then execute behaves
    // normally (and sends back the actual execution status).
    // Otherwise, don't bother to execute, and just send back
    // errorStatus (as the execution status, not the launch status).
    Result finish(const std::string& deviceName, ErrorStatus errorStatus) {
        std::vector<std::shared_ptr<Device>> devices;
        auto device = std::make_shared<Device>(deviceName, new TestDriver(deviceName, errorStatus));
        assert(device->initialize());
        devices.push_back(device);
        return static_cast<Result>(builder()->finish(devices));
    }

private:
    CompilationBuilder* builder() {
        return reinterpret_cast<CompilationBuilder*>(getHandle());
    }
};

class ExecutionTest :
            public ::testing::TestWithParam<std::tuple<ErrorStatus, Result>> {
public:
    ExecutionTest() :
            kName(toString(std::get<0>(GetParam()))),
            kForceErrorStatus(std::get<0>(GetParam())),
            kExpectResult(std::get<1>(GetParam())),
            mModel(makeModel()),
            mCompilation(&mModel) { }

protected:
    const std::string kName;

    // Allow dummying up the error status for execution.  If
    // kForceErrorStatus is NONE, then execution behaves normally (and
    // sends back the actual execution status).  Otherwise, don't
    // bother to execute, and just send back kForceErrorStatus (as the
    // execution status, not the launch status).
    const ErrorStatus kForceErrorStatus;

    // What result do we expect from the execution?  (The Result
    // equivalent of kForceErrorStatus.)
    const Result kExpectResult;

    WrapperModel mModel;
    TestCompilation mCompilation;

    void setInputOutput(WrapperExecution* execution) {
        ASSERT_EQ(execution->setInput(0, &mInputBuffer, sizeof(mInputBuffer)), Result::NO_ERROR);
        ASSERT_EQ(execution->setOutput(0, &mOutputBuffer, sizeof(mOutputBuffer)), Result::NO_ERROR);
    }

    float mInputBuffer  = 3.14;
    float mOutputBuffer = 0;
    const float kOutputBufferExpected = 3;

private:
    static WrapperModel makeModel() {
        static const WrapperOperandType tensorType(WrapperType::TENSOR_FLOAT32, { 1 });

        WrapperModel model;
        uint32_t input = model.addOperand(&tensorType);
        uint32_t output = model.addOperand(&tensorType);
        model.addOperation(ANEURALNETWORKS_FLOOR, { input }, { output });
        model.identifyInputsAndOutputs({ input }, { output } );
        assert(model.finish() == Result::NO_ERROR);

        return model;
    }
};

TEST_P(ExecutionTest, Wait) {
    SCOPED_TRACE(kName);
    ASSERT_EQ(mCompilation.finish(kName, kForceErrorStatus), Result::NO_ERROR);
    WrapperExecution execution(&mCompilation);
    ASSERT_NO_FATAL_FAILURE(setInputOutput(&execution));
    WrapperEvent event;
    ASSERT_EQ(execution.startCompute(&event), Result::NO_ERROR);
    ASSERT_EQ(event.wait(), kExpectResult);
    if (kExpectResult == Result::NO_ERROR) {
        ASSERT_EQ(mOutputBuffer, kOutputBufferExpected);
    }
}

INSTANTIATE_TEST_CASE_P(Flavor, ExecutionTest,
                        ::testing::Values(std::make_tuple(ErrorStatus::NONE,
                                                          Result::NO_ERROR),
                                          std::make_tuple(ErrorStatus::DEVICE_UNAVAILABLE,
                                                          Result::OP_FAILED),
                                          std::make_tuple(ErrorStatus::GENERAL_FAILURE,
                                                          Result::OP_FAILED),
                                          std::make_tuple(ErrorStatus::OUTPUT_INSUFFICIENT_SIZE,
                                                          Result::OP_FAILED),
                                          std::make_tuple(ErrorStatus::INVALID_ARGUMENT,
                                                          Result::BAD_DATA)));

}  // namespace
}  // namespace android