/* * Copyright (C) 2017 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 "android.hardware.neuralnetworks@1.0-impl-hvx" #include "HexagonModel.h" #include "HexagonOperations.h" #include "OperationsUtils.h" namespace android { namespace hardware { namespace neuralnetworks { namespace V1_0 { namespace implementation { namespace hexagon { using android::nn::Shape; namespace { bool addMul(const std::vector& ins, const std::vector& outs, HexagonModel* model, OperationType op) { HEXAGON_SOFT_ASSERT_EQ(3, ins.size(), "Need 3 inputs for " << toString(op)); HEXAGON_SOFT_ASSERT_EQ(1, outs.size(), "Need 1 output for " << toString(op)); // get output size const Shape in1Shape = model->getShape(ins[0]); const Shape in2Shape = model->getShape(ins[1]); Shape outShape = model->getShape(outs[0]); HEXAGON_SOFT_ASSERT(addMulPrepare(in1Shape, in2Shape, &outShape), "Error getting shape"); HEXAGON_SOFT_ASSERT(model->setShape(outs[0], outShape), "Error setting shape"); return true; } bool add(const std::vector& ins, const std::vector& outs, HexagonModel* model) { return addMul(ins, outs, model, OperationType::ADD); } bool mul(const std::vector& ins, const std::vector& outs, HexagonModel* model) { return addMul(ins, outs, model, OperationType::MUL); } bool pool(const std::vector& ins, const std::vector& outs, HexagonModel* model, OperationType op) { HEXAGON_SOFT_ASSERT(ins.size() == 10 || ins.size() == 7, "Need 7 or 10 inputs for " << toString(op)); // get parameters const Shape inShape = model->getShape(ins[0]); // setup parameters int32_t padding_left; int32_t padding_right; int32_t padding_top; int32_t padding_bottom; int32_t stride_width; int32_t stride_height; int32_t filter_width; int32_t filter_height; // get parameters if (ins.size() == 10) { padding_left = model->getScalar(ins[1]); padding_right = model->getScalar(ins[2]); padding_top = model->getScalar(ins[3]); padding_bottom = model->getScalar(ins[4]); stride_width = model->getScalar(ins[5]); stride_height = model->getScalar(ins[6]); filter_width = model->getScalar(ins[7]); filter_height = model->getScalar(ins[8]); HEXAGON_SOFT_ASSERT_NE(getPadding(inShape.dimensions[2], inShape.dimensions[1], stride_width, stride_height, filter_width, filter_height, padding_left, padding_right, padding_top, padding_bottom), NN_PAD_NA, "Unknown padding"); } else { const int32_t padding_implicit = model->getScalar(ins[1]); stride_width = model->getScalar(ins[2]); stride_height = model->getScalar(ins[3]); filter_width = model->getScalar(ins[4]); filter_height = model->getScalar(ins[5]); nn::calculateExplicitPadding(inShape.dimensions[2], stride_width, filter_width, padding_implicit, &padding_left, &padding_right); nn::calculateExplicitPadding(inShape.dimensions[1], stride_height, filter_height, padding_implicit, &padding_top, &padding_bottom); } // get output size Shape outShape = model->getShape(outs[0]); HEXAGON_SOFT_ASSERT( genericPoolingPrepare(inShape, padding_left, padding_right, padding_top, padding_bottom, stride_width, stride_height, filter_width, filter_height, &outShape), "Error getting shape"); HEXAGON_SOFT_ASSERT(model->setShape(outs[0], outShape), "Error setting shape"); return true; } bool average_pool_2d(const std::vector& ins, const std::vector& outs, HexagonModel* model) { return pool(ins, outs, model, OperationType::AVERAGE_POOL_2D); } bool l2_pool_2d(const std::vector& ins, const std::vector& outs, HexagonModel* model) { return pool(ins, outs, model, OperationType::L2_POOL_2D); } bool max_pool_2d(const std::vector& ins, const std::vector& outs, HexagonModel* model) { return pool(ins, outs, model, OperationType::MAX_POOL_2D); } bool concatenation(const std::vector& ins, const std::vector& outs, HexagonModel* model) { std::string name = toString(OperationType::CONCATENATION); HEXAGON_SOFT_ASSERT_LE(3, ins.size(), "Need at least 3 inputs for " << name); HEXAGON_SOFT_ASSERT_EQ(1, outs.size(), "Need 1 output for " << name); const size_t numInputTensors = ins.size() - 1; const int32_t axis = model->getScalar(ins[numInputTensors]); // get output size std::vector inShapes(numInputTensors); for (size_t i = 0; i < numInputTensors; ++i) { inShapes[i] = model->getShape(ins[i]); } Shape outShape = model->getShape(outs[0]); HEXAGON_SOFT_ASSERT(concatenationPrepare(inShapes, axis, &outShape), "Error getting shape"); HEXAGON_SOFT_ASSERT(model->setShape(outs[0], outShape), "Error setting shape"); return true; } bool conv_2d(const std::vector& ins, const std::vector& outs, HexagonModel* model) { std::string name = toString(OperationType::CONV_2D); HEXAGON_SOFT_ASSERT(ins.size() == 10 || ins.size() == 7, "Need 7 or 10 inputs for " << name); HEXAGON_SOFT_ASSERT_EQ(1, outs.size(), "Need 1 output for " << name); // setup shapes const Shape inputShape = model->getShape(ins[0]); const Shape filterShape = model->getShape(ins[1]); const Shape biasShape = model->getShape(ins[2]); // setup parameters int32_t padding_left; int32_t padding_right; int32_t padding_top; int32_t padding_bottom; int32_t stride_width; int32_t stride_height; // get parameters if (ins.size() == 10) { padding_left = model->getScalar(ins[3]); padding_right = model->getScalar(ins[4]); padding_top = model->getScalar(ins[5]); padding_bottom = model->getScalar(ins[6]); stride_width = model->getScalar(ins[7]); stride_height = model->getScalar(ins[8]); HEXAGON_SOFT_ASSERT_NE( getPadding(inputShape.dimensions[2], inputShape.dimensions[1], stride_width, stride_height, filterShape.dimensions[2], filterShape.dimensions[1], padding_left, padding_right, padding_top, padding_bottom), NN_PAD_NA, "Unknown padding"); } else { const int32_t padding_implicit = model->getScalar(ins[3]); stride_width = model->getScalar(ins[4]); stride_height = model->getScalar(ins[5]); nn::calculateExplicitPadding(inputShape.dimensions[2], stride_width, filterShape.dimensions[2], padding_implicit, &padding_left, &padding_right); nn::calculateExplicitPadding(inputShape.dimensions[1], stride_height, filterShape.dimensions[1], padding_implicit, &padding_top, &padding_bottom); } // get output size Shape outShape = model->getShape(outs[0]); HEXAGON_SOFT_ASSERT( convPrepare(inputShape, filterShape, biasShape, padding_left, padding_right, padding_top, padding_bottom, stride_width, stride_height, &outShape), "Error getting shape"); HEXAGON_SOFT_ASSERT(model->setShape(outs[0], outShape), "Error setting shape"); // enforce filter is a constant HEXAGON_SOFT_ASSERT(model->isConstant(ins[1]), name << "requires filter to be constant data"); return true; } bool depthwise_conv_2d(const std::vector& ins, const std::vector& outs, HexagonModel* model) { std::string name = toString(OperationType::DEPTHWISE_CONV_2D); HEXAGON_SOFT_ASSERT(ins.size() == 8 || ins.size() == 11, "Need 8 or 11 inputs for " << name); HEXAGON_SOFT_ASSERT_EQ(1, outs.size(), "Need 1 output for " << name); // setup shapes const Shape inputShape = model->getShape(ins[0]); const Shape filterShape = model->getShape(ins[1]); const Shape biasShape = model->getShape(ins[2]); // setup parameters int32_t padding_left; int32_t padding_right; int32_t padding_top; int32_t padding_bottom; int32_t stride_width; int32_t stride_height; // get parameters if (ins.size() == 11) { padding_left = model->getScalar(ins[3]); padding_right = model->getScalar(ins[4]); padding_top = model->getScalar(ins[5]); padding_bottom = model->getScalar(ins[6]); stride_width = model->getScalar(ins[7]); stride_height = model->getScalar(ins[8]); HEXAGON_SOFT_ASSERT_NE( getPadding(inputShape.dimensions[2], inputShape.dimensions[1], stride_width, stride_height, filterShape.dimensions[2], filterShape.dimensions[1], padding_left, padding_right, padding_top, padding_bottom), NN_PAD_NA, "Unknown padding"); } else { const int32_t padding_implicit = model->getScalar(ins[3]); stride_width = model->getScalar(ins[4]); stride_height = model->getScalar(ins[5]); nn::calculateExplicitPadding(inputShape.dimensions[2], stride_width, filterShape.dimensions[2], padding_implicit, &padding_left, &padding_right); nn::calculateExplicitPadding(inputShape.dimensions[1], stride_height, filterShape.dimensions[1], padding_implicit, &padding_top, &padding_bottom); } // get output size Shape outShape = model->getShape(outs[0]); HEXAGON_SOFT_ASSERT( depthwiseConvPrepare(inputShape, filterShape, biasShape, padding_left, padding_right, padding_top, padding_bottom, stride_width, stride_height, &outShape), "Error getting shape"); HEXAGON_SOFT_ASSERT(model->setShape(outs[0], outShape), "Error setting shape"); // enforce filter is a constant HEXAGON_SOFT_ASSERT(model->isConstant(ins[1]), name << " requires filter to be constant data"); return true; } bool dequantize(const std::vector& ins, const std::vector& outs, HexagonModel* model) { std::string name = toString(OperationType::DEQUANTIZE); HEXAGON_SOFT_ASSERT_EQ(1, ins.size(), "Need 1 input for " << name); HEXAGON_SOFT_ASSERT_EQ(1, outs.size(), "Need 1 output for " << name); // get output size const Shape inputShape = model->getShape(ins[0]); Shape outShape = model->getShape(outs[0]); HEXAGON_SOFT_ASSERT(dequantizePrepare(inputShape, &outShape), "Error getting shape"); HEXAGON_SOFT_ASSERT(model->setShape(outs[0], outShape), "Error setting shape"); return true; } bool fully_connected(const std::vector& ins, const std::vector& outs, HexagonModel* model) { std::string name = toString(OperationType::FULLY_CONNECTED); HEXAGON_SOFT_ASSERT_EQ(4, ins.size(), "Need 4 inputs for " << name); HEXAGON_SOFT_ASSERT_EQ(1, outs.size(), "Need 1 output for " << name); // get output size const Shape inputShape = model->getShape(ins[0]); const Shape weightsShape = model->getShape(ins[1]); const Shape biasShape = model->getShape(ins[2]); Shape outShape = model->getShape(outs[0]); HEXAGON_SOFT_ASSERT(fullyConnectedPrepare(inputShape, weightsShape, biasShape, &outShape), "Error getting shape"); HEXAGON_SOFT_ASSERT(model->setShape(outs[0], outShape), "Error setting shape"); // enforce weight is a constant HEXAGON_SOFT_ASSERT(model->isConstant(ins[1]), name << "requires weight to be constant data"); return true; } bool local_response_normalization(const std::vector& ins, const std::vector& outs, HexagonModel* model) { std::string name = toString(OperationType::LOCAL_RESPONSE_NORMALIZATION); HEXAGON_SOFT_ASSERT_EQ(5, ins.size(), "Need 5 inputs for " << name); HEXAGON_SOFT_ASSERT_EQ(1, outs.size(), "Need 1 output for " << name); // get output size const Shape inShape = model->getShape(ins[0]); Shape outShape = model->getShape(outs[0]); HEXAGON_SOFT_ASSERT(genericNormalizationPrepare(inShape, &outShape), "Error getting shape"); HEXAGON_SOFT_ASSERT(model->setShape(outs[0], outShape), "Error setting shape"); return true; } bool activation(const std::vector& ins, const std::vector& outs, HexagonModel* model, uint32_t numInputs, OperationType op) { HEXAGON_SOFT_ASSERT_EQ(numInputs, ins.size(), "Need " << numInputs << " input for " << toString(op)); HEXAGON_SOFT_ASSERT_EQ(1, outs.size(), "Need 1 output for " << toString(op)); // get output size const Shape inShape = model->getShape(ins[0]); Shape outShape = model->getShape(outs[0]); HEXAGON_SOFT_ASSERT(genericActivationPrepare(inShape, &outShape), "Error getting shape"); HEXAGON_SOFT_ASSERT(model->setShape(outs[0], outShape), "Error setting shape"); return true; } bool logistic(const std::vector& ins, const std::vector& outs, HexagonModel* model) { return activation(ins, outs, model, 1, OperationType::LOGISTIC); } bool relu(const std::vector& ins, const std::vector& outs, HexagonModel* model) { return activation(ins, outs, model, 1, OperationType::RELU); } bool relu1(const std::vector& ins, const std::vector& outs, HexagonModel* model) { return activation(ins, outs, model, 1, OperationType::RELU1); } bool relu6(const std::vector& ins, const std::vector& outs, HexagonModel* model) { return activation(ins, outs, model, 1, OperationType::RELU6); } bool softmax(const std::vector& ins, const std::vector& outs, HexagonModel* model) { return activation(ins, outs, model, 2, OperationType::SOFTMAX); } bool tanh(const std::vector& ins, const std::vector& outs, HexagonModel* model) { return activation(ins, outs, model, 1, OperationType::TANH); } bool reshape(const std::vector& ins, const std::vector& outs, HexagonModel* model) { std::string name = toString(OperationType::RESHAPE); HEXAGON_SOFT_ASSERT_EQ(2, ins.size(), "Need 2 inputs for " << name); HEXAGON_SOFT_ASSERT_EQ(1, outs.size(), "Need 1 output for " << name); // get output size const Shape inShape = model->getShape(ins[0]); const Shape targetShape = model->getShape(ins[1]); const int32_t* targetShapePtr = model->getPointer(ins[1]); int32_t targetShapeNumElem = ::android::nn::getNumberOfElements(targetShape); Shape outShape = model->getShape(outs[0]); HEXAGON_SOFT_ASSERT(targetShapePtr != nullptr, "pointer value is currently nullptr"); HEXAGON_SOFT_ASSERT(reshapePrepare(inShape, targetShapePtr, targetShapeNumElem, &outShape), "Error getting shape"); HEXAGON_SOFT_ASSERT(model->setShape(outs[0], outShape), "Error setting shape"); return true; } bool resize_bilinear(const std::vector& ins, const std::vector& outs, HexagonModel* model) { std::string name = toString(OperationType::RESIZE_BILINEAR); HEXAGON_SOFT_ASSERT_EQ(3, ins.size(), "Need 3 inputs for " << name); HEXAGON_SOFT_ASSERT_EQ(1, outs.size(), "Need 1 output for " << name); // get parameters const int32_t width = model->getScalar(ins[1]); const int32_t height = model->getScalar(ins[2]); // get output size const Shape inShape = model->getShape(ins[0]); Shape outShape = model->getShape(outs[0]); HEXAGON_SOFT_ASSERT(resizeBilinearPrepare(inShape, width, height, &outShape), "Error getting shape"); HEXAGON_SOFT_ASSERT(model->setShape(outs[0], outShape), "Error setting shape"); return true; } } // namespace OperationTable& getOperationCheckTable() { static OperationTable table = { // NOTE: the operations that are commented out via inline represent // operations that are valid for the Android O NNAPI release, but are // currently not implemented in HVX. // -------------------------- 32-BIT FLOAT ---------------------------- // HVX is only performant when running on quantized values. Further, as // an optimization, the current HVX driver will convert some floating // point tensors into quantized values, perform the operation, and then // convert them back to floating point. This results in a loss in // precision causing some tests to fail. For these reasons, the FLOAT32 // operations are being temporarily disabled. /* {{OperationType::ADD, OperandType::TENSOR_FLOAT32}, add}, {{OperationType::AVERAGE_POOL_2D, OperandType::TENSOR_FLOAT32}, average_pool_2d}, {{OperationType::CONCATENATION, OperandType::TENSOR_FLOAT32}, concatenation}, {{OperationType::CONV_2D, OperandType::TENSOR_FLOAT32}, conv_2d}, {{OperationType::DEPTHWISE_CONV_2D, OperandType::TENSOR_FLOAT32}, depthwise_conv_2d}, //{{OperationType::DEPTH_TO_SPACE, OperandType::TENSOR_FLOAT32}, depth_to_space}, //{{OperationType::EMBEDDING_LOOKUP, OperandType::TENSOR_FLOAT32}, embedding_lookup}, //{{OperationType::FLOOR, OperandType::TENSOR_FLOAT32}, floor}, {{OperationType::FULLY_CONNECTED, OperandType::TENSOR_FLOAT32}, fully_connected}, //{{OperationType::HASHTABLE_LOOKUP, OperandType::TENSOR_FLOAT32}, hashtable_lookup}, //{{OperationType::L2_NORMALIZATION, OperandType::TENSOR_FLOAT32}, l2_normalization}, {{OperationType::L2_POOL_2D, OperandType::TENSOR_FLOAT32}, l2_pool_2d}, {{OperationType::LOCAL_RESPONSE_NORMALIZATION, OperandType::TENSOR_FLOAT32}, local_response_normalization}, {{OperationType::LOGISTIC, OperandType::TENSOR_FLOAT32}, logistic}, //{{OperationType::LSH_PROJECTION, OperandType::TENSOR_FLOAT32}, lsh_projection}, //{{OperationType::LSTM, OperandType::TENSOR_FLOAT32}, lstm }, {{OperationType::MAX_POOL_2D, OperandType::TENSOR_FLOAT32}, max_pool_2d}, {{OperationType::MUL, OperandType::TENSOR_FLOAT32}, mul}, {{OperationType::RELU, OperandType::TENSOR_FLOAT32}, relu}, {{OperationType::RELU1, OperandType::TENSOR_FLOAT32}, relu1}, {{OperationType::RELU6, OperandType::TENSOR_FLOAT32}, relu6}, {{OperationType::RESHAPE, OperandType::TENSOR_FLOAT32}, reshape}, {{OperationType::RESIZE_BILINEAR, OperandType::TENSOR_FLOAT32}, resize_bilinear}, //{{OperationType::RNN, OperandType::TENSOR_FLOAT32}, rnn}, {{OperationType::SOFTMAX, OperandType::TENSOR_FLOAT32}, softmax}, //{{OperationType::SPACE_TO_DEPTH, OperandType::TENSOR_FLOAT32}, space_to_depth}, //{{OperationType::SVDF, OperandType::TENSOR_FLOAT32}, svdf }, {{OperationType::TANH, OperandType::TENSOR_FLOAT32}, tanh}, */ // -------------------- QUANTIZED 8-BIT ASYMMETRICAL ------------------ {{OperationType::ADD, OperandType::TENSOR_QUANT8_ASYMM}, add}, {{OperationType::AVERAGE_POOL_2D, OperandType::TENSOR_QUANT8_ASYMM}, average_pool_2d}, {{OperationType::CONCATENATION, OperandType::TENSOR_QUANT8_ASYMM}, concatenation}, {{OperationType::CONV_2D, OperandType::TENSOR_QUANT8_ASYMM}, conv_2d}, {{OperationType::DEPTHWISE_CONV_2D, OperandType::TENSOR_QUANT8_ASYMM}, depthwise_conv_2d}, //{{OperationType::DEPTH_TO_SPACE, OperandType::TENSOR_QUANT8_ASYMM}, depth_to_space}, {{OperationType::DEQUANTIZE, OperandType::TENSOR_QUANT8_ASYMM}, dequantize}, //{{OperationType::EMBEDDING_LOOKUP, OperandType::TENSOR_QUANT8_ASYMM}, embedding_lookup}, {{OperationType::FULLY_CONNECTED, OperandType::TENSOR_QUANT8_ASYMM}, fully_connected}, //{{OperationType::HASHTABLE_LOOKUP, OperandType::TENSOR_QUANT8_ASYMM}, hashtable_lookup}, {{OperationType::LOGISTIC, OperandType::TENSOR_QUANT8_ASYMM}, logistic}, //{{OperationType::LSH_PROJECTION, OperandType::TENSOR_QUANT8_ASYMM}, lsh_projection}, {{OperationType::MAX_POOL_2D, OperandType::TENSOR_QUANT8_ASYMM}, max_pool_2d}, {{OperationType::MUL, OperandType::TENSOR_QUANT8_ASYMM}, mul}, {{OperationType::RELU, OperandType::TENSOR_QUANT8_ASYMM}, relu}, {{OperationType::RELU1, OperandType::TENSOR_QUANT8_ASYMM}, relu1}, {{OperationType::RELU6, OperandType::TENSOR_QUANT8_ASYMM}, relu6}, {{OperationType::RESHAPE, OperandType::TENSOR_QUANT8_ASYMM}, reshape}, {{OperationType::SOFTMAX, OperandType::TENSOR_QUANT8_ASYMM}, softmax}, //{{OperationType::SPACE_TO_DEPTH, OperandType::TENSOR_QUANT8_ASYMM}, space_to_depth}, }; // The following functions are normally used by float32, but those // operations have been temporarily disabled. Void explicitly marks them as // unused, and prevents the compiler from throwing an error. (void)l2_pool_2d; (void)local_response_normalization; (void)tanh; (void)resize_bilinear; return table; } } // namespace hexagon } // namespace implementation } // namespace V1_0 } // namespace neuralnetworks } // namespace hardware } // namespace android