ArmNN
 25.11
Loading...
Searching...
No Matches
ResizeOperator.hpp File Reference
Include dependency graph for ResizeOperator.hpp:
This graph shows which files directly or indirectly include this file:

Go to the source code of this file.

Functions

TosaSerializationBasicBlock * ConvertResizeToTosaOperator (const Layer *layer, const std::vector< const TensorInfo * > &inputs, const std::vector< const TensorInfo * > &outputs, const ResizeDescriptor *resizeDescriptor)

Function Documentation

◆ ConvertResizeToTosaOperator()

TosaSerializationBasicBlock * ConvertResizeToTosaOperator ( const Layer * layer,
const std::vector< const TensorInfo * > & inputs,
const std::vector< const TensorInfo * > & outputs,
const ResizeDescriptor * resizeDescriptor )

Definition at line 16 of file ResizeOperator.cpp.

20{
21 ARMNN_THROW_INVALIDARG_MSG_IF_FALSE( inputs.size() == 1,
22 "ConvertResizeToTosaOperator: Resize must have only one input." );
23 ARMNN_THROW_INVALIDARG_MSG_IF_FALSE( resizeDescriptor->m_DataLayout == DataLayout::NHWC,
24 "ConvertResizeToTosaOperator: NCHW not supported.");
25
26 ResizeMode mode;
27 if (resizeDescriptor->m_Method == ResizeMethod::NearestNeighbor)
28 {
29 mode = tosa::ResizeMode_NEAREST;
30 }
31 else if (resizeDescriptor->m_Method == ResizeMethod::Bilinear)
32 {
33 mode = tosa::ResizeMode_BILINEAR;
34 }
35 else
36 {
37 throw armnn::InvalidArgumentException("ConvertResizeToTosaOperator: Unsupported Resize method.");
38 }
39
40 std::string inputName = std::string("input_");
41 std::string outputName = std::string("output0_");
42 std::string blockName = std::string("Op_RESIZE_block_") + GetUniqueTosaMappingID();
43
44 // If a layer is present then the block will be used for execution, so input and output names need to be determined
45 // using the previous and following layers so the graph is connected correctly. For validation this doesn't matter.
46 if(layer != nullptr)
47 {
48 inputName = GenerateUniqueInputName(layer->GetInputSlot(0));
49 outputName = GenerateUniqueOutputName(*layer);
50 }
51
52 int32_t inputHeight = static_cast<int32_t>(inputs[0]->GetShape()[1]);
53 int32_t inputWidth = static_cast<int32_t>(inputs[0]->GetShape()[2]);
54
55 int32_t outputHeight = static_cast<int32_t>(resizeDescriptor->m_TargetHeight);
56 int32_t outputWidth = static_cast<int32_t>(resizeDescriptor->m_TargetWidth);
57 bool alignCorners = resizeDescriptor->m_AlignCorners;
58 bool halfPixel = resizeDescriptor->m_HalfPixelCenters;
59
60 // Go from ArmNN parameters (outputShape, halfPixel and alignedCorners)
61 // to TOSA parameters (scale, offset and border)
62 // Align corners sets the scaling ratio to (O - 1)/(I - 1) rather than O / I.
63 auto preprocessResizeParameters = [&](int inputSize, int outputSize, int& scale_n, int& scale_d, int& offset)
64 {
65 // Dimension is length 1, we are just sampling from one value.
66 if (inputSize == 1)
67 {
68 scale_n = outputSize;
69 scale_d = 1;
70 offset = 0;
71 return;
72 }
73
74 // Apply if aligned and capable to be aligned.
75 // Align corners sets the scaling ratio to (OH - 1)/(IH - 1) rather than OH / IH. Same for width.
76 bool applyAligned = alignCorners && (outputSize > 1);
77 scale_n = applyAligned ? (outputSize - 1) : outputSize;
78 scale_d = applyAligned ? (inputSize - 1) : inputSize;
79
80 // Simplify the scales, make sure they are even values.
81 int gcd = std::gcd(scale_n, scale_d);
82 scale_n = 2 * scale_n / gcd;
83 scale_d = 2 * scale_d / gcd;
84
85 // If half pixel centers then input and output sampling positions are offset by 1/2 pixel.
86 offset = halfPixel ? (scale_d / 2 - scale_n / 2) : 0;
87
88 // Reduce the scaling ratio if possible, we know scale_n and scale_d are even
89 if ((offset & 1) == 0)
90 {
91 scale_n /= 2;
92 scale_d /= 2;
93 offset /= 2;
94 }
95 };
96
97 int scale_y_n, scale_y_d, offset_y;
98 int scale_x_n, scale_x_d, offset_x;
99 preprocessResizeParameters(inputHeight, outputHeight, scale_y_n, scale_y_d, offset_y);
100 preprocessResizeParameters(inputWidth, outputWidth, scale_x_n, scale_x_d, offset_x);
101
102 int border_y = scale_y_d * (outputHeight - 1) - scale_y_n * (inputHeight - 1) + offset_y;
103 int border_x = scale_x_d * (outputWidth - 1) - scale_x_n * (inputWidth - 1) + offset_x;
104
105 // [scale_y_n, scale_y_d, scale_x_n, scale_x_d]
106 std::vector<int16_t> scale = { static_cast<int16_t>(scale_y_n),
107 static_cast<int16_t>(scale_y_d),
108 static_cast<int16_t>(scale_x_n),
109 static_cast<int16_t>(scale_x_d) };
110
111 // [offset_y, offset_x]
112 std::vector<int16_t> offset = { static_cast<int16_t>(offset_y),
113 static_cast<int16_t>(offset_x) };
114 // [border_y, border_x]
115 std::vector<int16_t> border = { static_cast<int16_t>(border_y),
116 static_cast<int16_t>(border_x) };
117
118 auto isInt16Range = [](int x)
119 {
120 return (x <= std::numeric_limits<int16_t>::max()) && (x >= std::numeric_limits<int16_t>::min());
121 };
122
123 if (inputs[0]->IsQuantized())
124 {
125 // It isn't commonly seen these numbers aren't fit within 16 bits, and won't match TFLite reference.
126 if (!isInt16Range(scale_y_n) || !isInt16Range(scale_y_d) ||
127 !isInt16Range(scale_x_n) || !isInt16Range(scale_x_d) ||
128 !isInt16Range(offset_y) || !isInt16Range(offset_x) ||
129 !isInt16Range(border_y) || !isInt16Range(border_x))
130 {
131 throw armnn::Exception("ConvertResizeToTosaOperator: stride or offset out of 16 bit range");
132 }
133 }
134
135 TosaResizeAttribute resizeAttribute(scale, offset, border, mode);
136
137 std::vector<TosaSerializationTensor*> tensors;
138
139 DType inputDType = ArmNNToDType(inputs[0]->GetDataType());
140 DType outputDType = ArmNNToDType(outputs[0]->GetDataType());
141
142 if(inputs[0]->GetDataType() == DataType::QSymmS16 && mode == tosa::ResizeMode_BILINEAR)
143 {
144 throw armnn::Exception("ConvertResizeToTosaOperator(): Bilinear INT16 is not yet implemented.");
145 }
146
147 if (inputs[0]->GetDataType() == DataType::Signed32 && mode == tosa::ResizeMode_BILINEAR)
148 {
149 throw armnn::Exception("ConvertResizeToTosaOperator(): Bilinear INT32 is not supported.");
150 }
151
152 // Only add input tensors if connected layer is an input layer.
153 // As intermediate or constant tensors will be created separately.
154 // There also can't be duplicate tensor.
155 if (inputName.find("input_") != std::string::npos)
156 {
157 std::vector<int32_t> inputShape = GetTosaTensorShape(inputs[0]->GetShape());
158
159 tensors.push_back(new TosaSerializationTensor(inputName, inputShape, inputDType, {}));
160 }
161
162 std::vector<int32_t> outputShape = GetTosaTensorShape(outputs[0]->GetShape());
163
164 if (mode == tosa::ResizeMode_BILINEAR &&
165 inputDType == DType::DType_INT8 &&
166 outputDType == DType::DType_INT8)
167 {
168 std::string inoutResizeToRescale = std::string("inout_resize2rescale_bilinear_") + GetUniqueTosaMappingID();
169
170 //For this scenario the resize output TOSA tensor type is a scaled INT32 value. Need to
171 //convert to unscaled INT8
172 tensors.push_back(new TosaSerializationTensor(inoutResizeToRescale, outputShape, DType::DType_INT32, {}));
173
174 auto* resizeOp = new TosaSerializationOperator(Op_RESIZE,
175 Attribute_ResizeAttribute,
176 &resizeAttribute,
177 {inputName},
178 {inoutResizeToRescale});
179
180 tensors.push_back(new TosaSerializationTensor(outputName, outputShape, outputDType, {}));
181
182 //As per TOSA spec INT32 output is scaled by scale_y_n * scale_x_n for bilinear resize
183 double scale_bi { 1. / static_cast<double>(scale_y_n * scale_x_n) };
184
185 TosaSerializationOperator* rescaleOp {nullptr};
186
187 CreateRescaleTosaOperator(inoutResizeToRescale,
188 outputName,
189 scale_bi,
190 0,
191 0,
192 false,
193 false,
194 true,
195 true,
196 &rescaleOp);
197
198 return new TosaSerializationBasicBlock(blockName, // name
199 mainName, // region name
200 { resizeOp, rescaleOp }, // operators
201 tensors, // tensors
202 { inputName }, // inputs
203 { outputName }); // outputs
204 }
205 else
206 {
207 tensors.push_back(new TosaSerializationTensor(outputName, outputShape, outputDType, {}));
208
209 auto* op = new TosaSerializationOperator(Op_RESIZE,
210 Attribute_ResizeAttribute,
211 &resizeAttribute,
212 {inputName},
213 {outputName});
214
215 // operatorInputNames/operatorOutputNames ends up being the same as
216 // blockInputNames/blockOutputNames for one-to-one ArmNN to TOSA mappings
217 return new TosaSerializationBasicBlock(blockName, // name
218 mainName, // region name
219 {op}, // operators
220 tensors, // tensors
221 {inputName}, // inputs
222 {outputName}); // outputs
223 }
224}
#define ARMNN_THROW_INVALIDARG_MSG_IF_FALSE(_cond, _str)
std::string GenerateUniqueOutputName(const Layer &layer, uint32_t layerSlot=0)
const std::string mainName
DType ArmNNToDType(const DataType &type)
std::string GenerateUniqueInputName(const armnn::InputSlot &slot)
std::string GetUniqueTosaMappingID()
std::vector< int32_t > GetTosaTensorShape(const TensorShape &shape)
void CreateRescaleTosaOperator(const std::string &inputName, const std::string &outputName, double scale, int32_t input_zp, int32_t output_zp, bool input_unsigned, bool output_unsigned, bool double_round, bool scale32, TosaSerializationOperator **op)
Creates a Tosa rescale operator.
Base class for all ArmNN exceptions so that users can filter to just those.
const InputSlot & GetInputSlot(unsigned int index) const override
Get a const input slot handle by slot index.
Definition Layer.hpp:337
bool m_HalfPixelCenters
Half Pixel Centers.
uint32_t m_TargetHeight
Target height value.
DataLayout m_DataLayout
The data layout to be used (NCHW, NHWC).
ResizeMethod m_Method
The Interpolation method to use (Bilinear, NearestNeighbor).
uint32_t m_TargetWidth
Target width value.
bool m_AlignCorners
Aligned corners.

References ARMNN_THROW_INVALIDARG_MSG_IF_FALSE, ArmNNToDType(), CreateRescaleTosaOperator(), GenerateUniqueInputName(), GenerateUniqueOutputName(), Layer::GetInputSlot(), GetTosaTensorShape(), GetUniqueTosaMappingID(), ResizeDescriptor::m_AlignCorners, ResizeDescriptor::m_DataLayout, ResizeDescriptor::m_HalfPixelCenters, ResizeDescriptor::m_Method, ResizeDescriptor::m_TargetHeight, ResizeDescriptor::m_TargetWidth, and mainName.

Referenced by GetTosaMapping().