IVGCVSW-2093 Add SpaceToBatchNd layer and corresponding no-op factory implementations
[platform/upstream/armnn.git] / src / backends / WorkloadData.cpp
1 //
2 // Copyright © 2017 Arm Ltd. All rights reserved.
3 // SPDX-License-Identifier: MIT
4 //
5 #include "WorkloadData.hpp"
6
7 #include "CpuTensorHandle.hpp"
8
9 #include <algorithm>
10 #include <string>
11 #include <sstream>
12 #include <iomanip>
13
14 #include <boost/format.hpp>
15
16 namespace armnn
17 {
18
19 //---------------------------------------------------------------
20 DataType GetBiasDataType(DataType inputDataType)
21 {
22     switch (inputDataType)
23     {
24         case DataType::Float16:
25             return DataType::Float16;
26         case DataType::Float32:
27             return DataType::Float32;
28         case DataType::QuantisedAsymm8:
29             return DataType::Signed32;
30         default:
31             BOOST_ASSERT_MSG(false, "Invalid input data type");
32             return DataType::Float32;
33     }
34 }
35
36 namespace
37 {
38
39 //---------------------------------------------------------------
40 //android ndk does not support std::to_string function.
41 template <typename T>
42 std::string to_string(T value)
43 {
44     std::ostringstream os;
45     os << value;
46     return os.str();
47 }
48
49 //---------------------------------------------------------------
50 void ValidatePointer(const void* ptr, std::string const& descName, std::string const& paramName)
51 {
52     if (!ptr)
53     {
54         throw InvalidArgumentException(descName +  ": Invalid null pointer. The " +
55                                       paramName + " parameter must be set.");
56     }
57 }
58
59 //---------------------------------------------------------------
60 void ValidateTensorShapesMatch(const TensorInfo& first,
61                                const TensorInfo& second,
62                                std::string const& descName,
63                                std::string const& firstName,
64                                std::string const& secondName)
65 {
66     if (first.GetShape() != second.GetShape())
67     {
68         throw InvalidArgumentException(descName + ": "
69                                        + firstName + " & " + secondName + " must have identical shapes");
70     }
71 }
72
73 //---------------------------------------------------------------
74 void ValidateNoInputs(const WorkloadInfo& workloadInfo, std::string const& descName)
75 {
76     if (workloadInfo.m_InputTensorInfos.size() != 0)
77     {
78         throw InvalidArgumentException(descName +
79             ": Requires no inputs. " +
80             to_string(workloadInfo.m_InputTensorInfos.size()) + " has been provided.");
81     }
82 }
83
84 //---------------------------------------------------------------
85 void ValidateSingleInput(const WorkloadInfo& workloadInfo, std::string const& descName)
86 {
87     if (workloadInfo.m_InputTensorInfos.size() != 1)
88     {
89         throw InvalidArgumentException(descName +
90                                        ": Requires exactly one input. " +
91                                        to_string(workloadInfo.m_InputTensorInfos.size()) + " has been provided." );
92     }
93 }
94
95 //---------------------------------------------------------------
96 void ValidateTwoInputs(const WorkloadInfo& workloadInfo, std::string const& descName)
97 {
98     if (workloadInfo.m_InputTensorInfos.size() != 2)
99     {
100         throw InvalidArgumentException(descName +
101                                        ": Requires exactly two workloadInfo.m_InputTensorInfos. " +
102                                        to_string(workloadInfo.m_InputTensorInfos.size()) + " have been provided.");
103     }
104 }
105
106 //---------------------------------------------------------------
107 void ValidateSingleOutput(const WorkloadInfo& workloadInfo, std::string const& descName)
108 {
109     if (workloadInfo.m_OutputTensorInfos.size() != 1)
110     {
111         throw InvalidArgumentException(descName +
112                                        ": Requires exactly one output. " +
113                                        to_string(workloadInfo.m_OutputTensorInfos.size()) + " has been provided.");
114     }
115 }
116
117 //---------------------------------------------------------------
118 void ValidateTensorNumDimensions(const TensorInfo&  tensor,
119                                  std::string const& descName,
120                                  unsigned int       numDimensions,
121                                  std::string const& tensorName)
122 {
123     if (tensor.GetNumDimensions() != numDimensions)
124     {
125         throw InvalidArgumentException(descName + ": Expected " + to_string(numDimensions) + " but got " +
126             to_string(tensor.GetNumDimensions()) + " dimensions for " +
127             tensorName + " tensor.");
128     }
129 }
130
131 //---------------------------------------------------------------
132 void ValidateTensorDataType(const TensorInfo& tensor, DataType dataType,
133     const std::string& descName, std::string const& tensorName)
134 {
135     if (tensor.GetDataType() != dataType)
136     {
137         throw InvalidArgumentException(descName + ": Expected data type " + GetDataTypeName(dataType) + " but got " +
138             GetDataTypeName(tensor.GetDataType()) + " for " + tensorName + " tensor.");
139     }
140 }
141
142 //---------------------------------------------------------------
143 void ValidateBiasTensorQuantization(const TensorInfo& biasTensor, const TensorInfo& inputTensorInfo,
144     const TensorInfo& weightsTensorInfo, const std::string& descName)
145 {
146     if (biasTensor.GetQuantizationOffset() != 0)
147     {
148         throw InvalidArgumentException(descName + ": Expected zero quantization offset for bias tensor but got " +
149             to_string(biasTensor.GetQuantizationOffset()));
150     }
151     const float expectedScale = inputTensorInfo.GetQuantizationScale() * weightsTensorInfo.GetQuantizationScale();
152     if (std::abs(biasTensor.GetQuantizationScale() - expectedScale) > 0.000000001f)
153     {
154         // Print the float values with extra precision to see very small differences
155         std::stringstream msg;
156         msg << std::setprecision(10) << descName << ": Expected " << expectedScale <<
157             " quantization scale for bias tensor (the product of the input and weight scales), but got " <<
158             biasTensor.GetQuantizationScale();
159         throw InvalidArgumentException(msg.str());
160     }
161 }
162
163 //---------------------------------------------------------------
164 void ValidateTensors(const std::vector<ITensorHandle*>& vec,
165     unsigned int numExpected,
166     const std::string& descName,
167     const std::string& varName)
168 {
169     if (vec.empty() && numExpected > 0)
170     {
171         throw InvalidArgumentException(descName + ": Invalid empty " + varName + " array.");
172     }
173
174     for (unsigned int i = 0; i < numExpected; ++i)
175     {
176         if (!vec[i])
177         {
178             throw InvalidArgumentException(descName + ": Invalid NULL for " + varName + to_string(i));
179         }
180     }
181 }
182
183 //---------------------------------------------------------------
184 void ValidateBroadcastTensorShapesMatch(const TensorInfo& first,
185                                         const TensorInfo& second,
186                                         const TensorInfo& output,
187                                         std::string const& descName,
188                                         std::string const& firstName,
189                                         std::string const& secondName)
190 {
191     // Tensors must have the same number of dimensions in order to be explicit about which dimensions will get
192     // broadcasted.
193     if (first.GetNumDimensions() != second.GetNumDimensions())
194     {
195         throw InvalidArgumentException(descName  + ": Tensors "
196             + firstName + " & " + secondName
197             + " must have the same number of dimensions in order to be broadcasted");
198     }
199     uint32_t numDims = first.GetNumDimensions();
200     std::vector<uint32_t> outputDims(numDims, 0u);
201     for (uint32_t i = 0; i < numDims; i++)
202     {
203         const bool dimsNotEqual = first.GetShape()[i] != second.GetShape()[i];
204         const bool dimsNotOne = (first.GetShape()[i] != 1) && (second.GetShape()[i] != 1);
205         if (dimsNotEqual && dimsNotOne)
206         {
207             throw InvalidArgumentException("Broadcasting is not possible for incompatible shapes");
208         }
209         outputDims[i] = std::max(first.GetShape()[i], second.GetShape()[i]);
210     }
211     TensorShape broadcastShape =  TensorShape(boost::numeric_cast<unsigned int>(outputDims.size()), outputDims.data());
212     if (broadcastShape != output.GetShape())
213     {
214         throw InvalidArgumentException(descName + ": The tensor shape resulting from adding "
215                                        + firstName + " & " + secondName
216                                        + " does not match the output shape");
217     }
218 }
219
220 //---------------------------------------------------------------
221 /// Validates that the output tensor's quantization scale is greater than the product
222 /// of the two input tensors' quantization scales. This is a requirement of the implementation of
223 /// the quantized multiplication.
224 void ValidateTensorQuantizationMultiplier(const TensorInfo& inputTensor1, const TensorInfo& inputTensor2,
225     const TensorInfo& outputTensorInfo, std::string const& descName,
226     const std::string& inputTensor1Name, const std::string& inputTensor2Name, const std::string& outputTensorName)
227 {
228     if (outputTensorInfo.GetDataType() == DataType::QuantisedAsymm8)
229     {
230         if (outputTensorInfo.GetQuantizationScale() <=
231             inputTensor1.GetQuantizationScale() * inputTensor2.GetQuantizationScale())
232         {
233             std::stringstream msg;
234             msg << descName << ": Quantization scale of " << outputTensorName << " is not greater than " <<
235                 "the product of the " << inputTensor1Name << " and " << inputTensor2Name << " tensors";
236             throw InvalidArgumentException(msg.str());
237         }
238     }
239 }
240
241 } //namespace
242
243 void QueueDescriptor::ValidateInputsOutputs(const std::string& descName,
244     unsigned int numExpectedIn, unsigned int numExpectedOut) const
245 {
246     ValidateTensors(m_Inputs, numExpectedIn, descName, "input");
247     ValidateTensors(m_Outputs, numExpectedOut, descName, "output");
248 }
249
250 //---------------------------------------------------------------
251 void MemCopyQueueDescriptor::Validate(const WorkloadInfo& workloadInfo) const
252 {
253     ValidateSingleInput(workloadInfo, "MemCopyQueueDescriptor");
254     ValidateSingleOutput(workloadInfo, "MemCopyQueueDescriptor");
255
256     if (workloadInfo.m_InputTensorInfos.size() != workloadInfo.m_OutputTensorInfos.size())
257     {
258         throw InvalidArgumentException(boost::str(
259             boost::format("Number of input infos (%1%) does not match the number of output infos (%2%)")
260                 % workloadInfo.m_InputTensorInfos.size() % workloadInfo.m_OutputTensorInfos.size()));
261     }
262
263     for (std::size_t i = 0; i < workloadInfo.m_InputTensorInfos.size(); ++i)
264     {
265         if (workloadInfo.m_InputTensorInfos[i].GetNumElements() !=
266             workloadInfo.m_OutputTensorInfos[i].GetNumElements())
267         {
268             throw InvalidArgumentException(boost::str(
269                 boost::format("Number of elements for tensor input and output %1% does not match")
270                     % i ));
271         }
272     }
273
274     if (m_Inputs.size() != m_Outputs.size())
275     {
276         throw InvalidArgumentException(boost::str(
277             boost::format("Number of inputs (%1%) does not match the number of outputs (%2%)")
278                 % m_Inputs.size() % m_Outputs.size()));
279     }
280
281     for (unsigned int i = 0; i < m_Inputs.size(); ++i)
282     {
283         if (!m_Inputs[i])
284         {
285             throw InvalidArgumentException(boost::str(boost::format("Invalid null input %1%") % i));
286         }
287
288         if (!m_Outputs[i])
289         {
290             throw InvalidArgumentException(boost::str(boost::format("Invalid null output %1%") % i));
291         }
292     }
293 }
294
295 //---------------------------------------------------------------
296 void ActivationQueueDescriptor::Validate(const WorkloadInfo& workloadInfo) const
297 {
298     ValidateSingleInput(workloadInfo, "ActivationQueueDescriptor");
299     ValidateSingleOutput(workloadInfo, "ActivationQueueDescriptor");
300     ValidateTensorShapesMatch(workloadInfo.m_InputTensorInfos[0],
301                               workloadInfo.m_OutputTensorInfos[0],
302                               "ActivationQueueDescriptor",
303                               "input",
304                               "output");
305 }
306
307 //---------------------------------------------------------------
308 void SoftmaxQueueDescriptor::Validate(const WorkloadInfo& workloadInfo) const
309 {
310     ValidateSingleInput(workloadInfo, "SoftmaxQueueDescriptor");
311     ValidateSingleOutput(workloadInfo, "SoftmaxQueueDescriptor");
312     ValidateTensorNumDimensions(workloadInfo.m_InputTensorInfos[0], "SoftmaxQueueDescriptor", 2, "input");
313     ValidateTensorNumDimensions(workloadInfo.m_OutputTensorInfos[0], "SoftmaxQueueDescriptor", 2, "output");
314
315     ValidateTensorShapesMatch(workloadInfo.m_InputTensorInfos[0],
316                               workloadInfo.m_OutputTensorInfos[0],
317                               "SoftmaxQueueDescriptor",
318                               "input",
319                               "output");
320 }
321
322 //---------------------------------------------------------------
323 void SplitterQueueDescriptor::Validate(const WorkloadInfo& workloadInfo) const
324 {
325     ValidateSingleInput(workloadInfo, "SplitterQueueDescriptor");
326
327     if (workloadInfo.m_OutputTensorInfos.size() <= 0)
328     {
329         throw InvalidArgumentException("SplitterQueueDescriptor: At least one output needs to be provided.");
330     }
331
332     if (workloadInfo.m_OutputTensorInfos.size() != m_ViewOrigins.size())
333     {
334         throw InvalidArgumentException(
335             "SplitterQueueDescriptor: Number of split windows "
336             "has to match number of workloadInfo.m_OutputTensorInfos. "
337             "Number of windows: " +
338             to_string(m_ViewOrigins.size()) +
339             ". Number of workloadInfo.m_OutputTensorInfos: " + to_string(workloadInfo.m_OutputTensorInfos.size()));
340     }
341
342     //The dimensionality of all the windows has to match the dimensionality (not shape) of the input.
343     std::size_t inputDims = workloadInfo.m_InputTensorInfos[0].GetNumDimensions();
344     for(unsigned int w = 0; w < m_ViewOrigins.size(); ++w )
345     {
346         //Checks that the dimensionality of input is same as the split windows.
347         ViewOrigin const& e = m_ViewOrigins[w];
348         if (e.m_Origin.size() != inputDims)
349         {
350             throw InvalidArgumentException("SplitterQueueDescriptor: Window origin have to "
351                                            "have the same dimensionality as the input tensor. "
352                                            "Window origin (index: " +
353                                            to_string(w) + ") has " + to_string(e.m_Origin.size()) +
354                                            " dimensions, the input "
355                                            "tensor has " +
356                                            to_string(inputDims) + " dimensions.");
357         }
358         for (unsigned int i = 0; i < e.m_Origin.size(); ++i)
359         {
360             if (e.m_Origin[i] + workloadInfo.m_OutputTensorInfos[w].GetShape()[i] >
361                 workloadInfo.m_InputTensorInfos[0].GetShape()[i])
362             {
363                 throw InvalidArgumentException("SplitterQueueDescriptor: Window extent coordinates have to "
364                                                "be smaller or equal than the size of the input in that coord.");
365             }
366         }
367     }
368 }
369
370 //---------------------------------------------------------------
371 void MergerQueueDescriptor::Validate(const WorkloadInfo& workloadInfo) const
372 {
373     ValidateSingleOutput(workloadInfo, "MergerQueueDescriptor");
374
375     if (m_Inputs.size() <= 0)
376     {
377         throw InvalidArgumentException("MergerQueueDescriptor: At least one input needs to be provided.");
378     }
379     if (m_Outputs.size() <= 0)
380     {
381         throw InvalidArgumentException("MergerQueueDescriptor: At least one output needs to be provided.");
382     }
383
384     if (workloadInfo.m_InputTensorInfos.size() <= 0)
385     {
386         throw InvalidArgumentException("MergerQueueDescriptor: At least one TensorInfo input needs to be provided.");
387     }
388     if (workloadInfo.m_OutputTensorInfos.size() <= 0)
389     {
390         throw InvalidArgumentException("MergerQueueDescriptor: At least one TensorInfo output needs to be provided.");
391     }
392
393     if (workloadInfo.m_InputTensorInfos.size() != m_ViewOrigins.size())
394     {
395         throw InvalidArgumentException(
396             "MergerQueueDescriptor: Number of split windows "
397             "has to match number of workloadInfo.m_InputTensorInfos. "
398             "Number of windows: " +
399             to_string(m_ViewOrigins.size()) +
400             ". Number of workloadInfo.m_InputTensorInfos: " + to_string(workloadInfo.m_InputTensorInfos.size()));
401     }
402
403     //The dimensionality of all the windows has to match the dimensionality (not shape) of the output.
404     std::size_t outputDims = workloadInfo.m_OutputTensorInfos[0].GetNumDimensions();
405     for(unsigned int w = 0; w < m_ViewOrigins.size(); ++w )
406     {
407         //Checks that the dimensionality of output is same as the split windows.
408         ViewOrigin const& e = m_ViewOrigins[w];
409         if (e.m_Origin.size() != outputDims)
410         {
411             throw InvalidArgumentException("MergerQueueDescriptor: Window origin have to "
412                                            "have the same dimensionality as the output tensor. "
413                                            "Window origin (index: " +
414                                            to_string(w) + ") has " + to_string(e.m_Origin.size()) +
415                                            " dimensions, the output "
416                                            "tensor has " +
417                                            to_string(outputDims) + " dimensions.");
418         }
419         //Checks that the merge windows are within the output tensor.
420         for (unsigned int i = 0; i < e.m_Origin.size(); ++i)
421         {
422             if (e.m_Origin[i] + workloadInfo.m_InputTensorInfos[w].GetShape()[i]
423                 > workloadInfo.m_OutputTensorInfos[0].GetShape()[i])
424             {
425                 throw InvalidArgumentException("MergerQueueDescriptor: Window extent coordinates have to "
426                                                "be smaller or equal than the size of the output in that coord.");
427             }
428         }
429     }
430 }
431
432 //---------------------------------------------------------------
433 void FullyConnectedQueueDescriptor::Validate(const WorkloadInfo& workloadInfo) const
434 {
435     ValidateSingleInput(workloadInfo, "FullyConnectedQueueDescriptor");
436     ValidateSingleOutput(workloadInfo, "FullyConnectedQueueDescriptor");
437     ValidateTensorNumDimensions(workloadInfo.m_OutputTensorInfos[0], "FullyConnectedQueueDescriptor", 2, "output");
438
439     if (!(workloadInfo.m_InputTensorInfos[0].GetNumDimensions() == 2 ||
440           workloadInfo.m_InputTensorInfos[0].GetNumDimensions() == 4))
441     {
442         throw InvalidArgumentException("FullyConnectedQueueDescriptor: Input tensor must have 2 or 4 dimensions.");
443     }
444
445     if (m_Weight == nullptr)
446     {
447         throw InvalidArgumentException("FullyConnectedQueueDescriptor: Weight tensor descriptor is missing.");
448     }
449
450     ValidateTensorNumDimensions(m_Weight->GetTensorInfo(), "FullyConnectedQueueDescriptor", 2, "weight");
451
452     if (m_Parameters.m_BiasEnabled)
453     {
454         if (m_Bias == nullptr)
455         {
456             throw InvalidArgumentException("FullyConnectedQueueDescriptor: Bias is enabled but "
457                                            "bias value tensor descriptor is missing.");
458         }
459
460         // Validates type and quantization values.
461         ValidateBiasTensorQuantization(m_Bias->GetTensorInfo(),
462             workloadInfo.m_InputTensorInfos[0], m_Weight->GetTensorInfo(), "FullyConnectedQueueDescriptor");
463
464         ValidateTensorDataType(m_Bias->GetTensorInfo(),
465                                GetBiasDataType(workloadInfo.m_InputTensorInfos[0].GetDataType()),
466                                "FullyConnectedQueueDescriptor", "bias");
467
468         ValidateTensorNumDimensions(m_Bias->GetTensorInfo(), "FullyConnectedQueueDescriptor", 1, "bias");
469     }
470
471     ValidateTensorQuantizationMultiplier(workloadInfo.m_InputTensorInfos[0], m_Weight->GetTensorInfo(),
472         workloadInfo.m_OutputTensorInfos[0], "FullyConnectedQueueDescriptor", "input", "weights", "output");
473 }
474
475 //---------------------------------------------------------------
476 void NormalizationQueueDescriptor::Validate(const WorkloadInfo& workloadInfo) const
477 {
478     ValidateSingleInput(workloadInfo, "NormalizationQueueDescriptor");
479     ValidateSingleOutput(workloadInfo, "NormalizationQueueDescriptor");
480     ValidateTensorShapesMatch(workloadInfo.m_InputTensorInfos[0],
481                               workloadInfo.m_OutputTensorInfos[0],
482                               "NormalizationQueueDescriptor",
483                               "input",
484                               "output");
485 }
486
487 void AdditionQueueDescriptor::Validate(const WorkloadInfo& workloadInfo) const
488 {
489     ValidateTwoInputs(workloadInfo, "AdditionQueueDescriptor");
490     ValidateSingleOutput(workloadInfo, "AdditionQueueDescriptor");
491
492     ValidateBroadcastTensorShapesMatch(workloadInfo.m_InputTensorInfos[0],
493                                        workloadInfo.m_InputTensorInfos[1],
494                                        workloadInfo.m_OutputTensorInfos[0],
495                                        "AdditionQueueDescriptor",
496                                        "first input",
497                                        "second input");
498
499 }
500
501 //---------------------------------------------------------------
502 void MultiplicationQueueDescriptor::Validate(const WorkloadInfo& workloadInfo) const
503 {
504     ValidateTwoInputs(workloadInfo, "MultiplicationQueueDescriptor");
505     ValidateSingleOutput(workloadInfo, "MultiplicationQueueDescriptor");
506
507     ValidateBroadcastTensorShapesMatch(workloadInfo.m_InputTensorInfos[0],
508                                        workloadInfo.m_InputTensorInfos[1],
509                                        workloadInfo.m_OutputTensorInfos[0],
510                                        "MultiplicationQueueDescriptor",
511                                        "first input",
512                                        "second input");
513 }
514
515 void BatchNormalizationQueueDescriptor::Validate(const WorkloadInfo& workloadInfo) const
516 {
517     ValidateSingleInput(workloadInfo, "BatchNormalizationQueueDescriptor");
518     ValidateSingleOutput(workloadInfo, "BatchNormalizationQueueDescriptor");
519     ValidateTensorShapesMatch(workloadInfo.m_InputTensorInfos[0],
520                               workloadInfo.m_OutputTensorInfos[0],
521                               "BatchNormalizationQueueDescriptor",
522                               "input",
523                               "output");
524     ValidatePointer(m_Mean, "BatchNormalizationQueueDescriptor", "mean");
525     ValidatePointer(m_Variance, "BatchNormalizationQueueDescriptor", "variance");
526     ValidatePointer(m_Beta, "BatchNormalizationQueueDescriptor", "beta");
527     ValidatePointer(m_Gamma, "BatchNormalizationQueueDescriptor", "gamma");
528
529
530     ValidateTensorNumDimensions(m_Mean->GetTensorInfo(), "BatchNormalizationQueueDescriptor", 1, "mean");
531     ValidateTensorNumDimensions(m_Variance->GetTensorInfo(), "BatchNormalizationQueueDescriptor", 1, "variance");
532     ValidateTensorNumDimensions(m_Beta->GetTensorInfo(), "BatchNormalizationQueueDescriptor", 1, "beta");
533     ValidateTensorNumDimensions(m_Gamma->GetTensorInfo(), "BatchNormalizationQueueDescriptor", 1, "gamma");
534
535     ValidateTensorShapesMatch(
536         m_Mean->GetTensorInfo(), m_Variance->GetTensorInfo(), "BatchNormalizationQueueDescriptor", "mean", "variance");
537     ValidateTensorShapesMatch(
538         m_Mean->GetTensorInfo(), m_Beta->GetTensorInfo(), "BatchNormalizationQueueDescriptor", "mean", "beta");
539     ValidateTensorShapesMatch(
540         m_Mean->GetTensorInfo(), m_Gamma->GetTensorInfo(), "BatchNormalizationQueueDescriptor", "mean", "gamma");
541 }
542
543 void Convolution2dQueueDescriptor::Validate(const WorkloadInfo& workloadInfo) const
544 {
545     ValidateSingleInput(workloadInfo, "Convolution2dQueueDescriptor");
546     ValidateSingleOutput(workloadInfo, "Convolution2dQueueDescriptor");
547
548     ValidateTensorNumDimensions(workloadInfo.m_InputTensorInfos[0], "Convolution2dQueueDescriptor", 4, "input");
549     ValidateTensorNumDimensions(workloadInfo.m_OutputTensorInfos[0], "Convolution2dQueueDescriptor", 4, "output");
550
551     ValidatePointer(m_Weight, "Convolution2dQueueDescriptor", "weight");
552     ValidateTensorNumDimensions(m_Weight->GetTensorInfo(), "Convolution2dQueueDescriptor", 4, "weight");
553     ValidateTensorDataType(m_Weight->GetTensorInfo(), workloadInfo.m_InputTensorInfos[0].GetDataType(),
554         "Convolution2dQueueDescriptor", "weight");
555     if (m_Parameters.m_BiasEnabled)
556     {
557         ValidateTensorNumDimensions(m_Bias->GetTensorInfo(), "Convolution2dQueueDescriptor", 1, "bias");
558         ValidateTensorDataType(m_Bias->GetTensorInfo(),
559                                GetBiasDataType(workloadInfo.m_InputTensorInfos[0].GetDataType()),
560                                "Convolution2dQueueDescriptor", "bias");
561         ValidateBiasTensorQuantization(m_Bias->GetTensorInfo(),
562             workloadInfo.m_InputTensorInfos[0], m_Weight->GetTensorInfo(), "Convolution2dQueueDescriptor");
563     }
564
565     ValidateTensorQuantizationMultiplier(workloadInfo.m_InputTensorInfos[0], m_Weight->GetTensorInfo(),
566         workloadInfo.m_OutputTensorInfos[0], "Convolution2dQueueDescriptor", "input", "weights", "output");
567 }
568
569 void DepthwiseConvolution2dQueueDescriptor::Validate(const WorkloadInfo& workloadInfo) const
570 {
571     ValidateSingleInput(workloadInfo, "DepthwiseConvolution2dQueueDescriptor");
572     ValidateSingleOutput(workloadInfo, "DepthwiseConvolution2dQueueDescriptor");
573
574     ValidateTensorNumDimensions(
575         workloadInfo.m_InputTensorInfos[0], "DepthwiseConvolution2dQueueDescriptor", 4, "input");
576     ValidateTensorNumDimensions(
577         workloadInfo.m_OutputTensorInfos[0], "DepthwiseConvolution2dQueueDescriptor", 4, "output");
578
579     ValidatePointer(m_Weight, "DepthwiseConvolution2dQueueDescriptor", "weight");
580     ValidateTensorNumDimensions(m_Weight->GetTensorInfo(), "DepthwiseConvolution2dQueueDescriptor", 4, "weight");
581
582     const unsigned int channelIndex = (m_Parameters.m_DataLayout == DataLayout::NCHW) ? 1 : 3;
583
584     //inputChannels * channelMultiplier should be equal to outputChannels.
585     const unsigned int numWeightChannelMultiplier = m_Weight->GetTensorInfo().GetShape()[0];
586     const unsigned int numWeightInputChannels = m_Weight->GetTensorInfo().GetShape()[channelIndex];
587     const unsigned int numWeightOutputChannels = workloadInfo.m_OutputTensorInfos[0].GetShape()[channelIndex];
588     if (numWeightChannelMultiplier * numWeightInputChannels != numWeightOutputChannels)
589     {
590         throw InvalidArgumentException(
591             boost::str(boost::format("DepthwiseConvolution2dQueueDescriptor: output_channels (provided %1%) should be "
592                                      "equal to input_channels (provided %2%) multiplied by channel_multiplier "
593                                      "(provided %3%).")
594                                      % numWeightOutputChannels % numWeightInputChannels % numWeightChannelMultiplier));
595     }
596
597     if (m_Parameters.m_BiasEnabled)
598     {
599         ValidatePointer(m_Bias, "DepthwiseConvolution2dQueueDescriptor", "bias");
600         ValidateTensorNumDimensions(m_Bias->GetTensorInfo(), "DepthwiseConvolution2dQueueDescriptor", 1, "bias");
601         ValidateBiasTensorQuantization(m_Bias->GetTensorInfo(),
602             workloadInfo.m_InputTensorInfos[0], m_Weight->GetTensorInfo(), "DepthwiseConvolution2dQueueDescriptor");
603
604         ValidateTensorDataType(m_Bias->GetTensorInfo(),
605                                GetBiasDataType(workloadInfo.m_InputTensorInfos[0].GetDataType()),
606                                "DepthwiseConvolution2dQueueDescriptor", "bias");
607     }
608
609     ValidateTensorQuantizationMultiplier(workloadInfo.m_InputTensorInfos[0], m_Weight->GetTensorInfo(),
610         workloadInfo.m_OutputTensorInfos[0], "DepthwiseConvolution2dQueueDescriptor", "input", "weights", "output");
611 }
612
613 void PermuteQueueDescriptor::Validate(const WorkloadInfo& workloadInfo) const
614 {
615     ValidateSingleInput(workloadInfo, "PermuteQueueDescriptor");
616     ValidateSingleOutput(workloadInfo, "PermuteQueueDescriptor");
617
618     const PermutationVector& mapping = m_Parameters.m_DimMappings;
619
620     const TensorInfo& input  = workloadInfo.m_InputTensorInfos[0];
621     const TensorInfo& output = workloadInfo.m_OutputTensorInfos[0];
622
623     ValidateTensorNumDimensions(input, "PermuteQueueDescriptor", mapping.GetSize(), "input");
624     ValidateTensorNumDimensions(output, "PermuteQueueDescriptor", mapping.GetSize(), "output");
625
626     for (unsigned int i = 0; i < mapping.GetSize(); ++i)
627     {
628         if (input.GetShape()[i] != output.GetShape()[mapping[i]])
629         {
630             throw InvalidArgumentException("PermuteQueueDescriptor: src dimension " + to_string(i) +
631                                                " (=" + to_string(input.GetShape()[i]) + ") " +
632                                                "must match dst dimension " + to_string(mapping[i]) +
633                                                " (=" + to_string(output.GetShape()[mapping[i]]) + ")");
634         }
635     }
636 }
637
638 void Pooling2dQueueDescriptor::Validate(const WorkloadInfo& workloadInfo) const
639 {
640     ValidateSingleInput(workloadInfo, "Pooling2dQueueDescriptor");
641     ValidateSingleOutput(workloadInfo, "Pooling2dQueueDescriptor");
642
643     ValidateTensorNumDimensions(workloadInfo.m_InputTensorInfos[0], "Pooling2dQueueDescriptor", 4, "input");
644     ValidateTensorNumDimensions(workloadInfo.m_OutputTensorInfos[0], "Pooling2dQueueDescriptor", 4, "output");
645 }
646
647 void ResizeBilinearQueueDescriptor::Validate(const WorkloadInfo& workloadInfo) const
648 {
649     ValidateSingleInput(workloadInfo, "ResizeBilinearQueueDescriptor");
650     ValidateSingleOutput(workloadInfo, "ResizeBilinearQueueDescriptor");
651
652     ValidateTensorNumDimensions(workloadInfo.m_InputTensorInfos[0], "ResizeBilinearQueueDescriptor", 4, "input");
653     ValidateTensorNumDimensions(workloadInfo.m_OutputTensorInfos[0], "ResizeBilinearQueueDescriptor", 4, "output");
654
655     // Resizes bilinear only changes width and height: batch and channel count must match.
656     {
657         const unsigned int inputBatchSize = workloadInfo.m_InputTensorInfos[0].GetShape()[0];
658         const unsigned int outputBatchSize = workloadInfo.m_OutputTensorInfos[0].GetShape()[0];
659         if (inputBatchSize != outputBatchSize)
660         {
661             throw InvalidArgumentException(
662                 boost::str(boost::format("ResizeBilinearQueueDescriptor: Input batch size (%1%) "
663                     "does not match output batch size (%2%)") % inputBatchSize % outputBatchSize));
664         }
665     }
666
667     {
668         const unsigned int inputChannelCount =
669             workloadInfo.m_InputTensorInfos[0].GetShape()[this->m_Parameters.m_DataLayout.GetChannelsIndex()];
670         const unsigned int outputChannelCount =
671             workloadInfo.m_OutputTensorInfos[0].GetShape()[this->m_Parameters.m_DataLayout.GetChannelsIndex()];
672         if (inputChannelCount != outputChannelCount)
673         {
674             throw InvalidArgumentException(
675                 boost::str(boost::format("ResizeBilinearQueueDescriptor: Input channel count (%1%) "
676                     "does not match output channel count (%2%)") % inputChannelCount % outputChannelCount));
677         }
678     }
679 }
680
681 void FakeQuantizationQueueDescriptor::Validate(const WorkloadInfo& workloadInfo) const
682 {
683     ValidateSingleInput(workloadInfo, "FakeQuantizationQueueDescriptor");
684     ValidateSingleOutput(workloadInfo, "FakeQuantizationQueueDescriptor");
685
686     ValidateTensorNumDimensions(workloadInfo.m_InputTensorInfos[0], "FakeQuantizationQueueDescriptor", 2, "input");
687     ValidateTensorNumDimensions(workloadInfo.m_OutputTensorInfos[0], "FakeQuantizationQueueDescriptor", 2, "output");
688     ValidateTensorShapesMatch(workloadInfo.m_InputTensorInfos[0],
689         workloadInfo.m_OutputTensorInfos[0],
690         "FakeQuantizationQueueDescriptor",
691         "input",
692         "output");
693     if (m_Parameters.m_Min > m_Parameters.m_Max)
694     {
695         throw InvalidArgumentException("FakeQuantizationQueueDescriptor: min cannot be greater than max");
696     }
697
698 }
699
700 void L2NormalizationQueueDescriptor::Validate(const WorkloadInfo& workloadInfo) const
701 {
702     ValidateSingleInput(workloadInfo, "L2NormalizationQueueDescriptor");
703     ValidateSingleOutput(workloadInfo, "L2NormalizationQueueDescriptor");
704
705     ValidateTensorNumDimensions(workloadInfo.m_InputTensorInfos[0], "L2NormalizationQueueDescriptor", 4, "input");
706     ValidateTensorNumDimensions(workloadInfo.m_OutputTensorInfos[0], "L2NormalizationQueueDescriptor", 4, "output");
707     ValidateTensorShapesMatch(workloadInfo.m_InputTensorInfos[0],
708         workloadInfo.m_OutputTensorInfos[0],
709         "L2NormalizationQueueDescriptor",
710         "input",
711         "output");
712 }
713
714 void ConstantQueueDescriptor::Validate(const WorkloadInfo& workloadInfo) const
715 {
716     ValidateNoInputs(workloadInfo, "ConstantQueueDescriptor");
717     ValidateSingleOutput(workloadInfo, "ConstantQueueDescriptor");
718
719     if (!m_LayerOutput)
720     {
721         throw InvalidArgumentException("ConstantQueueDescriptor: No const input specified");
722     }
723
724     ValidateTensorShapesMatch(m_LayerOutput->GetTensorInfo(),
725         workloadInfo.m_OutputTensorInfos[0],
726         "ConstantQueueDescriptor",
727         "constant",
728         "output");
729 }
730
731 void ReshapeQueueDescriptor::Validate(const WorkloadInfo& workloadInfo) const
732 {
733     ValidateSingleInput(workloadInfo, "ReshapeQueueDescriptor");
734     ValidateSingleOutput(workloadInfo, "ReshapeQueueDescriptor");
735
736     if (workloadInfo.m_InputTensorInfos[0].GetNumElements() != workloadInfo.m_OutputTensorInfos[0].GetNumElements())
737     {
738         throw InvalidArgumentException("ReshapeQueueDescriptor: Input tensor has " +
739             to_string(workloadInfo.m_InputTensorInfos[0].GetNumElements()) + " but output tensor has " +
740             to_string(workloadInfo.m_OutputTensorInfos[0].GetNumElements()) + " elements.");
741     }
742 }
743
744 void SpaceToBatchNdQueueDescriptor::Validate(const WorkloadInfo& workloadInfo) const
745 {
746     ValidateSingleInput(workloadInfo, "SpaceToBatchNdQueueDescriptor");
747     ValidateSingleOutput(workloadInfo, "SpaceToBatchNdQueueDescriptor");
748
749     ValidateTensorNumDimensions(workloadInfo.m_InputTensorInfos[0], "SpaceToBatchNdQueueDescriptor", 4, "input");
750     ValidateTensorNumDimensions(workloadInfo.m_OutputTensorInfos[0], "SpaceToBatchNdQueueDescriptor", 4, "output");
751
752     if (workloadInfo.m_InputTensorInfos[0].GetNumElements() != workloadInfo.m_OutputTensorInfos[0].GetNumElements())
753     {
754         throw InvalidArgumentException("SpaceToBatchNdQueueDescriptor: Input tensor has " +
755             to_string(workloadInfo.m_InputTensorInfos[0].GetNumElements()) + " but output tensor has " +
756             to_string(workloadInfo.m_OutputTensorInfos[0].GetNumElements()) + " elements.");
757     }
758
759     if (m_Parameters.m_BlockShape.size() != 2)
760     {
761         throw InvalidArgumentException("Block Shape must contains 2 spatial dimensions");
762     }
763
764     if (m_Parameters.m_BlockShape.size() != m_Parameters.m_PadList.size())
765     {
766         throw InvalidArgumentException("Pad List must contains the same number of dimensions as Block Shape.");
767     }
768
769     const TensorShape inputShape = workloadInfo.m_InputTensorInfos[0].GetShape();
770
771     std::pair<unsigned int, unsigned int> heightPad = m_Parameters.m_PadList[0];
772     std::pair<unsigned int, unsigned int> widthPad = m_Parameters.m_PadList[1];
773
774     if ((inputShape[m_Parameters.m_DataLayout.GetHeightIndex()] + heightPad.first + heightPad.second)
775             % m_Parameters.m_BlockShape[0] != 0 ||
776         (inputShape[m_Parameters.m_DataLayout.GetWidthIndex()] + widthPad.first + widthPad.second)
777             % m_Parameters.m_BlockShape[1] != 0)
778     {
779         throw InvalidArgumentException(
780             "Input shape after padding must be divisible by Block Shape in all spatial dimensions");
781     }
782 }
783
784 void FloorQueueDescriptor::Validate(const WorkloadInfo& workloadInfo) const
785 {
786     ValidateSingleInput(workloadInfo, "FloorQueueDescriptor");
787     ValidateSingleOutput(workloadInfo, "FlootQueueDescriptor");
788
789     if (workloadInfo.m_InputTensorInfos[0] != workloadInfo.m_OutputTensorInfos[0])
790     {
791         throw InvalidArgumentException("FloorQueueDescriptor: Input and output tensor infos do not match.");
792     }
793 }
794
795 void LstmQueueDescriptor::Validate(const WorkloadInfo& workloadInfo) const
796 {
797     ValidateTensorNumDimensions(workloadInfo.m_InputTensorInfos[0], "LstmQueueDescriptor", 2, "input");
798     ValidateTensorNumDimensions(workloadInfo.m_OutputTensorInfos[0], "LstmQueueDescriptor", 2, "output");
799 }
800
801 void ConvertFp32ToFp16QueueDescriptor::Validate(const WorkloadInfo& workloadInfo) const
802 {
803     ValidateSingleInput(workloadInfo, "ConvertFp32ToFp16QueueDescriptor");
804     ValidateSingleOutput(workloadInfo, "ConvertFp32ToFp16QueueDescriptor");
805
806     if (workloadInfo.m_InputTensorInfos[0].GetDataType() != DataType::Float32)
807     {
808         throw InvalidArgumentException("ConvertFp32ToFp16QueueDescriptor: Input tensor type must be Float32.");
809     }
810
811     if (workloadInfo.m_OutputTensorInfos[0].GetDataType() != DataType::Float16)
812     {
813         throw InvalidArgumentException("ConvertFp32ToFp16QueueDescriptor: Output tensor type must be Float16.");
814     }
815
816     ValidateTensorShapesMatch(workloadInfo.m_InputTensorInfos[0],
817                               workloadInfo.m_OutputTensorInfos[0],
818                               "ConvertFp32ToFp16QueueDescriptor",
819                               "input",
820                               "output");
821 }
822
823 void ConvertFp16ToFp32QueueDescriptor::Validate(const WorkloadInfo& workloadInfo) const
824 {
825     ValidateSingleInput(workloadInfo, "ConvertFp16ToFp32QueueDescriptor");
826     ValidateSingleOutput(workloadInfo, "ConvertFp16ToFp32QueueDescriptor");
827
828     if (workloadInfo.m_InputTensorInfos[0].GetDataType() != DataType::Float16)
829     {
830         throw InvalidArgumentException("ConvertFp16ToFp32QueueDescriptor: Input tensor type must be Float16.");
831     }
832     if (workloadInfo.m_OutputTensorInfos[0].GetDataType() != DataType::Float32)
833     {
834         throw InvalidArgumentException("ConvertFp16ToFp32QueueDescriptor: Output tensor type must be Float32.");
835     }
836
837     ValidateTensorShapesMatch(workloadInfo.m_InputTensorInfos[0],
838                               workloadInfo.m_OutputTensorInfos[0],
839                               "ConvertFp16ToFp32QueueDescriptor",
840                               "input",
841                               "output");
842 }
843
844 void DivisionQueueDescriptor::Validate(const WorkloadInfo& workloadInfo) const
845 {
846     ValidateTwoInputs(workloadInfo, "DivisionQueueDescriptor");
847     ValidateSingleOutput(workloadInfo, "DivisionQueueDescriptor");
848
849     ValidateBroadcastTensorShapesMatch(workloadInfo.m_InputTensorInfos[0],
850                                        workloadInfo.m_InputTensorInfos[1],
851                                        workloadInfo.m_OutputTensorInfos[0],
852                                        "DivisionQueueDescriptor",
853                                        "first input",
854                                        "second input");
855 }
856
857 void SubtractionQueueDescriptor::Validate(const WorkloadInfo& workloadInfo) const
858 {
859     ValidateTwoInputs(workloadInfo, "SubtractionQueueDescriptor");
860     ValidateSingleOutput(workloadInfo, "SubtractionQueueDescriptor");
861
862     ValidateBroadcastTensorShapesMatch(workloadInfo.m_InputTensorInfos[0],
863                                        workloadInfo.m_InputTensorInfos[1],
864                                        workloadInfo.m_OutputTensorInfos[0],
865                                        "SubtractionQueueDescriptor",
866                                        "first input",
867                                        "second input");
868 }
869
870 void MeanQueueDescriptor::Validate(const WorkloadInfo& workloadInfo) const
871 {
872     ValidateSingleInput(workloadInfo, "MeanQueueDescriptor");
873     ValidateSingleOutput(workloadInfo, "MeanQueueDescriptor");
874
875     const TensorInfo& input  = workloadInfo.m_InputTensorInfos[0];
876     const TensorInfo& output = workloadInfo.m_OutputTensorInfos[0];
877
878     if (m_Parameters.m_KeepDims)
879     {
880         ValidateTensorNumDimensions(output, "MeanQueueDescriptor", input.GetNumDimensions(), "output");
881     }
882     else if (m_Parameters.m_Axis.empty())
883     {
884         ValidateTensorNumDimensions(output, "MeanQueueDescriptor", 1, "output");
885     }
886     else
887     {
888         auto outputDim = input.GetNumDimensions() - boost::numeric_cast<unsigned int>(m_Parameters.m_Axis.size());
889         ValidateTensorNumDimensions(output,
890                                     "MeanQueueDescriptor",
891                                     outputDim > 0 ? outputDim : 1,
892                                     "output");
893     }
894 }
895
896 void PadQueueDescriptor::Validate(const WorkloadInfo& workloadInfo) const
897 {
898     ValidateSingleInput(workloadInfo, "PadQueueDescriptor");
899     ValidateSingleOutput(workloadInfo, "PadQueueDescriptor");
900
901     const TensorInfo& input = workloadInfo.m_InputTensorInfos[0];
902     const TensorInfo& output = workloadInfo.m_OutputTensorInfos[0];
903
904     // input and output should have the same number of dimensions
905     ValidateTensorNumDimensions(output, "PadQueueDescriptor", input.GetNumDimensions(), "output");
906     // there should be entry in the pad list for each dimension in the input tensor
907     if (m_Parameters.m_PadList.size() != input.GetNumDimensions()) {
908         throw InvalidArgumentException("Pad List should contain the same number of entries as there"
909                                        " are dimensions in the input tensor that is " +
910                                        to_string(input.GetNumDimensions()) + " entries " +
911                                        " not " + to_string(m_Parameters.m_PadList.size()) + " entries.");
912     }
913 }
914
915 } //namespace armnn