blob: db24b59c01223b6f544f587d091cebcfa94b8db4 [file] [log] [blame]
/*
* Copyright (C) 2020 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.
*/
#define LOG_TAG "SampleDriverFloatXNNPACK"
#include <android-base/logging.h>
#include <hidl/LegacySupport.h>
#include <hwbinder/IPCThreadState.h>
#include <xnnpack.h>
#include <algorithm>
#include <cstdint>
#include <limits>
#include <memory>
#include <string>
#include <thread>
#include <tuple>
#include <unordered_set>
#include <utility>
#include <vector>
#include "CpuExecutor.h"
#include "HalInterfaces.h"
#include "SampleDriverPartial.h"
#include "SampleDriverUtils.h"
#include "Utils.h"
#include "ValidateHal.h"
namespace android {
namespace nn {
namespace sample_driver {
namespace {
#define NN_DRIVER_RETURN_IF_ERROR(expr) \
do { \
V1_3::ErrorStatus _errorCode = (expr); \
if (_errorCode != V1_3::ErrorStatus::NONE) { \
return _errorCode; \
} \
} while (0)
const size_t kNumOfWorkerThreads = 1;
static const V1_2::Timing kNoTiming = {.timeOnDevice = UINT64_MAX, .timeInDriver = UINT64_MAX};
bool isScalarType(OperandType type) {
switch (type) {
case OperandType::FLOAT16:
case OperandType::FLOAT32:
case OperandType::INT32:
case OperandType::UINT32:
case OperandType::BOOL:
return true;
default:
return false;
}
}
void updateForArguments(const std::vector<uint32_t>& indexes,
const hardware::hidl_vec<V1_0::RequestArgument>& arguments,
const std::vector<RunTimePoolInfo>& requestPoolInfos,
RunTimeOperandInfo* operands) {
CHECK_EQ(indexes.size(), arguments.size());
for (size_t i = 0; i < indexes.size(); i++) {
const uint32_t operandIndex = indexes[i];
const V1_0::RequestArgument& from = arguments[i];
RunTimeOperandInfo& to = operands[operandIndex];
if (from.dimensions.size() > 0) {
// It's the responsibility of the caller to validate that
// from.dimensions only modifies the dimensions that were
// unspecified in the model. That's the case in SampleDriver.cpp
// with the call to validateRequest().
// TODO make sure that's the case for the default CPU path.
to.dimensions = from.dimensions;
}
if (from.hasNoValue) {
to.lifetime = Operand::LifeTime::NO_VALUE;
CHECK(to.buffer == nullptr);
to.length = 0;
} else {
auto poolIndex = from.location.poolIndex;
CHECK_LT(poolIndex, requestPoolInfos.size());
auto& r = requestPoolInfos[poolIndex];
to.buffer = r.getBuffer() + from.location.offset;
if (from.location.offset == 0 && from.location.length == 0) {
// Use the entire memory region.
to.length = r.getSize();
} else {
to.length = from.location.length;
}
}
}
}
std::vector<RunTimeOperandInfo> initializeRunTimeInfo(
const V1_3::Subgraph& subgraph, const std::vector<RunTimePoolInfo>& modelPoolInfos,
const hardware::hidl_vec<uint8_t>* mModelOperandValues) {
const size_t count = subgraph.operands.size();
std::vector<RunTimeOperandInfo> operands(count);
for (size_t i = 0; i < count; i++) {
const V1_3::Operand& from = subgraph.operands[i];
RunTimeOperandInfo& to = operands[i];
to.type = uncheckedConvert(from.type);
to.dimensions = from.dimensions;
to.scale = from.scale;
to.zeroPoint = from.zeroPoint;
to.length = from.location.length;
to.lifetime = uncheckedConvert(from.lifetime);
to.extraParams = uncheckedConvert(from.extraParams);
switch (from.lifetime) {
case V1_3::OperandLifeTime::TEMPORARY_VARIABLE:
to.buffer = nullptr;
to.numberOfUsesLeft = from.numberOfConsumers;
break;
case V1_3::OperandLifeTime::CONSTANT_COPY:
to.buffer = const_cast<uint8_t*>(&(*mModelOperandValues)[from.location.offset]);
to.numberOfUsesLeft = 0;
break;
case V1_3::OperandLifeTime::CONSTANT_REFERENCE: {
auto poolIndex = from.location.poolIndex;
CHECK_LT(poolIndex, modelPoolInfos.size());
auto& r = modelPoolInfos[poolIndex];
to.buffer = r.getBuffer() + from.location.offset;
to.numberOfUsesLeft = 0;
break;
}
case V1_3::OperandLifeTime::SUBGRAPH:
case V1_3::OperandLifeTime::SUBGRAPH_INPUT:
case V1_3::OperandLifeTime::SUBGRAPH_OUTPUT:
case V1_3::OperandLifeTime::NO_VALUE:
to.buffer = nullptr;
to.numberOfUsesLeft = 0;
break;
}
}
return operands;
}
} // namespace
class Subgraph {
public:
static Subgraph* Create(const hardware::hidl_vec<V1_3::Operation>& operations,
std::vector<RunTimeOperandInfo>& operands,
const std::vector<uint32_t>& inputIndexes,
const std::vector<uint32_t>& outputIndexes, pthreadpool_t threadpool,
bool useStaticBuffer = false) {
// Convert subgraph inputs and outputs to hash sets for faster lookup.
const std::unordered_set<uint32_t> inputs(inputIndexes.begin(), inputIndexes.end());
const std::unordered_set<uint32_t> outputs(outputIndexes.begin(), outputIndexes.end());
std::unordered_set<uint32_t> externals(outputs);
xnn_subgraph_t subgraphPtr = nullptr;
xnn_status status = xnn_create_subgraph(
/*external_value_ids=*/operands.size(), /*flags=*/0, &subgraphPtr);
if (status != xnn_status_success) {
LOG(ERROR) << "XNNPACK xnn_create_subgraph FAILED";
return nullptr;
}
// Smart pointer to automatically release subgraph on exit.
std::unique_ptr<xnn_subgraph, decltype(&xnn_delete_subgraph)> subgraph(
subgraphPtr, &xnn_delete_subgraph);
// Detect which tensors are used as inputs or outputs of any subgraph nodes.
// -1 denotes tensor not used in the subgraph.
std::vector<int> tensors(operands.size(), -1);
for (const auto& operation : operations) {
const std::vector<uint32_t>& ins = operation.inputs;
const std::vector<uint32_t>& outs = operation.outputs;
switch (operation.type) {
case V1_3::OperationType::MEAN:
case V1_3::OperationType::PAD:
case V1_3::OperationType::RESHAPE:
case V1_3::OperationType::RESIZE_BILINEAR:
// Ignore the second input (axes, static padding, or new shape),
// because it is represented as parameters of the XNNPACK operator
// rather than extra input.
{
const int t = ins[0];
tensors[t] = t;
}
break;
default:
// All other operators: process all inputs
for (size_t k = 0; k < ins.size(); k++) {
if (isScalarType(operands[ins[k]].type)) continue;
const int t = ins[k];
tensors[t] = t;
}
}
for (size_t k = 0; k < outs.size(); k++) {
if (isScalarType(operands[outs[k]].type)) continue;
const int t = outs[k];
tensors[t] = t;
}
}
// XNNPACK Value IDs for NNAPI Operands
std::vector<uint32_t> xnnpackTensors(operands.size());
for (int t : tensors) {
if (t < 0) continue;
if (operands[tensors[t]].type != OperandType::TENSOR_FLOAT32) {
LOG(ERROR) << "XNNPACK only support FLOAT32 tensors";
return nullptr;
}
uint32_t flags = 0;
const void* data = nullptr;
if (operands[tensors[t]].lifetime == Operand::LifeTime::CONSTANT_COPY ||
operands[tensors[t]].lifetime == Operand::LifeTime::CONSTANT_REFERENCE ||
operands[tensors[t]].lifetime == Operand::LifeTime::POINTER) {
data = operands[tensors[t]].buffer;
}
if (inputs.count(t) != 0) {
flags |= XNN_VALUE_FLAG_EXTERNAL_INPUT;
CHECK(data == nullptr);
VLOG(DRIVER) << "found input tensor, add to external";
externals.insert(static_cast<uint32_t>(t));
}
if (outputs.count(t) != 0) {
flags |= XNN_VALUE_FLAG_EXTERNAL_OUTPUT;
}
std::vector<size_t> dims(operands[tensors[t]].dimensions.size());
for (size_t i = 0; i < dims.size(); i++) {
dims[i] = operands[tensors[t]].dimensions[i];
}
const xnn_status status = xnn_define_tensor_value(
subgraph.get(), xnn_datatype_fp32, dims.size(), dims.data(), data,
static_cast<uint32_t>(t), flags, &xnnpackTensors[t]);
if (status != xnn_status_success) {
LOG(ERROR) << "XNNPACK xnn_define_tensor_value failed";
return nullptr;
}
}
// Create XNNPACK nodes for NNAPI Operations
for (const auto& operation : operations) {
if (VisitNode(subgraph.get(), operation, operands.data(), xnnpackTensors) !=
V1_3::ErrorStatus::NONE) {
LOG(ERROR) << "XNNPACK add op failed";
return nullptr;
}
}
xnn_runtime_t runtimePtr = nullptr;
status = xnn_create_runtime_v2(subgraph.get(), threadpool, /*flags=*/0, &runtimePtr);
if (status != xnn_status_success) {
LOG(ERROR) << "XNNPACK xnn_create_runtime_v2 FAILED";
return nullptr;
}
return new Subgraph(runtimePtr, std::move(externals), useStaticBuffer);
}
V1_3::ErrorStatus Prepare() { return V1_3::ErrorStatus::NONE; }
V1_3::ErrorStatus Invoke(RunTimeOperandInfo* operands) {
VLOG(DRIVER) << "Subgraph::Invoke() start";
if (!mUseStaticBuffer || mFirstRun) {
VLOG(DRIVER) << "Setup buffer for Subgraph";
std::vector<xnn_external_value> externalValues;
for (uint32_t t : mExternals) {
xnn_external_value value = {.id = 0, .data = nullptr};
value.id = t;
value.data = operands[t].buffer;
externalValues.push_back(value);
}
const xnn_status status =
xnn_setup_runtime(mRuntime.get(), externalValues.size(), externalValues.data());
if (status != xnn_status_success) {
LOG(ERROR) << "XNNPACK xnn_setup_runtime FAILED";
return V1_3::ErrorStatus::GENERAL_FAILURE;
}
mFirstRun = false;
}
VLOG(DRIVER) << "Subgraph::Invoke() finished xnn_setup_runtime";
const xnn_status status = xnn_invoke_runtime(mRuntime.get());
if (status != xnn_status_success) {
LOG(ERROR) << "XNNPACK xnn_invoke_runtime FAILED";
return V1_3::ErrorStatus::GENERAL_FAILURE;
}
return V1_3::ErrorStatus::NONE;
}
static V1_3::ErrorStatus CalculatePadding(int padding, uint32_t* flags) {
switch (padding) {
case ANEURALNETWORKS_PADDING_SAME:
*flags = XNN_FLAG_TENSORFLOW_SAME_PADDING;
return V1_3::ErrorStatus::NONE;
case ANEURALNETWORKS_PADDING_VALID:
*flags = 0;
return V1_3::ErrorStatus::NONE;
default:
LOG(ERROR) << "invalid padding mode";
return V1_3::ErrorStatus::INVALID_ARGUMENT;
}
}
static V1_3::ErrorStatus ConvertActivationToOutputRange(int activation, float* outputMin,
float* outputMax) {
switch (activation) {
case ANEURALNETWORKS_FUSED_NONE:
*outputMin = -std::numeric_limits<float>::infinity();
*outputMax = +std::numeric_limits<float>::infinity();
return V1_3::ErrorStatus::NONE;
case ANEURALNETWORKS_FUSED_RELU:
*outputMin = 0.0f;
*outputMax = +std::numeric_limits<float>::infinity();
return V1_3::ErrorStatus::NONE;
case ANEURALNETWORKS_FUSED_RELU1:
*outputMin = -1.0f;
*outputMax = +1.0f;
return V1_3::ErrorStatus::NONE;
case ANEURALNETWORKS_FUSED_RELU6:
*outputMin = 0.0f;
*outputMax = 6.0f;
return V1_3::ErrorStatus::NONE;
default:
return V1_3::ErrorStatus::INVALID_ARGUMENT;
}
}
static V1_3::ErrorStatus CheckConvolutionParams(int32_t stride_width, int32_t stride_height,
int32_t dilation_width_factor,
int32_t dilation_height_factor) {
if (stride_width <= 0) {
return V1_3::ErrorStatus::INVALID_ARGUMENT;
}
if (stride_height <= 0) {
return V1_3::ErrorStatus::INVALID_ARGUMENT;
}
if (dilation_width_factor <= 0) {
return V1_3::ErrorStatus::INVALID_ARGUMENT;
}
if (dilation_height_factor <= 0) {
return V1_3::ErrorStatus::INVALID_ARGUMENT;
}
return V1_3::ErrorStatus::NONE;
}
static V1_3::ErrorStatus CheckDepthwiseConvolutionParams(
int32_t stride_width, int32_t stride_height, int32_t dilation_width_factor,
int32_t dilation_height_factor, int32_t depth_multiplier, uint32_t output_channels) {
if (stride_width <= 0) {
return V1_3::ErrorStatus::INVALID_ARGUMENT;
}
if (stride_height <= 0) {
return V1_3::ErrorStatus::INVALID_ARGUMENT;
}
if (depth_multiplier <= 0) {
return V1_3::ErrorStatus::INVALID_ARGUMENT;
}
if (output_channels % depth_multiplier != 0) {
return V1_3::ErrorStatus::INVALID_ARGUMENT;
}
if (dilation_width_factor <= 0) {
return V1_3::ErrorStatus::INVALID_ARGUMENT;
}
if (dilation_height_factor <= 0) {
return V1_3::ErrorStatus::INVALID_ARGUMENT;
}
return V1_3::ErrorStatus::NONE;
}
static V1_3::ErrorStatus CheckPoolingParams(int32_t stride_width, int32_t stride_height,
int32_t filter_width, int32_t filter_height) {
if (stride_width <= 0) {
return V1_3::ErrorStatus::INVALID_ARGUMENT;
}
if (stride_height <= 0) {
return V1_3::ErrorStatus::INVALID_ARGUMENT;
}
if (filter_width <= 0) {
return V1_3::ErrorStatus::INVALID_ARGUMENT;
}
if (filter_height <= 0) {
return V1_3::ErrorStatus::INVALID_ARGUMENT;
}
if (filter_width == 1 && filter_height == 1 && std::max(stride_width, stride_height) > 1) {
return V1_3::ErrorStatus::INVALID_ARGUMENT;
}
return V1_3::ErrorStatus::NONE;
}
static V1_3::ErrorStatus CheckNumInputsAndOutputs(const V1_3::Operation& operation,
uint32_t expected_num_inputs,
uint32_t expected_num_outputs) {
if (operation.inputs.size() != expected_num_inputs) {
return V1_3::ErrorStatus::INVALID_ARGUMENT;
}
if (operation.outputs.size() != expected_num_outputs) {
return V1_3::ErrorStatus::INVALID_ARGUMENT;
}
return V1_3::ErrorStatus::NONE;
}
static V1_3::ErrorStatus CheckTensorType(OperandType tensor_type, OperandType expected_type) {
if (tensor_type != expected_type) {
return V1_3::ErrorStatus::INVALID_ARGUMENT;
}
return V1_3::ErrorStatus::NONE;
}
static V1_3::ErrorStatus CheckTensorFloatType(OperandType tensor_type) {
if (tensor_type != OperandType::TENSOR_FLOAT32) {
return V1_3::ErrorStatus::INVALID_ARGUMENT;
}
return V1_3::ErrorStatus::NONE;
}
static V1_3::ErrorStatus CheckTensorShape(std::vector<uint32_t>& dimensions,
uint32_t min_num_dims, uint32_t max_num_dims) {
if (min_num_dims == max_num_dims) {
if (dimensions.size() != min_num_dims) {
return V1_3::ErrorStatus::INVALID_ARGUMENT;
}
} else {
if (dimensions.size() < min_num_dims || dimensions.size() > max_num_dims) {
return V1_3::ErrorStatus::INVALID_ARGUMENT;
}
}
for (size_t i = 0; i < dimensions.size(); i++) {
if (dimensions[i] <= 0) {
return V1_3::ErrorStatus::INVALID_ARGUMENT;
}
}
return V1_3::ErrorStatus::NONE;
}
static V1_3::ErrorStatus CheckTensorShape(std::vector<uint32_t>& dimensions,
int expected_num_dims) {
return CheckTensorShape(dimensions, expected_num_dims, expected_num_dims);
}
static V1_3::ErrorStatus CheckSlopeTensorShape(std::vector<uint32_t>& dimensions) {
if (dimensions.size() < 1) {
return V1_3::ErrorStatus::INVALID_ARGUMENT;
}
// Validate that all non-channel dimensions (if any) are exactly 1.
for (size_t i = 0; i < dimensions.size() - 1; i++) {
if (dimensions[i] != 1) {
return V1_3::ErrorStatus::INVALID_ARGUMENT;
}
}
return V1_3::ErrorStatus::NONE;
}
static V1_3::ErrorStatus CheckAxesTensorShape(std::vector<uint32_t>& dimensions) {
if (dimensions.size() != 1) {
return V1_3::ErrorStatus::INVALID_ARGUMENT;
}
return V1_3::ErrorStatus::NONE;
}
static V1_3::ErrorStatus CheckShapeTensorShape(std::vector<uint32_t>& dimensions) {
if (dimensions.size() != 1) {
return V1_3::ErrorStatus::INVALID_ARGUMENT;
}
return V1_3::ErrorStatus::NONE;
}
static V1_3::ErrorStatus CheckTensorStaticAllocation(Operand::LifeTime lifetime) {
if (lifetime != Operand::LifeTime::CONSTANT_COPY &&
lifetime != Operand::LifeTime::CONSTANT_REFERENCE &&
lifetime != Operand::LifeTime::POINTER) {
VLOG(DRIVER) << "CheckTensorStaticAllocation: " << toString(convertToV1_3(lifetime));
return V1_3::ErrorStatus::INVALID_ARGUMENT;
}
return V1_3::ErrorStatus::NONE;
}
static V1_3::ErrorStatus VisitNode(xnn_subgraph_t subgraph, const V1_3::Operation& operation,
RunTimeOperandInfo* operands,
const std::vector<uint32_t>& xnnpackTensors) {
switch (operation.type) {
case V1_3::OperationType::ABS:
return VisitAbsNode(subgraph, operation, operands, xnnpackTensors);
case V1_3::OperationType::ADD:
return VisitAddNode(subgraph, operation, operands, xnnpackTensors);
case V1_3::OperationType::AVERAGE_POOL_2D:
return VisitAveragePool2DNode(subgraph, operation, operands, xnnpackTensors);
case V1_3::OperationType::CONV_2D:
return VisitConv2DNode(subgraph, operation, operands, xnnpackTensors);
case V1_3::OperationType::DEPTHWISE_CONV_2D:
return VisitDepthwiseConv2DNode(subgraph, operation, operands, xnnpackTensors);
case V1_3::OperationType::DIV:
return VisitDivNode(subgraph, operation, operands, xnnpackTensors);
case V1_3::OperationType::FLOOR:
return VisitFloorNode(subgraph, operation, operands, xnnpackTensors);
case V1_3::OperationType::FULLY_CONNECTED:
return VisitFullyConnectedNode(subgraph, operation, operands, xnnpackTensors);
case V1_3::OperationType::HARD_SWISH:
return VisitHardSwishNode(subgraph, operation, operands, xnnpackTensors);
case V1_3::OperationType::LOGISTIC:
return VisitLogisticNode(subgraph, operation, operands, xnnpackTensors);
case V1_3::OperationType::MAX_POOL_2D:
return VisitMaxPool2DNode(subgraph, operation, operands, xnnpackTensors);
case V1_3::OperationType::MAXIMUM:
return VisitMaximumNode(subgraph, operation, operands, xnnpackTensors);
case V1_3::OperationType::MEAN:
return VisitMeanNode(subgraph, operation, operands, xnnpackTensors);
case V1_3::OperationType::MINIMUM:
return VisitMinimumNode(subgraph, operation, operands, xnnpackTensors);
case V1_3::OperationType::MUL:
return VisitMulNode(subgraph, operation, operands, xnnpackTensors);
case V1_3::OperationType::NEG:
return VisitNegNode(subgraph, operation, operands, xnnpackTensors);
case V1_3::OperationType::PAD:
return VisitPadNode(subgraph, operation, operands, 0.0f, xnnpackTensors);
case V1_3::OperationType::PAD_V2:
return VisitPadV2Node(subgraph, operation, operands, xnnpackTensors);
case V1_3::OperationType::RESHAPE:
return VisitReshapeNode(subgraph, operation, operands, xnnpackTensors);
case V1_3::OperationType::RESIZE_BILINEAR:
return VisitResizeBilinearNode(subgraph, operation, operands, xnnpackTensors);
case V1_3::OperationType::PRELU:
return VisitPreluNode(subgraph, operation, operands, xnnpackTensors);
case V1_3::OperationType::RELU:
return VisitReluNode(subgraph, operation, operands, 0.0f,
std::numeric_limits<float>::infinity(), xnnpackTensors);
case V1_3::OperationType::RELU1:
return VisitReluNode(subgraph, operation, operands, -1.0f, 1.0f, xnnpackTensors);
case V1_3::OperationType::RELU6:
return VisitReluNode(subgraph, operation, operands, 0.0f, 6.0f, xnnpackTensors);
case V1_3::OperationType::SQRT:
return VisitSqrtNode(subgraph, operation, operands, xnnpackTensors);
case V1_3::OperationType::SUB:
return VisitSubNode(subgraph, operation, operands, xnnpackTensors);
case V1_3::OperationType::SOFTMAX:
return VisitSoftmaxNode(subgraph, operation, operands, xnnpackTensors);
default:
return V1_3::ErrorStatus::INVALID_ARGUMENT;
}
}
static V1_3::ErrorStatus VisitAbsNode(xnn_subgraph_t subgraph, const V1_3::Operation& operation,
RunTimeOperandInfo* operands,
const std::vector<uint32_t>& xnnpackTensors) {
const hardware::hidl_vec<uint32_t>& ins = operation.inputs;
const hardware::hidl_vec<uint32_t>& outs = operation.outputs;
NN_DRIVER_RETURN_IF_ERROR(CheckTensorFloatType(operands[ins[0]].type));
NN_DRIVER_RETURN_IF_ERROR(CheckTensorFloatType(operands[outs[0]].type));
if (subgraph != nullptr) {
const xnn_status status =
xnn_define_abs(subgraph, /*input_id=*/xnnpackTensors[ins[0]],
/*output_id=*/xnnpackTensors[outs[0]], /*flags=*/0);
if (status != xnn_status_success) {
LOG(ERROR) << "XNNPACK xnn_define_abs FAILED";
return V1_3::ErrorStatus::GENERAL_FAILURE;
}
}
return V1_3::ErrorStatus::NONE;
}
static V1_3::ErrorStatus VisitAddNode(xnn_subgraph_t subgraph, const V1_3::Operation& operation,
RunTimeOperandInfo* operands,
const std::vector<uint32_t>& xnnpackTensors) {
const hardware::hidl_vec<uint32_t>& ins = operation.inputs;
const hardware::hidl_vec<uint32_t>& outs = operation.outputs;
NN_DRIVER_RETURN_IF_ERROR(CheckTensorFloatType(operands[ins[0]].type));
NN_DRIVER_RETURN_IF_ERROR(CheckTensorFloatType(operands[ins[1]].type));
NN_DRIVER_RETURN_IF_ERROR(CheckTensorStaticAllocation(operands[ins[2]].lifetime));
NN_DRIVER_RETURN_IF_ERROR(CheckTensorFloatType(operands[outs[0]].type));
float outputMin = -std::numeric_limits<float>::infinity();
float outputMax = +std::numeric_limits<float>::infinity();
int activation = getScalarData<int32_t>(operands[ins[2]]);
NN_DRIVER_RETURN_IF_ERROR(
ConvertActivationToOutputRange(activation, &outputMin, &outputMax));
if (subgraph != nullptr) {
const xnn_status status =
xnn_define_add2(subgraph, outputMin, outputMax,
/*input1_id=*/xnnpackTensors[ins[0]],
/*input2_id=*/xnnpackTensors[ins[1]],
/*output_id=*/xnnpackTensors[outs[0]], /*flags=*/0);
if (status != xnn_status_success) {
LOG(ERROR) << "XNNPACK xnn_define_add2 FAILED";
return V1_3::ErrorStatus::GENERAL_FAILURE;
}
}
return V1_3::ErrorStatus::NONE;
}
static V1_3::ErrorStatus VisitAveragePool2DNode(xnn_subgraph_t subgraph,
const V1_3::Operation& operation,
RunTimeOperandInfo* operands,
const std::vector<uint32_t>& xnnpackTensors) {
const hardware::hidl_vec<uint32_t>& ins = operation.inputs;
const hardware::hidl_vec<uint32_t>& outs = operation.outputs;
NN_DRIVER_RETURN_IF_ERROR(CheckTensorFloatType(operands[ins[0]].type));
NN_DRIVER_RETURN_IF_ERROR(CheckTensorFloatType(operands[outs[0]].type));
// Make sure all scalar params are constant.
for (uint32_t i = 1; i < ins.size(); i++) {
NN_DRIVER_RETURN_IF_ERROR(CheckTensorStaticAllocation(operands[ins[i]].lifetime));
}
bool use_nchw = false;
if (ins.size() == 8) {
use_nchw = getScalarData<bool>(operands[ins[7]]);
}
if (ins.size() == 11) {
use_nchw = getScalarData<bool>(operands[ins[10]]);
}
if (use_nchw) {
VLOG(DRIVER) << "XNNPACK VisitAveragePool2DNode FAILED: only NHWC layout is supported";
return V1_3::ErrorStatus::INVALID_ARGUMENT;
}
int32_t stride_width, stride_height, filter_width, filter_height, activation;
uint32_t input_padding_top = 0;
uint32_t input_padding_right = 0;
uint32_t input_padding_bottom = 0;
uint32_t input_padding_left = 0;
uint32_t flags = 0;
if (ins.size() >= 10) {
// Explicit padding
input_padding_left = static_cast<uint32_t>(getScalarData<int32_t>(operands[ins[1]]));
input_padding_right = static_cast<uint32_t>(getScalarData<int32_t>(operands[ins[2]]));
input_padding_top = static_cast<uint32_t>(getScalarData<int32_t>(operands[ins[3]]));
input_padding_bottom = static_cast<uint32_t>(getScalarData<int32_t>(operands[ins[4]]));
stride_width = getScalarData<int32_t>(operands[ins[5]]);
stride_height = getScalarData<int32_t>(operands[ins[6]]);
filter_width = getScalarData<int32_t>(operands[ins[7]]);
filter_height = getScalarData<int32_t>(operands[ins[8]]);
activation = getScalarData<int32_t>(operands[ins[9]]);
} else {
// Implicit padding
int padding_implicit = getScalarData<int32_t>(operands[ins[1]]);
NN_DRIVER_RETURN_IF_ERROR(CalculatePadding(padding_implicit, &flags));
stride_width = getScalarData<int32_t>(operands[ins[2]]);
stride_height = getScalarData<int32_t>(operands[ins[3]]);
filter_width = getScalarData<int32_t>(operands[ins[4]]);
filter_height = getScalarData<int32_t>(operands[ins[5]]);
activation = getScalarData<int32_t>(operands[ins[6]]);
}
NN_DRIVER_RETURN_IF_ERROR(
CheckPoolingParams(stride_width, stride_height, filter_width, filter_height));
float outputMin = -std::numeric_limits<float>::infinity();
float outputMax = +std::numeric_limits<float>::infinity();
NN_DRIVER_RETURN_IF_ERROR(
ConvertActivationToOutputRange(activation, &outputMin, &outputMax));
if (subgraph != nullptr) {
xnn_status status = xnn_status_success;
if (filter_width == 1 && filter_height == 1) {
status = xnn_define_clamp(subgraph, outputMin, outputMax,
/*input_id=*/xnnpackTensors[ins[0]],
/*output_id=*/xnnpackTensors[outs[0]], /*flags=*/0);
} else {
status = xnn_define_average_pooling_2d(
subgraph, input_padding_top, input_padding_right, input_padding_bottom,
input_padding_left, static_cast<uint32_t>(filter_height),
static_cast<uint32_t>(filter_width), static_cast<uint32_t>(stride_height),
static_cast<uint32_t>(stride_width), outputMin, outputMax,
/*input_id=*/xnnpackTensors[ins[0]],
/*output_id=*/xnnpackTensors[outs[0]], flags);
}
if (status != xnn_status_success) {
LOG(ERROR) << "XNNPACK xnn_define_average_pooling_2d FAILED";
return V1_3::ErrorStatus::GENERAL_FAILURE;
}
}
return V1_3::ErrorStatus::NONE;
}
static V1_3::ErrorStatus VisitConv2DNode(xnn_subgraph_t subgraph,
const V1_3::Operation& operation,
RunTimeOperandInfo* operands,
const std::vector<uint32_t>& xnnpackTensors) {
const hardware::hidl_vec<uint32_t>& ins = operation.inputs;
const hardware::hidl_vec<uint32_t>& outs = operation.outputs;
NN_DRIVER_RETURN_IF_ERROR(CheckTensorFloatType(operands[ins[0]].type));
NN_DRIVER_RETURN_IF_ERROR(CheckTensorFloatType(operands[ins[1]].type));
NN_DRIVER_RETURN_IF_ERROR(CheckTensorStaticAllocation(operands[ins[1]].lifetime));
NN_DRIVER_RETURN_IF_ERROR(CheckTensorFloatType(operands[ins[2]].type));
NN_DRIVER_RETURN_IF_ERROR(CheckTensorStaticAllocation(operands[ins[2]].lifetime));
NN_DRIVER_RETURN_IF_ERROR(CheckTensorFloatType(operands[outs[0]].type));
// Make sure all scalar params are constant.
for (uint32_t i = 3; i < ins.size(); i++) {
NN_DRIVER_RETURN_IF_ERROR(CheckTensorStaticAllocation(operands[ins[i]].lifetime));
}
bool use_nchw = false;
if (ins.size() >= 8 && operands[ins[7]].type == OperandType::BOOL) {
use_nchw = getScalarData<bool>(operands[ins[7]]);
}
if (ins.size() >= 11) {
use_nchw = getScalarData<bool>(operands[ins[10]]);
}
if (use_nchw) {
VLOG(DRIVER) << "XNNPACK VisitConv2DNode FAILED: only NHWC layout is supported";
return V1_3::ErrorStatus::INVALID_ARGUMENT;
}
int32_t stride_width, stride_height, activation;
int32_t dilation_width_factor = 1;
int32_t dilation_height_factor = 1;
uint32_t input_padding_top = 0;
uint32_t input_padding_right = 0;
uint32_t input_padding_bottom = 0;
uint32_t input_padding_left = 0;
uint32_t flags = 0;
if (ins.size() >= 10 && operands[ins[7]].type != OperandType::BOOL) {
// Explicit padding
input_padding_left = static_cast<uint32_t>(getScalarData<int32_t>(operands[ins[3]]));
input_padding_right = static_cast<uint32_t>(getScalarData<int32_t>(operands[ins[4]]));
input_padding_top = static_cast<uint32_t>(getScalarData<int32_t>(operands[ins[5]]));
input_padding_bottom = static_cast<uint32_t>(getScalarData<int32_t>(operands[ins[6]]));
stride_width = getScalarData<int32_t>(operands[ins[7]]);
stride_height = getScalarData<int32_t>(operands[ins[8]]);
activation = getScalarData<int32_t>(operands[ins[9]]);
if (ins.size() == 13) {
dilation_width_factor = getScalarData<int32_t>(operands[ins[11]]);
dilation_height_factor = getScalarData<int32_t>(operands[ins[12]]);
}
} else {
// Implicit padding
int padding_implicit = getScalarData<int32_t>(operands[ins[3]]);
NN_DRIVER_RETURN_IF_ERROR(CalculatePadding(padding_implicit, &flags));
stride_width = getScalarData<int32_t>(operands[ins[4]]);
stride_height = getScalarData<int32_t>(operands[ins[5]]);
activation = getScalarData<int32_t>(operands[ins[6]]);
if (ins.size() == 10) {
dilation_width_factor = getScalarData<int32_t>(operands[ins[8]]);
dilation_height_factor = getScalarData<int32_t>(operands[ins[9]]);
}
}
NN_DRIVER_RETURN_IF_ERROR(CheckConvolutionParams(
stride_width, stride_height, dilation_width_factor, dilation_height_factor));
float outputMin = -std::numeric_limits<float>::infinity();
float outputMax = +std::numeric_limits<float>::infinity();
NN_DRIVER_RETURN_IF_ERROR(
ConvertActivationToOutputRange(activation, &outputMin, &outputMax));
const RunTimeOperandInfo& filter = operands[ins[1]];
const uint32_t output_channels = filter.dimensions[0];
const uint32_t kernel_height = filter.dimensions[1];
const uint32_t kernel_width = filter.dimensions[2];
const uint32_t input_channels = filter.dimensions[3];
if (subgraph != nullptr) {
const xnn_status status = xnn_define_convolution_2d(
subgraph, input_padding_top, input_padding_right, input_padding_bottom,
input_padding_left, static_cast<uint32_t>(kernel_height),
static_cast<uint32_t>(kernel_width), static_cast<uint32_t>(stride_height),
static_cast<uint32_t>(stride_width),
static_cast<uint32_t>(dilation_height_factor),
static_cast<uint32_t>(dilation_width_factor),
/*groups=*/1, static_cast<size_t>(input_channels),
static_cast<size_t>(output_channels), outputMin, outputMax,
/*input_id=*/xnnpackTensors[ins[0]],
/*filter_id=*/xnnpackTensors[ins[1]],
/*bias_id=*/xnnpackTensors[ins[2]],
/*output_id=*/xnnpackTensors[outs[0]], flags);
if (status != xnn_status_success) {
LOG(ERROR) << "XNNPACK xnn_define_convolution_2d FAILED";
return V1_3::ErrorStatus::GENERAL_FAILURE;
}
}
return V1_3::ErrorStatus::NONE;
}
static V1_3::ErrorStatus VisitDepthwiseConv2DNode(xnn_subgraph_t subgraph,
const V1_3::Operation& operation,
RunTimeOperandInfo* operands,
const std::vector<uint32_t>& xnnpackTensors) {
const hardware::hidl_vec<uint32_t>& ins = operation.inputs;
const hardware::hidl_vec<uint32_t>& outs = operation.outputs;
NN_DRIVER_RETURN_IF_ERROR(CheckTensorFloatType(operands[ins[0]].type));
NN_DRIVER_RETURN_IF_ERROR(CheckTensorFloatType(operands[ins[1]].type));
NN_DRIVER_RETURN_IF_ERROR(CheckTensorStaticAllocation(operands[ins[1]].lifetime));
NN_DRIVER_RETURN_IF_ERROR(CheckTensorFloatType(operands[ins[2]].type));
NN_DRIVER_RETURN_IF_ERROR(CheckTensorStaticAllocation(operands[ins[2]].lifetime));
NN_DRIVER_RETURN_IF_ERROR(CheckTensorFloatType(operands[outs[0]].type));
// Make sure all scalar params are constant.
for (uint32_t i = 3; i < ins.size(); i++) {
NN_DRIVER_RETURN_IF_ERROR(CheckTensorStaticAllocation(operands[ins[i]].lifetime));
}
bool use_nchw = false;
if (ins.size() >= 9 && operands[ins[8]].type == OperandType::BOOL) {
use_nchw = getScalarData<bool>(operands[ins[8]]);
}
if (ins.size() >= 12) {
use_nchw = getScalarData<bool>(operands[ins[11]]);
}
if (use_nchw) {
VLOG(DRIVER)
<< "XNNPACK VisitDepthwiseConv2DNode FAILED: only NHWC layout is supported";
return V1_3::ErrorStatus::INVALID_ARGUMENT;
}
int32_t stride_width, stride_height, depth_multiplier, activation;
int32_t dilation_width_factor = 1;
int32_t dilation_height_factor = 1;
uint32_t input_padding_top = 0;
uint32_t input_padding_right = 0;
uint32_t input_padding_bottom = 0;
uint32_t input_padding_left = 0;
uint32_t flags = 0;
if (ins.size() >= 11 && operands[ins[8]].type != OperandType::BOOL) {
// Explicit padding
input_padding_left = static_cast<uint32_t>(getScalarData<int32_t>(operands[ins[3]]));
input_padding_right = static_cast<uint32_t>(getScalarData<int32_t>(operands[ins[4]]));
input_padding_top = static_cast<uint32_t>(getScalarData<int32_t>(operands[ins[5]]));
input_padding_bottom = static_cast<uint32_t>(getScalarData<int32_t>(operands[ins[6]]));
stride_width = getScalarData<int32_t>(operands[ins[7]]);
stride_height = getScalarData<int32_t>(operands[ins[8]]);
depth_multiplier = getScalarData<int32_t>(operands[ins[9]]);
activation = getScalarData<int32_t>(operands[ins[10]]);
if (ins.size() == 14) {
dilation_width_factor = getScalarData<int32_t>(operands[ins[12]]);
dilation_height_factor = getScalarData<int32_t>(operands[ins[13]]);
}
} else {
// Implicit padding
int padding_implicit = getScalarData<int32_t>(operands[ins[3]]);
NN_DRIVER_RETURN_IF_ERROR(CalculatePadding(padding_implicit, &flags));
stride_width = getScalarData<int32_t>(operands[ins[4]]);
stride_height = getScalarData<int32_t>(operands[ins[5]]);
depth_multiplier = getScalarData<int32_t>(operands[ins[6]]);
activation = getScalarData<int32_t>(operands[ins[7]]);
if (ins.size() == 11) {
dilation_width_factor = getScalarData<int32_t>(operands[ins[9]]);
dilation_height_factor = getScalarData<int32_t>(operands[ins[10]]);
}
}
float outputMin = -std::numeric_limits<float>::infinity();
float outputMax = +std::numeric_limits<float>::infinity();
NN_DRIVER_RETURN_IF_ERROR(
ConvertActivationToOutputRange(activation, &outputMin, &outputMax));
const RunTimeOperandInfo& filter = operands[ins[1]];
const uint32_t output_channels = filter.dimensions[3];
const uint32_t kernel_height = filter.dimensions[1];
const uint32_t kernel_width = filter.dimensions[2];
NN_DRIVER_RETURN_IF_ERROR(CheckDepthwiseConvolutionParams(
stride_width, stride_height, dilation_width_factor, dilation_height_factor,
depth_multiplier, output_channels));
if (subgraph != nullptr) {
const xnn_status status = xnn_define_depthwise_convolution_2d(
subgraph, input_padding_top, input_padding_right, input_padding_bottom,
input_padding_left, static_cast<uint32_t>(kernel_height),
static_cast<uint32_t>(kernel_width), static_cast<uint32_t>(stride_height),
static_cast<uint32_t>(stride_width),
static_cast<uint32_t>(dilation_height_factor),
static_cast<uint32_t>(dilation_width_factor),
static_cast<uint32_t>(depth_multiplier),
/*input_channels=*/
static_cast<uint32_t>(output_channels / depth_multiplier), outputMin, outputMax,
/*input_id=*/xnnpackTensors[ins[0]],
/*filter_id=*/xnnpackTensors[ins[1]],
/*bias_id=*/xnnpackTensors[ins[2]],
/*output_id=*/xnnpackTensors[outs[0]], flags);
if (status != xnn_status_success) {
LOG(ERROR) << "XNNPACK xnn_define_depthwise_convolution_2d FAILED";
return V1_3::ErrorStatus::GENERAL_FAILURE;
}
}
return V1_3::ErrorStatus::NONE;
}
static V1_3::ErrorStatus VisitDivNode(xnn_subgraph_t subgraph, const V1_3::Operation& operation,
RunTimeOperandInfo* operands,
const std::vector<uint32_t>& xnnpackTensors) {
const hardware::hidl_vec<uint32_t>& ins = operation.inputs;
const hardware::hidl_vec<uint32_t>& outs = operation.outputs;
NN_DRIVER_RETURN_IF_ERROR(CheckTensorFloatType(operands[ins[0]].type));
NN_DRIVER_RETURN_IF_ERROR(CheckTensorFloatType(operands[ins[1]].type));
NN_DRIVER_RETURN_IF_ERROR(CheckTensorStaticAllocation(operands[ins[2]].lifetime));
NN_DRIVER_RETURN_IF_ERROR(CheckTensorFloatType(operands[outs[0]].type));
float outputMin = -std::numeric_limits<float>::infinity();
float outputMax = +std::numeric_limits<float>::infinity();
int activation = getScalarData<int32_t>(operands[ins[2]]);
NN_DRIVER_RETURN_IF_ERROR(
ConvertActivationToOutputRange(activation, &outputMin, &outputMax));
if (subgraph != nullptr) {
const xnn_status status =
xnn_define_divide(subgraph, outputMin, outputMax,
/*input1_id=*/xnnpackTensors[ins[0]],
/*input2_id=*/xnnpackTensors[ins[1]],
/*output_id=*/xnnpackTensors[outs[0]], /*flags=*/0);
if (status != xnn_status_success) {
LOG(ERROR) << "XNNPACK xnn_define_divide FAILED";
return V1_3::ErrorStatus::GENERAL_FAILURE;
}
}
return V1_3::ErrorStatus::NONE;
}
static V1_3::ErrorStatus VisitFullyConnectedNode(xnn_subgraph_t subgraph,
const V1_3::Operation& operation,
RunTimeOperandInfo* operands,
const std::vector<uint32_t>& xnnpackTensors) {
const hardware::hidl_vec<uint32_t>& ins = operation.inputs;
const hardware::hidl_vec<uint32_t>& outs = operation.outputs;
NN_DRIVER_RETURN_IF_ERROR(CheckTensorFloatType(operands[ins[0]].type));
NN_DRIVER_RETURN_IF_ERROR(CheckTensorFloatType(operands[ins[1]].type));
NN_DRIVER_RETURN_IF_ERROR(CheckTensorStaticAllocation(operands[ins[1]].lifetime));
NN_DRIVER_RETURN_IF_ERROR(CheckTensorFloatType(operands[ins[2]].type));
NN_DRIVER_RETURN_IF_ERROR(CheckTensorStaticAllocation(operands[ins[2]].lifetime));
NN_DRIVER_RETURN_IF_ERROR(CheckTensorStaticAllocation(operands[ins[3]].lifetime));
NN_DRIVER_RETURN_IF_ERROR(CheckTensorFloatType(operands[outs[0]].type));
float outputMin = -std::numeric_limits<float>::infinity();
float outputMax = +std::numeric_limits<float>::infinity();
int activation = getScalarData<int32_t>(operands[ins[3]]);
NN_DRIVER_RETURN_IF_ERROR(
ConvertActivationToOutputRange(activation, &outputMin, &outputMax));
if (subgraph != nullptr) {
const xnn_status status =
xnn_define_fully_connected(subgraph, outputMin, outputMax,
/*input_id=*/xnnpackTensors[ins[0]],
/*filter_id=*/xnnpackTensors[ins[1]],
/*bias_id=*/xnnpackTensors[ins[2]],
/*output_id=*/xnnpackTensors[outs[0]],
/*flags=*/XNN_FLAG_TENSORFLOW_RESHAPE_2D);
if (status != xnn_status_success) {
LOG(ERROR) << "XNNPACK xnn_define_fully_connected FAILED";
return V1_3::ErrorStatus::GENERAL_FAILURE;
}
}
return V1_3::ErrorStatus::NONE;
}
static V1_3::ErrorStatus VisitFloorNode(xnn_subgraph_t subgraph,
const V1_3::Operation& operation,
RunTimeOperandInfo* operands,
const std::vector<uint32_t>& xnnpackTensors) {
const hardware::hidl_vec<uint32_t>& ins = operation.inputs;
const hardware::hidl_vec<uint32_t>& outs = operation.outputs;
NN_DRIVER_RETURN_IF_ERROR(CheckTensorFloatType(operands[ins[0]].type));
NN_DRIVER_RETURN_IF_ERROR(CheckTensorFloatType(operands[outs[0]].type));
if (subgraph != nullptr) {
const xnn_status status =
xnn_define_floor(subgraph,
/*input1_id=*/xnnpackTensors[ins[0]],
/*output_id=*/xnnpackTensors[outs[0]], /*flags=*/0);
if (status != xnn_status_success) {
LOG(ERROR) << "XNNPACK xnn_define_floor FAILED";
return V1_3::ErrorStatus::GENERAL_FAILURE;
}
}
return V1_3::ErrorStatus::NONE;
}
static V1_3::ErrorStatus VisitHardSwishNode(xnn_subgraph_t subgraph,
const V1_3::Operation& operation,
RunTimeOperandInfo* operands,
const std::vector<uint32_t>& xnnpackTensors) {
const hardware::hidl_vec<uint32_t>& ins = operation.inputs;
const hardware::hidl_vec<uint32_t>& outs = operation.outputs;
NN_DRIVER_RETURN_IF_ERROR(CheckTensorFloatType(operands[ins[0]].type));
NN_DRIVER_RETURN_IF_ERROR(CheckTensorFloatType(operands[outs[0]].type));
if (subgraph != nullptr) {
const xnn_status status =
xnn_define_hardswish(subgraph, /*input_id=*/xnnpackTensors[ins[0]],
/*output_id=*/xnnpackTensors[outs[0]], /*flags=*/0);
if (status != xnn_status_success) {
LOG(ERROR) << "XNNPACK xnn_define_hardswish FAILED";
return V1_3::ErrorStatus::GENERAL_FAILURE;
}
}
return V1_3::ErrorStatus::NONE;
}
static V1_3::ErrorStatus VisitLogisticNode(xnn_subgraph_t subgraph,
const V1_3::Operation& operation,
RunTimeOperandInfo* operands,
const std::vector<uint32_t>& xnnpackTensors) {
const hardware::hidl_vec<uint32_t>& ins = operation.inputs;
const hardware::hidl_vec<uint32_t>& outs = operation.outputs;
NN_DRIVER_RETURN_IF_ERROR(CheckTensorFloatType(operands[ins[0]].type));
NN_DRIVER_RETURN_IF_ERROR(CheckTensorFloatType(operands[outs[0]].type));
if (subgraph != nullptr) {
const xnn_status status =
xnn_define_sigmoid(subgraph, /*input_id=*/xnnpackTensors[ins[0]],
/*output_id=*/xnnpackTensors[outs[0]], /*flags=*/0);
if (status != xnn_status_success) {
LOG(ERROR) << "XNNPACK xnn_define_sigmoid FAILED";
return V1_3::ErrorStatus::GENERAL_FAILURE;
}
}
return V1_3::ErrorStatus::NONE;
}
static V1_3::ErrorStatus VisitMaxPool2DNode(xnn_subgraph_t subgraph,
const V1_3::Operation& operation,
RunTimeOperandInfo* operands,
const std::vector<uint32_t>& xnnpackTensors) {
const hardware::hidl_vec<uint32_t>& ins = operation.inputs;
const hardware::hidl_vec<uint32_t>& outs = operation.outputs;
NN_DRIVER_RETURN_IF_ERROR(CheckTensorFloatType(operands[ins[0]].type));
NN_DRIVER_RETURN_IF_ERROR(CheckTensorFloatType(operands[outs[0]].type));
// Make sure all scalar params are constant.
for (uint32_t i = 1; i < ins.size(); i++) {
NN_DRIVER_RETURN_IF_ERROR(CheckTensorStaticAllocation(operands[ins[i]].lifetime));
}
bool use_nchw = false;
if (ins.size() == 8) {
use_nchw = getScalarData<bool>(operands[ins[7]]);
}
if (ins.size() == 11) {
use_nchw = getScalarData<bool>(operands[ins[10]]);
}
if (use_nchw) {
VLOG(DRIVER) << "XNNPACK VisitMaxPool2DNode FAILED: only NHWC layout is supported";
return V1_3::ErrorStatus::INVALID_ARGUMENT;
}
int32_t stride_width, stride_height, filter_width, filter_height, activation;
uint32_t input_padding_top = 0;
uint32_t input_padding_right = 0;
uint32_t input_padding_bottom = 0;
uint32_t input_padding_left = 0;
uint32_t flags = 0;
if (ins.size() >= 10) {
// Explicit padding
input_padding_left = static_cast<uint32_t>(getScalarData<int32_t>(operands[ins[1]]));
input_padding_right = static_cast<uint32_t>(getScalarData<int32_t>(operands[ins[2]]));
input_padding_top = static_cast<uint32_t>(getScalarData<int32_t>(operands[ins[3]]));
input_padding_bottom = static_cast<uint32_t>(getScalarData<int32_t>(operands[ins[4]]));
stride_width = getScalarData<int32_t>(operands[ins[5]]);
stride_height = getScalarData<int32_t>(operands[ins[6]]);
filter_width = getScalarData<int32_t>(operands[ins[7]]);
filter_height = getScalarData<int32_t>(operands[ins[8]]);
activation = getScalarData<int32_t>(operands[ins[9]]);
} else {
// Implicit padding
int padding_implicit = getScalarData<int32_t>(operands[ins[1]]);
NN_DRIVER_RETURN_IF_ERROR(CalculatePadding(padding_implicit, &flags));
stride_width = getScalarData<int32_t>(operands[ins[2]]);
stride_height = getScalarData<int32_t>(operands[ins[3]]);
filter_width = getScalarData<int32_t>(operands[ins[4]]);
filter_height = getScalarData<int32_t>(operands[ins[5]]);
activation = getScalarData<int32_t>(operands[ins[6]]);
}
NN_DRIVER_RETURN_IF_ERROR(
CheckPoolingParams(stride_width, stride_height, filter_width, filter_height));
float outputMin = -std::numeric_limits<float>::infinity();
float outputMax = +std::numeric_limits<float>::infinity();
NN_DRIVER_RETURN_IF_ERROR(
ConvertActivationToOutputRange(activation, &outputMin, &outputMax));
if (subgraph != nullptr) {
xnn_status status = xnn_status_success;
if (filter_width == 1 && filter_height == 1) {
status = xnn_define_clamp(subgraph, outputMin, outputMax,
/*input_id=*/xnnpackTensors[ins[0]],
/*output_id=*/xnnpackTensors[outs[0]], /*flags=*/0);
} else {
status = xnn_define_max_pooling_2d(
subgraph, input_padding_top, input_padding_right, input_padding_bottom,
input_padding_left, static_cast<uint32_t>(filter_height),
static_cast<uint32_t>(filter_width), static_cast<uint32_t>(stride_height),
static_cast<uint32_t>(stride_width), /*dilation_height=*/1,
/*dilation_width=*/1, outputMin, outputMax,
/*input_id=*/xnnpackTensors[ins[0]],
/*output_id=*/xnnpackTensors[outs[0]], flags);
}
if (status != xnn_status_success) {
LOG(ERROR) << "XNNPACK xnn_define_max_pooling_2d FAILED";
return V1_3::ErrorStatus::GENERAL_FAILURE;
}
}
return V1_3::ErrorStatus::NONE;
}
static V1_3::ErrorStatus VisitMaximumNode(xnn_subgraph_t subgraph,
const V1_3::Operation& operation,
RunTimeOperandInfo* operands,
const std::vector<uint32_t>& xnnpackTensors) {
const hardware::hidl_vec<uint32_t>& ins = operation.inputs;
const hardware::hidl_vec<uint32_t>& outs = operation.outputs;
NN_DRIVER_RETURN_IF_ERROR(CheckTensorFloatType(operands[ins[0]].type));
NN_DRIVER_RETURN_IF_ERROR(CheckTensorFloatType(operands[ins[1]].type));
NN_DRIVER_RETURN_IF_ERROR(CheckTensorStaticAllocation(operands[ins[2]].lifetime));
NN_DRIVER_RETURN_IF_ERROR(CheckTensorFloatType(operands[outs[0]].type));
float outputMin = -std::numeric_limits<float>::infinity();
float outputMax = +std::numeric_limits<float>::infinity();
int activation = getScalarData<int32_t>(operands[ins[2]]);
NN_DRIVER_RETURN_IF_ERROR(
ConvertActivationToOutputRange(activation, &outputMin, &outputMax));
if (subgraph != nullptr) {
const xnn_status status =
xnn_define_maximum2(subgraph,
/*input1_id=*/xnnpackTensors[ins[0]],
/*input2_id=*/xnnpackTensors[ins[1]],
/*output_id=*/xnnpackTensors[outs[0]], /*flags=*/0);
if (status != xnn_status_success) {
LOG(ERROR) << "XNNPACK xnn_define_maximum2 FAILED";
return V1_3::ErrorStatus::GENERAL_FAILURE;
}
}
return V1_3::ErrorStatus::NONE;
}
static V1_3::ErrorStatus VisitMeanNode(xnn_subgraph_t subgraph,
const V1_3::Operation& operation,
RunTimeOperandInfo* operands,
const std::vector<uint32_t>& xnnpackTensors) {
const hardware::hidl_vec<uint32_t>& ins = operation.inputs;
const hardware::hidl_vec<uint32_t>& outs = operation.outputs;
NN_DRIVER_RETURN_IF_ERROR(CheckTensorFloatType(operands[ins[0]].type));
NN_DRIVER_RETURN_IF_ERROR(CheckTensorShape(operands[ins[0]].dimensions, 4));
NN_DRIVER_RETURN_IF_ERROR(CheckAxesTensorShape(operands[ins[1]].dimensions));
NN_DRIVER_RETURN_IF_ERROR(CheckTensorStaticAllocation(operands[ins[1]].lifetime));
NN_DRIVER_RETURN_IF_ERROR(CheckTensorStaticAllocation(operands[ins[2]].lifetime));
NN_DRIVER_RETURN_IF_ERROR(CheckTensorFloatType(operands[outs[0]].type));
NN_DRIVER_RETURN_IF_ERROR(CheckTensorShape(operands[outs[0]].dimensions, 4));
int keep_dims = getScalarData<int32_t>(operands[ins[2]]);
if (keep_dims <= 0) {
LOG(ERROR) << "XNNPACK VisitMeanNode FAILED: only support keep_dims";
return V1_3::ErrorStatus::INVALID_ARGUMENT;
}
const int32_t* axes_buffer = reinterpret_cast<const int32_t*>(operands[ins[1]].buffer);
if (operands[ins[1]].dimensions[0] != 2) {
LOG(ERROR) << "XNNPACK VisitMeanNode FAILED: unsupported axes";
return V1_3::ErrorStatus::INVALID_ARGUMENT;
}
if (std::min(axes_buffer[0], axes_buffer[1]) != 1 ||
std::max(axes_buffer[0], axes_buffer[1]) != 2) {
LOG(ERROR) << "XNNPACK VisitMeanNode FAILED: unsupported axes";
return V1_3::ErrorStatus::INVALID_ARGUMENT;
}
if (subgraph != nullptr) {
const xnn_status status = xnn_define_global_average_pooling_2d(
subgraph,
/*outputMin=*/-std::numeric_limits<float>::infinity(),
/*outputMax=*/+std::numeric_limits<float>::infinity(),
/*input_id=*/xnnpackTensors[ins[0]],
/*output_id=*/xnnpackTensors[outs[0]], /*flags=*/0);
if (status != xnn_status_success) {
LOG(ERROR) << "XNNPACK xnn_define_global_average_pooling_2d FAILED";
return V1_3::ErrorStatus::GENERAL_FAILURE;
}
}
return V1_3::ErrorStatus::NONE;
}
static V1_3::ErrorStatus VisitMinimumNode(xnn_subgraph_t subgraph,
const V1_3::Operation& operation,
RunTimeOperandInfo* operands,
const std::vector<uint32_t>& xnnpackTensors) {
const hardware::hidl_vec<uint32_t>& ins = operation.inputs;
const hardware::hidl_vec<uint32_t>& outs = operation.outputs;
NN_DRIVER_RETURN_IF_ERROR(CheckTensorFloatType(operands[ins[0]].type));
NN_DRIVER_RETURN_IF_ERROR(CheckTensorFloatType(operands[ins[1]].type));
NN_DRIVER_RETURN_IF_ERROR(CheckTensorStaticAllocation(operands[ins[2]].lifetime));
NN_DRIVER_RETURN_IF_ERROR(CheckTensorFloatType(operands[outs[0]].type));
float outputMin = -std::numeric_limits<float>::infinity();
float outputMax = +std::numeric_limits<float>::infinity();
int activation = getScalarData<int32_t>(operands[ins[2]]);
NN_DRIVER_RETURN_IF_ERROR(
ConvertActivationToOutputRange(activation, &outputMin, &outputMax));
if (subgraph != nullptr) {
const xnn_status status =
xnn_define_minimum2(subgraph,
/*input1_id=*/xnnpackTensors[ins[0]],
/*input2_id=*/xnnpackTensors[ins[1]],
/*output_id=*/xnnpackTensors[outs[0]], /*flags=*/0);
if (status != xnn_status_success) {
LOG(ERROR) << "XNNPACK xnn_define_minimum2 FAILED";
return V1_3::ErrorStatus::GENERAL_FAILURE;
}
}
return V1_3::ErrorStatus::NONE;
}
static V1_3::ErrorStatus VisitMulNode(xnn_subgraph_t subgraph, const V1_3::Operation& operation,
RunTimeOperandInfo* operands,
const std::vector<uint32_t>& xnnpackTensors) {
const hardware::hidl_vec<uint32_t>& ins = operation.inputs;
const hardware::hidl_vec<uint32_t>& outs = operation.outputs;
NN_DRIVER_RETURN_IF_ERROR(CheckTensorFloatType(operands[ins[0]].type));
NN_DRIVER_RETURN_IF_ERROR(CheckTensorFloatType(operands[ins[1]].type));
NN_DRIVER_RETURN_IF_ERROR(CheckTensorStaticAllocation(operands[ins[2]].lifetime));
NN_DRIVER_RETURN_IF_ERROR(CheckTensorFloatType(operands[outs[0]].type));
int activation = getScalarData<int32_t>(operands[ins[2]]);
float outputMin = -std::numeric_limits<float>::infinity();
float outputMax = +std::numeric_limits<float>::infinity();
NN_DRIVER_RETURN_IF_ERROR(
ConvertActivationToOutputRange(activation, &outputMin, &outputMax));
if (subgraph != nullptr) {
const xnn_status status =
xnn_define_multiply2(subgraph, outputMin, outputMax,
/*input1_id=*/xnnpackTensors[ins[0]],
/*input2_id=*/xnnpackTensors[ins[1]],
/*output_id=*/xnnpackTensors[outs[0]], /*flags=*/0);
if (status != xnn_status_success) {
LOG(ERROR) << "XNNPACK xnn_define_multiply2 FAILED";
return V1_3::ErrorStatus::GENERAL_FAILURE;
}
}
return V1_3::ErrorStatus::NONE;
}
static V1_3::ErrorStatus VisitNegNode(xnn_subgraph_t subgraph, const V1_3::Operation& operation,
RunTimeOperandInfo* operands,
const std::vector<uint32_t>& xnnpackTensors) {
const hardware::hidl_vec<uint32_t>& ins = operation.inputs;
const hardware::hidl_vec<uint32_t>& outs = operation.outputs;
NN_DRIVER_RETURN_IF_ERROR(CheckTensorFloatType(operands[ins[0]].type));
NN_DRIVER_RETURN_IF_ERROR(CheckTensorFloatType(operands[outs[0]].type));
if (subgraph != nullptr) {
const xnn_status status =
xnn_define_negate(subgraph,
/*input1_id=*/xnnpackTensors[ins[0]],
/*output_id=*/xnnpackTensors[outs[0]], /*flags=*/0);
if (status != xnn_status_success) {
LOG(ERROR) << "XNNPACK xnn_define_negate FAILED";
return V1_3::ErrorStatus::GENERAL_FAILURE;
}
}
return V1_3::ErrorStatus::NONE;
}
static V1_3::ErrorStatus VisitPreluNode(xnn_subgraph_t subgraph,
const V1_3::Operation& operation,
RunTimeOperandInfo* operands,
const std::vector<uint32_t>& xnnpackTensors) {
const hardware::hidl_vec<uint32_t>& ins = operation.inputs;
const hardware::hidl_vec<uint32_t>& outs = operation.outputs;
NN_DRIVER_RETURN_IF_ERROR(CheckTensorFloatType(operands[ins[0]].type));
NN_DRIVER_RETURN_IF_ERROR(
CheckTensorShape(operands[ins[0]].dimensions, 1, XNN_MAX_TENSOR_DIMS));
NN_DRIVER_RETURN_IF_ERROR(CheckTensorFloatType(operands[ins[1]].type));
NN_DRIVER_RETURN_IF_ERROR(CheckSlopeTensorShape(operands[ins[1]].dimensions));
NN_DRIVER_RETURN_IF_ERROR(CheckTensorFloatType(operands[outs[0]].type));
NN_DRIVER_RETURN_IF_ERROR(
CheckTensorShape(operands[outs[0]].dimensions, 1, XNN_MAX_TENSOR_DIMS));
if (subgraph != nullptr) {
const xnn_status status =
xnn_define_prelu(subgraph, /*input_id=*/xnnpackTensors[ins[0]],
/*slope_id=*/xnnpackTensors[ins[1]],
/*output_id=*/xnnpackTensors[outs[0]], /*flags=*/0);
if (status != xnn_status_success) {
LOG(ERROR) << "XNNPACK xnn_define_prelu FAILED";
return V1_3::ErrorStatus::GENERAL_FAILURE;
}
}
return V1_3::ErrorStatus::NONE;
}
static V1_3::ErrorStatus VisitPadNode(xnn_subgraph_t subgraph, const V1_3::Operation& operation,
RunTimeOperandInfo* operands, float padding_value,
const std::vector<uint32_t>& xnnpackTensors) {
const hardware::hidl_vec<uint32_t>& ins = operation.inputs;
const hardware::hidl_vec<uint32_t>& outs = operation.outputs;
NN_DRIVER_RETURN_IF_ERROR(CheckTensorFloatType(operands[ins[0]].type));
NN_DRIVER_RETURN_IF_ERROR(
CheckTensorShape(operands[ins[0]].dimensions, 1, XNN_MAX_TENSOR_DIMS));
NN_DRIVER_RETURN_IF_ERROR(CheckTensorStaticAllocation(operands[ins[1]].lifetime));
NN_DRIVER_RETURN_IF_ERROR(CheckTensorFloatType(operands[outs[0]].type));
NN_DRIVER_RETURN_IF_ERROR(
CheckTensorShape(operands[outs[0]].dimensions, 1, XNN_MAX_TENSOR_DIMS));
const int32_t* paddings_data = reinterpret_cast<const int32_t*>(operands[ins[1]].buffer);
for (size_t i = 0; i < operands[ins[1]].dimensions.size() * 2; i++) {
if (paddings_data[i] < 0) return V1_3::ErrorStatus::INVALID_ARGUMENT;
}
if (subgraph != nullptr) {
std::array<size_t, XNN_MAX_TENSOR_DIMS> pre_paddings{};
std::array<size_t, XNN_MAX_TENSOR_DIMS> post_paddings{};
for (size_t i = 0; i < operands[ins[1]].dimensions.size(); i++) {
pre_paddings[i] = static_cast<size_t>(paddings_data[i * 2 + 0]);
post_paddings[i] = static_cast<size_t>(paddings_data[i * 2 + 1]);
}
const xnn_status status = xnn_define_static_constant_pad(
subgraph, pre_paddings.data(), post_paddings.data(), padding_value,
/*input_id=*/xnnpackTensors[ins[0]],
/*output_id=*/xnnpackTensors[outs[0]], /*flags=*/0);
if (status != xnn_status_success) {
LOG(ERROR) << "XNNPACK xnn_define_static_constant_pad FAILED";
return V1_3::ErrorStatus::GENERAL_FAILURE;
}
}
return V1_3::ErrorStatus::NONE;
}
static V1_3::ErrorStatus VisitPadV2Node(xnn_subgraph_t subgraph,
const V1_3::Operation& operation,
RunTimeOperandInfo* operands,
const std::vector<uint32_t>& xnnpackTensors) {
const hardware::hidl_vec<uint32_t>& ins = operation.inputs;
if (operands[ins[2]].type != OperandType::FLOAT32) {
return V1_3::ErrorStatus::INVALID_ARGUMENT;
}
float padding_value = getScalarData<float>(operands[ins[2]]);
return VisitPadNode(subgraph, operation, operands, padding_value, xnnpackTensors);
}
static V1_3::ErrorStatus VisitReshapeNode(xnn_subgraph_t subgraph,
const V1_3::Operation& operation,
RunTimeOperandInfo* operands,
const std::vector<uint32_t>& xnnpackTensors) {
const hardware::hidl_vec<uint32_t>& ins = operation.inputs;
const hardware::hidl_vec<uint32_t>& outs = operation.outputs;
NN_DRIVER_RETURN_IF_ERROR(CheckTensorFloatType(operands[ins[0]].type));
NN_DRIVER_RETURN_IF_ERROR(
CheckTensorShape(operands[ins[0]].dimensions, 0, XNN_MAX_TENSOR_DIMS));
NN_DRIVER_RETURN_IF_ERROR(CheckTensorStaticAllocation(operands[ins[1]].lifetime));
NN_DRIVER_RETURN_IF_ERROR(CheckTensorFloatType(operands[outs[0]].type));
NN_DRIVER_RETURN_IF_ERROR(
CheckTensorShape(operands[outs[0]].dimensions, 0, XNN_MAX_TENSOR_DIMS));
if (subgraph != nullptr) {
std::array<size_t, XNN_MAX_TENSOR_DIMS> new_shape;
for (uint32_t i = 0; i < operands[outs[0]].dimensions.size(); i++) {
new_shape[i] = static_cast<size_t>(operands[outs[0]].dimensions[i]);
}
const xnn_status status = xnn_define_static_reshape(
subgraph, static_cast<size_t>(operands[outs[0]].dimensions.size()),
new_shape.data(),
/*input_id=*/xnnpackTensors[ins[0]],
/*output_id=*/xnnpackTensors[outs[0]], /*flags=*/0);
if (status != xnn_status_success) {
LOG(ERROR) << "XNNPACK xnn_define_static_reshape FAILED";
return V1_3::ErrorStatus::GENERAL_FAILURE;
}
}
return V1_3::ErrorStatus::NONE;
}
static V1_3::ErrorStatus VisitResizeBilinearNode(xnn_subgraph_t subgraph,
const V1_3::Operation& operation,
RunTimeOperandInfo* operands,
const std::vector<uint32_t>& xnnpackTensors) {
const hardware::hidl_vec<uint32_t>& ins = operation.inputs;
const hardware::hidl_vec<uint32_t>& outs = operation.outputs;
NN_DRIVER_RETURN_IF_ERROR(CheckTensorFloatType(operands[ins[0]].type));
NN_DRIVER_RETURN_IF_ERROR(CheckTensorShape(operands[ins[0]].dimensions, 4));
NN_DRIVER_RETURN_IF_ERROR(CheckTensorFloatType(operands[outs[0]].type));
NN_DRIVER_RETURN_IF_ERROR(CheckTensorShape(operands[outs[0]].dimensions, 4));
// Make sure all scalar params are constant.
for (uint32_t i = 1; i < ins.size(); i++) {
NN_DRIVER_RETURN_IF_ERROR(CheckTensorStaticAllocation(operands[ins[i]].lifetime));
}
if (ins.size() >= 4) {
bool use_nchw = getScalarData<bool>(operands[ins[3]]);
if (use_nchw) {
VLOG(DRIVER)
<< "XNNPACK VisitResizeBilinearNode FAILED: only NHWC layout is supported";
return V1_3::ErrorStatus::INVALID_ARGUMENT;
}
}
size_t new_height, new_width;
if (operands[ins[1]].type == OperandType::INT32) {
// explicitly specify the output dimension.
new_width = static_cast<size_t>(getScalarData<int32_t>(operands[ins[1]]));
new_height = static_cast<size_t>(getScalarData<int32_t>(operands[ins[2]]));
} else if (operands[ins[1]].type == OperandType::FLOAT32) {
// specify the output dimension scaling factor.
float width_scale = getScalarData<float>(operands[ins[1]]);
float height_scale = getScalarData<float>(operands[ins[2]]);
if (width_scale <= 0 || height_scale <= 0) {
return V1_3::ErrorStatus::INVALID_ARGUMENT;
}
new_height = static_cast<size_t>(operands[ins[0]].dimensions[1] * height_scale);
new_width = static_cast<size_t>(operands[ins[0]].dimensions[2] * width_scale);
} else {
return V1_3::ErrorStatus::INVALID_ARGUMENT;
}
bool align_corners = false;
bool half_pixel_centers = false;
if (ins.size() == 6) {
align_corners = getScalarData<bool>(operands[ins[4]]);
half_pixel_centers = getScalarData<bool>(operands[ins[5]]);
}
if (align_corners && !half_pixel_centers) {
return V1_3::ErrorStatus::INVALID_ARGUMENT;
}
if (subgraph != nullptr) {
uint32_t flags = 0;
if (align_corners) {
flags |= XNN_FLAG_ALIGN_CORNERS;
} else if (!half_pixel_centers) {
flags |= XNN_FLAG_TENSORFLOW_LEGACY_MODE;
}
const xnn_status status = xnn_define_static_resize_bilinear_2d(
subgraph, new_height, new_width,
/*input_id=*/xnnpackTensors[ins[0]],
/*output_id=*/xnnpackTensors[outs[0]], flags);
if (status != xnn_status_success) {
LOG(ERROR) << "XNNPACK xnn_define_static_resize_bilinear_2d FAILED";
return V1_3::ErrorStatus::GENERAL_FAILURE;
}
}
return V1_3::ErrorStatus::NONE;
}
static V1_3::ErrorStatus VisitReluNode(xnn_subgraph_t subgraph,
const V1_3::Operation& operation,
RunTimeOperandInfo* operands, float outputMin,
float outputMax,
const std::vector<uint32_t>& xnnpackTensors) {
const hardware::hidl_vec<uint32_t>& ins = operation.inputs;
const hardware::hidl_vec<uint32_t>& outs = operation.outputs;
NN_DRIVER_RETURN_IF_ERROR(CheckTensorFloatType(operands[ins[0]].type));
NN_DRIVER_RETURN_IF_ERROR(CheckTensorFloatType(operands[outs[0]].type));
if (subgraph != nullptr) {
const xnn_status status =
xnn_define_clamp(subgraph, outputMin, outputMax,
/*input_id=*/xnnpackTensors[ins[0]],
/*output_id=*/xnnpackTensors[outs[0]], /*flags=*/0);
if (status != xnn_status_success) {
LOG(ERROR) << "XNNPACK xnn_define_clamp FAILED";
return V1_3::ErrorStatus::GENERAL_FAILURE;
}
}
return V1_3::ErrorStatus::NONE;
}
static V1_3::ErrorStatus VisitSqrtNode(xnn_subgraph_t subgraph,
const V1_3::Operation& operation,
RunTimeOperandInfo* operands,
const std::vector<uint32_t>& xnnpackTensors) {
const hardware::hidl_vec<uint32_t>& ins = operation.inputs;
const hardware::hidl_vec<uint32_t>& outs = operation.outputs;
NN_DRIVER_RETURN_IF_ERROR(CheckTensorFloatType(operands[ins[0]].type));
NN_DRIVER_RETURN_IF_ERROR(CheckTensorFloatType(operands[outs[0]].type));
if (subgraph != nullptr) {
const xnn_status status =
xnn_define_square_root(subgraph,
/*input1_id=*/xnnpackTensors[ins[0]],
/*output_id=*/xnnpackTensors[outs[0]], /*flags=*/0);
if (status != xnn_status_success) {
LOG(ERROR) << "XNNPACK xnn_define_bankers_rounding FAILED";
return V1_3::ErrorStatus::GENERAL_FAILURE;
}
}
return V1_3::ErrorStatus::NONE;
}
static V1_3::ErrorStatus VisitSubNode(xnn_subgraph_t subgraph, const V1_3::Operation& operation,
RunTimeOperandInfo* operands,
const std::vector<uint32_t>& xnnpackTensors) {
const hardware::hidl_vec<uint32_t>& ins = operation.inputs;
const hardware::hidl_vec<uint32_t>& outs = operation.outputs;
NN_DRIVER_RETURN_IF_ERROR(CheckTensorFloatType(operands[ins[0]].type));
NN_DRIVER_RETURN_IF_ERROR(CheckTensorFloatType(operands[ins[1]].type));
NN_DRIVER_RETURN_IF_ERROR(CheckTensorStaticAllocation(operands[ins[2]].lifetime));
NN_DRIVER_RETURN_IF_ERROR(CheckTensorFloatType(operands[outs[0]].type));
float outputMin = -std::numeric_limits<float>::infinity();
float outputMax = +std::numeric_limits<float>::infinity();
int activation = getScalarData<int32_t>(operands[ins[2]]);
NN_DRIVER_RETURN_IF_ERROR(
ConvertActivationToOutputRange(activation, &outputMin, &outputMax));
if (subgraph != nullptr) {
const xnn_status status =
xnn_define_subtract(subgraph, outputMin, outputMax,
/*input1_id=*/xnnpackTensors[ins[0]],
/*input2_id=*/xnnpackTensors[ins[1]],
/*output_id=*/xnnpackTensors[outs[0]], /*flags=*/0);
if (status != xnn_status_success) {
LOG(ERROR) << "XNNPACK xnn_define_subtract FAILED";
return V1_3::ErrorStatus::GENERAL_FAILURE;
}
}
return V1_3::ErrorStatus::NONE;
}
static V1_3::ErrorStatus VisitSoftmaxNode(xnn_subgraph_t subgraph,
const V1_3::Operation& operation,
RunTimeOperandInfo* operands,
const std::vector<uint32_t>& xnnpackTensors) {
const hardware::hidl_vec<uint32_t>& ins = operation.inputs;
const hardware::hidl_vec<uint32_t>& outs = operation.outputs;
NN_DRIVER_RETURN_IF_ERROR(CheckTensorFloatType(operands[ins[0]].type));
NN_DRIVER_RETURN_IF_ERROR(CheckTensorStaticAllocation(operands[ins[1]].lifetime));
NN_DRIVER_RETURN_IF_ERROR(CheckTensorFloatType(operands[outs[0]].type));
float beta = getScalarData<float>(operands[ins[1]]);
if (beta != 1.0f) {
LOG(ERROR) << "XNNPACK VisitSoftmaxNode FAILED, unsupported beta value: " << beta;
return V1_3::ErrorStatus::INVALID_ARGUMENT;
}
if (ins.size() >= 3) {
NN_DRIVER_RETURN_IF_ERROR(CheckTensorStaticAllocation(operands[ins[2]].lifetime));
int axis = getScalarData<int32_t>(operands[ins[2]]);
if (axis != -1) {
LOG(ERROR) << "XNNPACK VisitSoftmaxNode FAILED, unsupported axis value: " << axis;
return V1_3::ErrorStatus::INVALID_ARGUMENT;
}
}
if (subgraph != nullptr) {
const xnn_status status =
xnn_define_softmax(subgraph, /*input_id=*/xnnpackTensors[ins[0]],
/*output_id=*/xnnpackTensors[outs[0]], /*flags=*/0);
if (status != xnn_status_success) {
LOG(ERROR) << "XNNPACK xnn_define_softmax FAILED";
return V1_3::ErrorStatus::GENERAL_FAILURE;
}
}
return V1_3::ErrorStatus::NONE;
}
private:
Subgraph(xnn_runtime_t runtime, std::unordered_set<uint32_t>&& externals,
bool useStaticBuffer = false)
: mRuntime(runtime, &xnn_delete_runtime),
mExternals(externals),
mUseStaticBuffer(useStaticBuffer) {}
// XNNPACK Runtime (subgraph + workspace) with smart-pointer for lifetime
// management.
std::unique_ptr<xnn_runtime, decltype(&xnn_delete_runtime)> mRuntime{nullptr,
&xnn_delete_runtime};
std::unordered_set<uint32_t> mExternals;
bool mFirstRun = true;
bool mUseStaticBuffer;
};
class SamplePreparedModelXNNPACK : public SamplePreparedModel {
public:
SamplePreparedModelXNNPACK(const V1_3::Model& model, const SampleDriver* driver,
V1_1::ExecutionPreference preference, uid_t userId,
V1_3::Priority priority)
: SamplePreparedModel(model, driver, preference, userId, priority),
mSubgraph(nullptr),
mThreadpool(nullptr) {}
~SamplePreparedModelXNNPACK() {
delete mSubgraph;
pthreadpool_destroy(mThreadpool);
};
bool initialize();
hardware::Return<V1_0::ErrorStatus> execute(
const V1_0::Request& request, const sp<V1_0::IExecutionCallback>& callback) override;
hardware::Return<V1_0::ErrorStatus> execute_1_2(
const V1_0::Request& request, V1_2::MeasureTiming measure,
const sp<V1_2::IExecutionCallback>& callback) override;
hardware::Return<V1_3::ErrorStatus> execute_1_3(
const V1_3::Request& request, V1_2::MeasureTiming measure,
const V1_3::OptionalTimePoint& deadline,
const V1_3::OptionalTimeoutDuration& loopTimeoutDuration,
const sp<V1_3::IExecutionCallback>& callback) override;
hardware::Return<void> executeSynchronously(const V1_0::Request& request,
V1_2::MeasureTiming measure,
executeSynchronously_cb cb) override;
hardware::Return<void> executeSynchronously_1_3(
const V1_3::Request& request, V1_2::MeasureTiming measure,
const V1_3::OptionalTimePoint& deadline,
const V1_3::OptionalTimeoutDuration& loopTimeoutDuration,
executeSynchronously_1_3_cb cb) override;
hardware::Return<void> configureExecutionBurst(
const sp<V1_2::IBurstCallback>& callback,
const MQDescriptorSync<V1_2::FmqRequestDatum>& requestChannel,
const MQDescriptorSync<V1_2::FmqResultDatum>& resultChannel,
configureExecutionBurst_cb cb) override;
hardware::Return<void> executeFenced(const V1_3::Request& request,
const hardware::hidl_vec<hardware::hidl_handle>& wait_for,
V1_2::MeasureTiming measure,
const V1_3::OptionalTimePoint& deadline,
const V1_3::OptionalTimeoutDuration& loopTimeoutDuration,
const V1_3::OptionalTimeoutDuration& duration,
executeFenced_cb callback) override;
private:
Subgraph* mSubgraph;
std::vector<RunTimeOperandInfo> mOperands;
pthreadpool* mThreadpool;
};
hardware::Return<void> SamplePreparedModelXNNPACK::configureExecutionBurst(
const sp<V1_2::IBurstCallback>& callback,
const MQDescriptorSync<V1_2::FmqRequestDatum>& requestChannel,
const MQDescriptorSync<V1_2::FmqResultDatum>& resultChannel,
configureExecutionBurst_cb cb) {
VLOG(DRIVER) << "SamplePreparedModelXNNPACK::configureExecutionBurst not supported";
cb(V1_0::ErrorStatus::GENERAL_FAILURE, {});
return hardware::Void();
}
bool SamplePreparedModelXNNPACK::initialize() {
auto status = SamplePreparedModel::initialize();
mThreadpool = pthreadpool_create(kNumOfWorkerThreads);
if (mThreadpool == nullptr) {
VLOG(DRIVER) << "SamplePreparedModelXNNPACK::initialize failed to create pthreadpool, "
"fallback to single threaded execution";
}
const V1_3::Model* model = getModel();
mOperands = initializeRunTimeInfo(model->main, mPoolInfos, &model->operandValues);
mSubgraph = Subgraph::Create(model->main.operations, mOperands, model->main.inputIndexes,
model->main.outputIndexes, mThreadpool);
return status;
}
template <typename T_IExecutionCallback>
void asyncExecuteXNNPACK(Subgraph* subgraph, RunTimeOperandInfo* operands,
const V1_3::Request& request, V1_2::MeasureTiming measure,
const V1_3::Model& model, const std::optional<Deadline>& deadline,
const V1_3::OptionalTimeoutDuration& loopTimeoutDuration,
const sp<T_IExecutionCallback>& callback) {
std::vector<RunTimePoolInfo> requestPoolInfos;
if (!setRunTimePoolInfosFromMemoryPools(&requestPoolInfos, uncheckedConvert(request.pools))) {
notify(callback, V1_3::ErrorStatus::GENERAL_FAILURE, {}, kNoTiming);
}
updateForArguments(model.main.inputIndexes, request.inputs, requestPoolInfos, operands);
updateForArguments(model.main.outputIndexes, request.outputs, requestPoolInfos, operands);
auto status = subgraph->Invoke(operands);
VLOG(DRIVER) << "XNNPACK subgraph invoke returned " << toString(status);
if (status == V1_3::ErrorStatus::NONE) {
VLOG(DRIVER) << "Completed run normally";
for (auto& runtimeInfo : requestPoolInfos) {
runtimeInfo.flush();
}
}
notify(callback, status, {}, kNoTiming);
}
template <typename T_IExecutionCallback>
V1_3::ErrorStatus executeXNNPACKBase(Subgraph* subgraph, RunTimeOperandInfo* operands,
const V1_3::Request& request, V1_2::MeasureTiming measure,
const V1_3::Model& model,
const V1_3::OptionalTimePoint& halDeadline,
const V1_3::OptionalTimeoutDuration& loopTimeoutDuration,
const sp<T_IExecutionCallback>& callback) {
VLOG(DRIVER) << "executeXNNPACKBase(" << SHOW_IF_DEBUG(toString(request)) << ")";
if (callback.get() == nullptr) {
LOG(ERROR) << "invalid callback passed to executeXNNPACKBase";
return V1_3::ErrorStatus::INVALID_ARGUMENT;
}
if (!validateRequest(request, model, /*allowUnspecifiedOutput=*/false)) {
notify(callback, V1_3::ErrorStatus::INVALID_ARGUMENT, {}, kNoTiming);
return V1_3::ErrorStatus::INVALID_ARGUMENT;
}
const auto deadline = makeDeadline(halDeadline);
if (hasDeadlinePassed(deadline)) {
notify(callback, V1_3::ErrorStatus::MISSED_DEADLINE_PERSISTENT, {}, kNoTiming);
return V1_3::ErrorStatus::NONE;
}
// This thread is intentionally detached because the sample driver service
// is expected to live forever.
std::thread([&subgraph, &operands, &model, request, measure, deadline, loopTimeoutDuration,
callback] {
asyncExecuteXNNPACK(subgraph, operands, request, measure, model, deadline,
loopTimeoutDuration, callback);
}).detach();
return V1_3::ErrorStatus::NONE;
}
hardware::Return<V1_0::ErrorStatus> SamplePreparedModelXNNPACK::execute(
const V1_0::Request& request, const sp<V1_0::IExecutionCallback>& callback) {
const V1_3::Model* model = getModel();
const V1_3::ErrorStatus status =
executeXNNPACKBase(mSubgraph, mOperands.data(), convertToV1_3(request),
V1_2::MeasureTiming::NO, *model, {}, {}, callback);
return convertToV1_0(status);
}
hardware::Return<V1_0::ErrorStatus> SamplePreparedModelXNNPACK::execute_1_2(
const V1_0::Request& request, V1_2::MeasureTiming measure,
const sp<V1_2::IExecutionCallback>& callback) {
const V1_3::Model* model = getModel();
const V1_3::ErrorStatus status = executeXNNPACKBase(
mSubgraph, mOperands.data(), convertToV1_3(request), measure, *model, {}, {}, callback);
return convertToV1_0(status);
}
hardware::Return<V1_3::ErrorStatus> SamplePreparedModelXNNPACK::execute_1_3(
const V1_3::Request& request, V1_2::MeasureTiming measure,
const V1_3::OptionalTimePoint& deadline,
const V1_3::OptionalTimeoutDuration& loopTimeoutDuration,
const sp<V1_3::IExecutionCallback>& callback) {
const V1_3::Model* model = getModel();
return executeXNNPACKBase(mSubgraph, mOperands.data(), request, measure, *model, deadline,
loopTimeoutDuration, callback);
}
static std::tuple<V1_3::ErrorStatus, hardware::hidl_vec<V1_2::OutputShape>, V1_2::Timing>
executeSynchronouslyXNNPACKBase(Subgraph* subgraph, RunTimeOperandInfo* operands,
const V1_3::Request& request, V1_2::MeasureTiming measure,
const V1_3::Model& model,
const V1_3::OptionalTimePoint& halDeadline,
const V1_3::OptionalTimeoutDuration& loopTimeoutDuration) {
VLOG(DRIVER) << "executeSynchronouslyXNNPACKBase(" << SHOW_IF_DEBUG(toString(request)) << ")";
if (!validateRequest(request, model, /*allowUnspecifiedOutput=*/false)) {
return {V1_3::ErrorStatus::INVALID_ARGUMENT, {}, kNoTiming};
}
const auto deadline = makeDeadline(halDeadline);
if (hasDeadlinePassed(deadline)) {
return {V1_3::ErrorStatus::MISSED_DEADLINE_PERSISTENT, {}, kNoTiming};
}
std::vector<RunTimePoolInfo> requestPoolInfos;
if (!setRunTimePoolInfosFromMemoryPools(&requestPoolInfos, uncheckedConvert(request.pools))) {
return {V1_3::ErrorStatus::GENERAL_FAILURE, {}, kNoTiming};
}
updateForArguments(model.main.inputIndexes, request.inputs, requestPoolInfos, operands);
updateForArguments(model.main.outputIndexes, request.outputs, requestPoolInfos, operands);
VLOG(DRIVER) << "XNNPACK subgraph invoke started";
auto status = subgraph->Invoke(operands);
VLOG(DRIVER) << "XNNPACK subgraph invoke returned " << toString(status);
if (status == V1_3::ErrorStatus::NONE) {
VLOG(DRIVER) << "Completed run normally";
for (auto& runtimeInfo : requestPoolInfos) {
runtimeInfo.flush();
}
}
return {status, {}, kNoTiming};
}
hardware::Return<void> SamplePreparedModelXNNPACK::executeSynchronously(
const V1_0::Request& request, V1_2::MeasureTiming measure, executeSynchronously_cb cb) {
const V1_3::Model* model = getModel();
auto [status, outputShapes, timing] = executeSynchronouslyXNNPACKBase(
mSubgraph, mOperands.data(), convertToV1_3(request), measure, *model, {}, {});
cb(convertToV1_0(status), std::move(outputShapes), timing);
return hardware::Void();
}
hardware::Return<void> SamplePreparedModelXNNPACK::executeSynchronously_1_3(
const V1_3::Request& request, V1_2::MeasureTiming measure,
const V1_3::OptionalTimePoint& deadline,
const V1_3::OptionalTimeoutDuration& loopTimeoutDuration, executeSynchronously_1_3_cb cb) {
const V1_3::Model* model = getModel();
auto [status, outputShapes, timing] = executeSynchronouslyXNNPACKBase(
mSubgraph, mOperands.data(), request, measure, *model, deadline, loopTimeoutDuration);
cb(status, std::move(outputShapes), timing);
return hardware::Void();
}
// The sample driver will finish the execution and then return.
hardware::Return<void> SamplePreparedModelXNNPACK::executeFenced(
const V1_3::Request& request, const hardware::hidl_vec<hardware::hidl_handle>& waitFor,
V1_2::MeasureTiming measure, const V1_3::OptionalTimePoint& halDeadline,
const V1_3::OptionalTimeoutDuration& loopTimeoutDuration,
const V1_3::OptionalTimeoutDuration& duration, executeFenced_cb cb) {
VLOG(DRIVER) << "executeFenced(" << SHOW_IF_DEBUG(toString(request)) << ")";
const V1_3::Model* model = getModel();
if (!validateRequest(request, *model, /*allowUnspecifiedOutput=*/false)) {
cb(V1_3::ErrorStatus::INVALID_ARGUMENT, hardware::hidl_handle(nullptr), nullptr);
return hardware::Void();
}
const auto deadline = makeDeadline(halDeadline);
if (hasDeadlinePassed(deadline)) {
cb(V1_3::ErrorStatus::MISSED_DEADLINE_PERSISTENT, hardware::hidl_handle(nullptr), nullptr);
return hardware::Void();
}
// Wait for the dependent events to signal
for (const auto& fenceHandle : waitFor) {
if (!fenceHandle.getNativeHandle()) {
cb(V1_3::ErrorStatus::INVALID_ARGUMENT, hardware::hidl_handle(nullptr), nullptr);
return hardware::Void();
}
int syncFenceFd = fenceHandle.getNativeHandle()->data[0];
if (syncWait(syncFenceFd, -1) != FenceState::SIGNALED) {
LOG(ERROR) << "syncWait failed";
cb(V1_3::ErrorStatus::GENERAL_FAILURE, hardware::hidl_handle(nullptr), nullptr);
return hardware::Void();
}
}
std::vector<RunTimePoolInfo> requestPoolInfos;
if (!setRunTimePoolInfosFromMemoryPools(&requestPoolInfos, uncheckedConvert(request.pools))) {
cb(V1_3::ErrorStatus::GENERAL_FAILURE, hardware::hidl_handle(nullptr), nullptr);
}
updateForArguments(model->main.inputIndexes, request.inputs, requestPoolInfos,
mOperands.data());
updateForArguments(model->main.outputIndexes, request.outputs, requestPoolInfos,
mOperands.data());
auto status = mSubgraph->Invoke(mOperands.data());
VLOG(DRIVER) << "XNNPACK subgraph invoke returned " << toString(status);
if (status == V1_3::ErrorStatus::NONE) {
VLOG(DRIVER) << "Completed run normally";
for (auto& runtimeInfo : requestPoolInfos) {
runtimeInfo.flush();
}
}
sp<SampleFencedExecutionCallback> fencedExecutionCallback =
new SampleFencedExecutionCallback(kNoTiming, kNoTiming, status);
cb(status, hardware::hidl_handle(nullptr), fencedExecutionCallback);
return hardware::Void();
}
class SampleDriverFloatXNNPACK : public SampleDriverPartial {
public:
SampleDriverFloatXNNPACK() : SampleDriverPartial("nnapi-sample_float_xnnpack") {}
hardware::Return<void> getCapabilities_1_3(getCapabilities_1_3_cb cb) override;
hardware::Return<V1_0::ErrorStatus> prepareModel(
const V1_0::Model& model, const sp<V1_0::IPreparedModelCallback>& callback) override;
hardware::Return<V1_0::ErrorStatus> prepareModel_1_1(
const V1_1::Model& model, V1_1::ExecutionPreference preference,
const sp<V1_0::IPreparedModelCallback>& callback) override;
hardware::Return<V1_0::ErrorStatus> prepareModel_1_2(
const V1_2::Model& model, V1_1::ExecutionPreference preference,
const hardware::hidl_vec<hardware::hidl_handle>& modelCache,
const hardware::hidl_vec<hardware::hidl_handle>& dataCache, const HalCacheToken& token,
const sp<V1_2::IPreparedModelCallback>& callback) override;
hardware::Return<V1_3::ErrorStatus> prepareModel_1_3(
const V1_3::Model& model, V1_1::ExecutionPreference preference, V1_3::Priority priority,
const V1_3::OptionalTimePoint& deadline,
const hardware::hidl_vec<hardware::hidl_handle>& modelCache,
const hardware::hidl_vec<hardware::hidl_handle>& dataCache, const HalCacheToken& token,
const sp<V1_3::IPreparedModelCallback>& callback) override;
hardware::Return<void> allocate(
const V1_3::BufferDesc& desc,
const hardware::hidl_vec<sp<V1_3::IPreparedModel>>& preparedModels,
const hardware::hidl_vec<V1_3::BufferRole>& inputRoles,
const hardware::hidl_vec<V1_3::BufferRole>& outputRoles, allocate_cb cb) override;
private:
std::vector<bool> getSupportedOperationsImpl(const V1_3::Model& model) const override;
};
template <typename T_Model, typename T_IPreparedModelCallback>
V1_3::ErrorStatus prepareModelXNNPACK(const T_Model& model, const SampleDriver* driver,
V1_1::ExecutionPreference preference, V1_3::Priority priority,
const V1_3::OptionalTimePoint& deadline,
const sp<T_IPreparedModelCallback>& callback) {
const uid_t userId = hardware::IPCThreadState::self()->getCallingUid();
if (callback.get() == nullptr) {
LOG(ERROR) << "invalid callback passed to prepareModelBase";
return V1_3::ErrorStatus::INVALID_ARGUMENT;
}
if (VLOG_IS_ON(DRIVER)) {
VLOG(DRIVER) << "prepareModelBase";
logModelToInfo(model);
}
if (!validateModel(model) || !validateExecutionPreference(preference) ||
!validatePriority(priority)) {
notify(callback, V1_3::ErrorStatus::INVALID_ARGUMENT, nullptr);
return V1_3::ErrorStatus::INVALID_ARGUMENT;
}
// asynchronously prepare the model from a new, detached thread
std::thread([model, driver, preference, userId, priority, callback] {
sp<SamplePreparedModelXNNPACK> preparedModel = new SamplePreparedModelXNNPACK(
convertToV1_3(model), driver, preference, userId, priority);
if (!preparedModel->initialize()) {
notify(callback, V1_3::ErrorStatus::INVALID_ARGUMENT, nullptr);
return;
}
notify(callback, V1_3::ErrorStatus::NONE, preparedModel);
}).detach();
return V1_3::ErrorStatus::NONE;
}
hardware::Return<V1_0::ErrorStatus> SampleDriverFloatXNNPACK::prepareModel(
const V1_0::Model& model, const sp<V1_0::IPreparedModelCallback>& callback) {
const V1_3::ErrorStatus status =
prepareModelXNNPACK(model, this, V1_1::ExecutionPreference::FAST_SINGLE_ANSWER,
kDefaultPriority13, {}, callback);
return convertToV1_0(status);
}
hardware::Return<V1_0::ErrorStatus> SampleDriverFloatXNNPACK::prepareModel_1_1(
const V1_1::Model& model, V1_1::ExecutionPreference preference,
const sp<V1_0::IPreparedModelCallback>& callback) {
const V1_3::ErrorStatus status =
prepareModelXNNPACK(model, this, preference, kDefaultPriority13, {}, callback);
return convertToV1_0(status);
}
hardware::Return<V1_0::ErrorStatus> SampleDriverFloatXNNPACK::prepareModel_1_2(
const V1_2::Model& model, V1_1::ExecutionPreference preference,
const hardware::hidl_vec<hardware::hidl_handle>&,
const hardware::hidl_vec<hardware::hidl_handle>&, const HalCacheToken&,
const sp<V1_2::IPreparedModelCallback>& callback) {
const V1_3::ErrorStatus status =
prepareModelXNNPACK(model, this, preference, kDefaultPriority13, {}, callback);
return convertToV1_0(status);
}
hardware::Return<V1_3::ErrorStatus> SampleDriverFloatXNNPACK::prepareModel_1_3(
const V1_3::Model& model, V1_1::ExecutionPreference preference, V1_3::Priority priority,
const V1_3::OptionalTimePoint& deadline,
const hardware::hidl_vec<hardware::hidl_handle>& modelCache,
const hardware::hidl_vec<hardware::hidl_handle>& dataCache, const HalCacheToken& token,
const sp<V1_3::IPreparedModelCallback>& callback) {
return prepareModelXNNPACK(model, this, preference, priority, deadline, callback);
}
hardware::Return<void> SampleDriverFloatXNNPACK::getCapabilities_1_3(getCapabilities_1_3_cb cb) {
android::nn::initVLogMask();
VLOG(DRIVER) << "SampleDriverFloatXNNPACK::getCapabilities()";
V1_3::Capabilities capabilities = {
.relaxedFloat32toFloat16PerformanceScalar = {.execTime = 0.7f, .powerUsage = 1.1f},
.relaxedFloat32toFloat16PerformanceTensor = {.execTime = 0.7f, .powerUsage = 1.1f},
.operandPerformance = nonExtensionOperandPerformance<HalVersion::V1_3>({1.0f, 1.0f}),
.ifPerformance = {.execTime = 1.0f, .powerUsage = 1.0f},
.whilePerformance = {.execTime = 1.0f, .powerUsage = 1.0f}};
update(&capabilities.operandPerformance, V1_3::OperandType::TENSOR_FLOAT32,
{.execTime = 0.8f, .powerUsage = 1.2f});
update(&capabilities.operandPerformance, V1_3::OperandType::FLOAT32,
{.execTime = 0.8f, .powerUsage = 1.2f});
cb(V1_3::ErrorStatus::NONE, capabilities);
return hardware::Void();
}
std::vector<bool> SampleDriverFloatXNNPACK::getSupportedOperationsImpl(
const V1_3::Model& model) const {
std::vector<RunTimePoolInfo> poolInfos;
setRunTimePoolInfosFromCanonicalMemories(&poolInfos, uncheckedConvert(model.pools));
auto operands = initializeRunTimeInfo(model.main, poolInfos, &model.operandValues);
const size_t count = model.main.operations.size();
std::vector<bool> supported(count);
for (size_t i = 0; i < count; i++) {
bool isSupportedOp = false;
const V1_3::Operation& operation = model.main.operations[i];
if (Subgraph::VisitNode(/*subgraph=*/nullptr, operation, operands.data(), {}) ==
V1_3::ErrorStatus::NONE) {
isSupportedOp = true;
}
supported[i] = isSupportedOp;
}
return supported;
}
hardware::Return<void> SampleDriverFloatXNNPACK::allocate(
const V1_3::BufferDesc& desc,
const hardware::hidl_vec<sp<V1_3::IPreparedModel>>& preparedModels,
const hardware::hidl_vec<V1_3::BufferRole>& inputRoles,
const hardware::hidl_vec<V1_3::BufferRole>& outputRoles, allocate_cb cb) {
VLOG(DRIVER) << "SampleDriverFloatXNNPACK::allocate not supported";
constexpr uint32_t kInvalidBufferToken = 0;
cb(V1_3::ErrorStatus::INVALID_ARGUMENT, nullptr, kInvalidBufferToken);
return hardware::Void();
}
} // namespace sample_driver
} // namespace nn
} // namespace android
using android::sp;
using android::nn::sample_driver::SampleDriverFloatXNNPACK;
int main() {
sp<SampleDriverFloatXNNPACK> driver(new SampleDriverFloatXNNPACK());
xnn_status status = xnn_initialize(/*allocator=*/nullptr);
if (status != xnn_status_success) {
return 0;
}
return driver->run();
}