Matthew Sloyan | c5fe6e7 | 2022-11-25 16:10:00 +0000 | [diff] [blame] | 1 | // |
Teresa Charlin | 571a4f7 | 2024-03-26 11:18:42 +0000 | [diff] [blame] | 2 | // Copyright © 2022-2024 Arm Ltd and Contributors. All rights reserved. |
Matthew Sloyan | c5fe6e7 | 2022-11-25 16:10:00 +0000 | [diff] [blame] | 3 | // SPDX-License-Identifier: MIT |
| 4 | // |
| 5 | |
| 6 | #include "Conv2dOperator.hpp" |
John Mcloughlin | ceb4428 | 2024-04-23 16:47:04 +0100 | [diff] [blame] | 7 | #include "TosaRescaleOperatorUtils.hpp" |
| 8 | #include <ResolveType.hpp> |
Matthew Sloyan | c5fe6e7 | 2022-11-25 16:10:00 +0000 | [diff] [blame] | 9 | |
| 10 | TosaSerializationBasicBlock* ConvertConv2dToTosaOperator(const Layer* layer, |
| 11 | const std::vector<const TensorInfo*>& inputs, |
| 12 | const std::vector<const TensorInfo*>& outputs, |
| 13 | const Convolution2dDescriptor* conv2dDescriptor) |
| 14 | { |
| 15 | std::vector<std::string> inputNames; |
| 16 | std::string outputName = std::string("output0_"); |
| 17 | std::string blockName = std::string("Op_CONV2D_block_") + GetUniqueTosaMappingID(); |
| 18 | |
John Mcloughlin | ceb4428 | 2024-04-23 16:47:04 +0100 | [diff] [blame] | 19 | DType inputDType0 = ArmNNToDType(inputs[0]->GetDataType()); |
| 20 | DType outputDType0 = ArmNNToDType(outputs[0]->GetDataType()); |
| 21 | |
Matthew Sloyan | c5fe6e7 | 2022-11-25 16:10:00 +0000 | [diff] [blame] | 22 | // Set input names for validation purposes only. |
| 23 | if(layer == nullptr) |
| 24 | { |
| 25 | inputNames.emplace_back("input0_"); |
| 26 | inputNames.emplace_back("input1_"); |
| 27 | if(conv2dDescriptor->m_BiasEnabled) |
| 28 | { |
| 29 | inputNames.emplace_back("input2_"); |
| 30 | } |
| 31 | } |
Kevin May | 5b58e31 | 2022-12-15 10:15:21 +0000 | [diff] [blame] | 32 | // If a layer is present then the block will be used for execution, so input and output names need to be |
| 33 | // determined using the previous and following layers so the graph is connected correctly. |
| 34 | // For validation this doesn't matter. |
Matthew Sloyan | c5fe6e7 | 2022-11-25 16:10:00 +0000 | [diff] [blame] | 35 | else |
| 36 | { |
Kevin May | 5b58e31 | 2022-12-15 10:15:21 +0000 | [diff] [blame] | 37 | // Get the layer connected to the input slot and determine unique tensor names. |
Matthew Sloyan | c5fe6e7 | 2022-11-25 16:10:00 +0000 | [diff] [blame] | 38 | for (uint32_t i = 0; i < inputs.size(); ++i) |
| 39 | { |
Matthew Sloyan | c5fe6e7 | 2022-11-25 16:10:00 +0000 | [diff] [blame] | 40 | Layer& connectedLayer = layer->GetInputSlot(i).GetConnectedOutputSlot()->GetOwningLayer(); |
| 41 | |
| 42 | std::string inputName = GenerateUniqueName(connectedLayer, i); |
| 43 | inputNames.push_back(inputName); |
| 44 | } |
| 45 | |
Kevin May | 5b58e31 | 2022-12-15 10:15:21 +0000 | [diff] [blame] | 46 | // Determine unique output tensor name. |
Matthew Sloyan | da6bf9e | 2022-12-14 10:16:27 +0000 | [diff] [blame] | 47 | outputName = GenerateUniqueOutputName(*layer, 0); |
Matthew Sloyan | c5fe6e7 | 2022-11-25 16:10:00 +0000 | [diff] [blame] | 48 | } |
| 49 | |
| 50 | std::vector<TosaSerializationTensor*> tensors; |
| 51 | std::vector<TosaSerializationOperator*> operators; |
| 52 | |
| 53 | // Setup input Tensor |
Matthew Sloyan | da6bf9e | 2022-12-14 10:16:27 +0000 | [diff] [blame] | 54 | // Only add tensor if connected layer is an input layer. |
| 55 | // As intermediate or constant tensors will be created separately. |
| 56 | // There also can't be duplicate tensors. |
| 57 | if(inputNames[0].find("input0_") != std::string::npos) |
| 58 | { |
| 59 | std::vector<int32_t> inputShape0 = GetTosaTensorShape(inputs[0]->GetShape()); |
Matthew Sloyan | c5fe6e7 | 2022-11-25 16:10:00 +0000 | [diff] [blame] | 60 | |
Matthew Sloyan | da6bf9e | 2022-12-14 10:16:27 +0000 | [diff] [blame] | 61 | tensors.push_back(new TosaSerializationTensor(inputNames[0], inputShape0, inputDType0, {})); |
| 62 | } |
Matthew Sloyan | c5fe6e7 | 2022-11-25 16:10:00 +0000 | [diff] [blame] | 63 | |
| 64 | // Only add input tensors if weights and bias are not constant or if running validation. |
| 65 | // Constant tensors will be created in the ConvertConstantToTosaOperator function. |
| 66 | if(!inputs[1]->IsConstant() || layer == nullptr) |
| 67 | { |
| 68 | std::vector<int32_t> inputShape1 = GetTosaTensorShape(inputs[1]->GetShape()); |
| 69 | DType inputDType1 = ArmNNToDType(inputs[1]->GetDataType()); |
| 70 | |
| 71 | tensors.push_back(new TosaSerializationTensor(inputNames[1], inputShape1, inputDType1, {})); |
| 72 | } |
| 73 | |
| 74 | if(conv2dDescriptor->m_BiasEnabled) |
| 75 | { |
| 76 | if(!inputs[2]->IsConstant() || layer == nullptr) |
| 77 | { |
| 78 | std::vector<int32_t> inputShape2 = GetTosaTensorShape(inputs[2]->GetShape()); |
| 79 | DType inputDType2 = ArmNNToDType(inputs[2]->GetDataType()); |
| 80 | |
| 81 | tensors.push_back(new TosaSerializationTensor(inputNames[2], inputShape2, inputDType2, {})); |
| 82 | } |
| 83 | } |
| 84 | else |
| 85 | { |
| 86 | // If bias is disabled, create a constant bias of 0 as three inputs are required. |
| 87 | std::string constantName = std::string("constant_") + GetUniqueTosaMappingID(); |
| 88 | |
| 89 | operators.push_back(new TosaSerializationOperator(Op_CONST, Attribute_NONE, nullptr, {}, {constantName})); |
| 90 | |
Matthew Sloyan | da6bf9e | 2022-12-14 10:16:27 +0000 | [diff] [blame] | 91 | // The size of the bias must match the channels dimension, so get the correct index. |
| 92 | unsigned int index = (conv2dDescriptor->m_DataLayout == DataLayout::NHWC) ? 3 : 1; |
| 93 | |
John Mcloughlin | ceb4428 | 2024-04-23 16:47:04 +0100 | [diff] [blame] | 94 | const DType dType = (inputDType0 == DType_INT8) ? DType_INT32 : outputDType0; |
| 95 | std::vector<float> data(outputs[0]->GetShape()[index], 0); |
Matthew Sloyan | c5fe6e7 | 2022-11-25 16:10:00 +0000 | [diff] [blame] | 96 | |
John Mcloughlin | ceb4428 | 2024-04-23 16:47:04 +0100 | [diff] [blame] | 97 | std::vector<uint8_t> uint8Data; |
Matthew Sloyan | c5fe6e7 | 2022-11-25 16:10:00 +0000 | [diff] [blame] | 98 | TosaSerializationHandler::ConvertF32toU8(data, uint8Data); |
| 99 | |
Matthew Sloyan | da6bf9e | 2022-12-14 10:16:27 +0000 | [diff] [blame] | 100 | tensors.push_back(new TosaSerializationTensor(constantName, |
| 101 | {static_cast<int32_t>(outputs[0]->GetShape()[index])}, |
John Mcloughlin | ceb4428 | 2024-04-23 16:47:04 +0100 | [diff] [blame] | 102 | dType, |
Matthew Sloyan | da6bf9e | 2022-12-14 10:16:27 +0000 | [diff] [blame] | 103 | uint8Data)); |
Matthew Sloyan | c5fe6e7 | 2022-11-25 16:10:00 +0000 | [diff] [blame] | 104 | inputNames.emplace_back(constantName); |
| 105 | } |
| 106 | |
| 107 | // Setup Output Tensor |
John Mcloughlin | ceb4428 | 2024-04-23 16:47:04 +0100 | [diff] [blame] | 108 | std::vector<int32_t> outputShape0 = {GetTosaTensorShape(outputs[0]->GetShape())}; |
| 109 | std::string outputConv2dName; |
| 110 | bool isInputInt8 = (inputDType0 == DType_INT8); |
| 111 | if (isInputInt8) |
| 112 | { |
| 113 | outputConv2dName = std::string("intermediate0_") + GetUniqueTosaMappingID(); |
| 114 | tensors.push_back(new TosaSerializationTensor(outputConv2dName, outputShape0, DType_INT32, {})); |
| 115 | } |
| 116 | else |
| 117 | { |
| 118 | tensors.push_back(new TosaSerializationTensor(outputName, outputShape0, outputDType0, {})); |
| 119 | } |
Matthew Sloyan | c5fe6e7 | 2022-11-25 16:10:00 +0000 | [diff] [blame] | 120 | |
| 121 | // Set up CONV2D operator |
| 122 | std::vector<int> pad = {static_cast<int>(conv2dDescriptor->m_PadTop), |
| 123 | static_cast<int>(conv2dDescriptor->m_PadBottom), |
| 124 | static_cast<int>(conv2dDescriptor->m_PadLeft), |
| 125 | static_cast<int>(conv2dDescriptor->m_PadRight)}; |
| 126 | std::vector<int> stride = {static_cast<int>(conv2dDescriptor->m_StrideY), |
| 127 | static_cast<int>(conv2dDescriptor->m_StrideX)}; |
| 128 | std::vector<int> dilation = {static_cast<int>(conv2dDescriptor->m_DilationY), |
| 129 | static_cast<int>(conv2dDescriptor->m_DilationX)}; |
John Mcloughlin | ceb4428 | 2024-04-23 16:47:04 +0100 | [diff] [blame] | 130 | TosaConvAttribute attribute(pad, stride, dilation, |
| 131 | inputs[0]->GetQuantizationOffset(), // input_zp |
| 132 | inputs[1]->GetQuantizationOffset(), // weight_zp |
| 133 | false); // local_bound |
Matthew Sloyan | c5fe6e7 | 2022-11-25 16:10:00 +0000 | [diff] [blame] | 134 | |
John Mcloughlin | ceb4428 | 2024-04-23 16:47:04 +0100 | [diff] [blame] | 135 | std::string& convOutStr = isInputInt8 ? outputConv2dName : outputName; |
| 136 | auto* conv2d_op = new TosaSerializationOperator(Op_CONV2D, |
| 137 | Attribute_ConvAttribute, |
| 138 | &attribute, |
| 139 | inputNames, |
| 140 | {convOutStr}); |
| 141 | operators.push_back(conv2d_op); |
Matthew Sloyan | c5fe6e7 | 2022-11-25 16:10:00 +0000 | [diff] [blame] | 142 | |
John Mcloughlin | ceb4428 | 2024-04-23 16:47:04 +0100 | [diff] [blame] | 143 | if (isInputInt8) |
| 144 | { |
| 145 | int32_t output_zp = outputs[0]->GetQuantizationOffset(); |
| 146 | double output_scale = outputs[0]->GetQuantizationScales()[0]; |
| 147 | double input_scale = inputs[0]->GetQuantizationScales()[0]; |
| 148 | const std::vector<float>& weight_scales = inputs[1]->GetQuantizationScales(); |
| 149 | |
| 150 | TosaSerializationOperator* rescaleOp = nullptr; |
John Mcloughlin | ceb4428 | 2024-04-23 16:47:04 +0100 | [diff] [blame] | 151 | CreateRescaleTosaOperatorPerChannel(outputConv2dName, |
| 152 | outputName, |
John Mcloughlin | ceb4428 | 2024-04-23 16:47:04 +0100 | [diff] [blame] | 153 | 0, |
| 154 | output_zp, |
| 155 | true, |
| 156 | true, |
| 157 | input_scale, |
| 158 | output_scale, |
| 159 | weight_scales, |
Teresa Charlin | ce48d1d | 2024-04-24 13:30:58 +0100 | [diff] [blame^] | 160 | &rescaleOp); |
John Mcloughlin | ceb4428 | 2024-04-23 16:47:04 +0100 | [diff] [blame] | 161 | operators.push_back(rescaleOp); |
Teresa Charlin | ce48d1d | 2024-04-24 13:30:58 +0100 | [diff] [blame^] | 162 | tensors.push_back(new TosaSerializationTensor(outputName, |
| 163 | outputShape0, |
| 164 | DType_INT8, {})); |
John Mcloughlin | ceb4428 | 2024-04-23 16:47:04 +0100 | [diff] [blame] | 165 | } |
Teresa Charlin | ce48d1d | 2024-04-24 13:30:58 +0100 | [diff] [blame^] | 166 | |
Matthew Sloyan | c5fe6e7 | 2022-11-25 16:10:00 +0000 | [diff] [blame] | 167 | // operatorInputNames/operatorOutputNames ends up being the same as |
| 168 | // blockInputNames/blockOutputNames for one-to-one ArmNN to TOSA mappings |
| 169 | return new TosaSerializationBasicBlock(blockName, // name |
Narumol Prangnawarat | ad323af | 2023-09-29 17:00:38 +0100 | [diff] [blame] | 170 | mainName, // region name |
Matthew Sloyan | c5fe6e7 | 2022-11-25 16:10:00 +0000 | [diff] [blame] | 171 | operators, // operators |
| 172 | tensors, // tensors |
| 173 | inputNames, // inputs |
| 174 | {outputName}); // outputs |
| 175 | } |