ArmNN
 24.05
BatchMatMulImpl.cpp
Go to the documentation of this file.
1 //
2 // Copyright © 2022, 2024 Arm Ltd and Contributors. All rights reserved.
3 // SPDX-License-Identifier: MIT
4 //
5 
6 #include "BatchMatMulImpl.hpp"
7 
9 #include <armnn/Logging.hpp>
10 #include <armnnUtils/Permute.hpp>
11 
12 namespace armnn
13 {
14 
16  const TensorInfo& inputXInfo,
17  const TensorInfo& inputYInfo,
18  const TensorInfo& outputInfo,
19  Decoder<float>& inputXDecoder,
20  Decoder<float>& inputYDecoder,
21  Encoder<float>& outputEncoder)
22  : params(params),
23  inputXInfo(inputXInfo),
24  inputYInfo(inputYInfo),
25  outputInfo(outputInfo),
26  inputXDecoder(inputXDecoder),
27  inputYDecoder(inputYDecoder),
28  outputEncoder(outputEncoder)
29 {
30  inputXData = this->inputXDecoder.DecodeTensor(inputXInfo.GetShape());
31  inputYData = this->inputYDecoder.DecodeTensor(inputYInfo.GetShape());
32  // At this point, we don't touch the input decoders - just the resultant vectors
33 
34  ApplyParams();
35 
36  ApplyBatchMatMul();
37 }
38 
39 void BatchMatMul::ApplyBatchMatMul()
40 {
41  auto axesXToMul = BatchMatMulDescriptor::GetAxesToMul(params.m_DataLayoutX,
42  inputXInfo.GetShape());
43  auto axesYToMul = BatchMatMulDescriptor::GetAxesToMul(params.m_DataLayoutY,
44  inputYInfo.GetShape());
45  AdjustAxesToMulForUnequalRanks(axesXToMul, axesYToMul);
46 
47  unsigned int inputXColDim = axesXToMul.second;
48  unsigned int inputYRowDim = axesYToMul.first;
49 
50  unsigned int inputYRowSize = inputYInfo.GetShape()[inputYRowDim];
51 
52  auto batchMatMulOperation = [&](const std::vector<unsigned int>& curIdx)
53  {
54  float sum = 0.0f;
55 
56  // InputYRowSize is synonymous with inputXColSize
57  for (unsigned int inputYRowIdx = 0; inputYRowIdx < inputYRowSize; inputYRowIdx++) {
58  auto xIdx = curIdx;
59  xIdx[inputXColDim] = inputYRowIdx;
60 
61  auto yIdx = curIdx;
62  yIdx[inputYRowDim] = inputYRowIdx;
63 
64  sum += (GetValueAt(DataSlot::InputX, xIdx) * GetValueAt(DataSlot::InputY, yIdx));
65  }
66 
67  SetValueAt(sum, DataSlot::Output, curIdx);
68  };
69 
70  auto startIdx = std::vector<unsigned int>(outputInfo.GetNumDimensions(), 0);
71  RecurseTensor(outputInfo,
72  batchMatMulOperation,
73  startIdx,
74  0);
75 }
76 
77 void BatchMatMul::ApplyParams()
78 {
79  if(params.m_TransposeX)
80  {
81  Transpose(DataSlot::InputX);
82  }
83  else if(params.m_AdjointX)
84  {
85  Adjoint(DataSlot::InputX);
86  }
87  if(params.m_TransposeY)
88  {
89  Transpose(DataSlot::InputY);
90  }
91  else if(params.m_AdjointY)
92  {
93  Adjoint(DataSlot::InputY);
94  }
95 }
96 
97 void BatchMatMul::Transpose(DataSlot type)
98 {
99  // AKA the permute of the tensor
100  // This modifies the tensor's info.
101 
102  switch(type)
103  {
104  case DataSlot::InputX:
105  {
106  auto permuteVec = BatchMatMulDescriptor::GetPermuteVec(params.m_DataLayoutX,
107  inputXInfo.GetShape());
108  inputXInfo = armnnUtils::Permuted(inputXInfo, permuteVec);
109  std::vector<float> temp(inputXData.size());
110  armnnUtils::Permute(inputXInfo.GetShape(),
111  permuteVec,
112  inputXData.data(),
113  temp.data(),
114  sizeof(float));
115  inputXData = temp;
116  break;
117  }
118  case DataSlot::InputY:
119  {
120  auto permuteVec = BatchMatMulDescriptor::GetPermuteVec(params.m_DataLayoutY,
121  inputYInfo.GetShape());
122  inputYInfo = armnnUtils::Permuted(inputYInfo, permuteVec);
123  std::vector<float> temp(inputYData.size());
124  armnnUtils::Permute(inputYInfo.GetShape(),
125  permuteVec,
126  inputYData.data(),
127  temp.data(),
128  sizeof(float));
129  inputYData = temp;
130  break;
131  }
132  case DataSlot::Output: // We needn't transpose the output tensor
133  default:
134  break;
135  }
136 }
137 
138 void BatchMatMul::Adjoint(DataSlot type)
139 {
140  // Finding the adjoint of a square matrix:
141  // Calculate the cofactor of each element (using Gauss elimination here)
142  // Apply a transpose to it (this also modifies the tensor's info)
143 
144  TensorInfo& inputInfo = (type == DataSlot::InputX) ? inputXInfo : inputYInfo;
145  const auto& dataLayout = (type == DataSlot::InputX) ? params.m_DataLayoutX : params.m_DataLayoutY;
146  const auto axesToAdjoint = BatchMatMulDescriptor::GetAxesToMul(dataLayout,inputInfo.GetShape());
147 
148  // We grab a copy of the tensor data to prevent overwriting
149  std::vector<float> inputDataClone = (type == DataSlot::InputX) ? inputXData : inputYData;
150 
151  // The sub-matrix is the resultant matrix when the row and column of the current index is removed
152  unsigned int subMatAxisSize = inputInfo.GetShape()[axesToAdjoint.first] - 1;
153  std::vector<std::vector<float>> subMat(subMatAxisSize,
154  std::vector<float>(subMatAxisSize));
155 
156  // Lambdas for each sub-step of the cofactor operation
157  auto almostEquals = [&](const float& a, const float& b, float unitsInLastPlace = 2.0f)
158  {
159  float diff = std::fabs(a-b);
160  float bound = diff * std::numeric_limits<float>::epsilon() * unitsInLastPlace;
161  return (diff <= bound) || (diff < std::numeric_limits<float>::min());
162  };
163 
164  float swapMultiplier = std::numeric_limits<float>::max();
165  auto swapRows = [&](unsigned int rowIdxA, unsigned int rowIdxB)
166  {
167  // Every row swap flips this around by the negative (set to 1 at the beginning of each cofactor op run)
168  for(unsigned int colIdx = 0; colIdx < subMatAxisSize; colIdx++)
169  {
170  float tmp = subMat[rowIdxA][colIdx];
171  subMat[rowIdxA][colIdx] = subMat[rowIdxB][colIdx];
172  subMat[rowIdxB][colIdx] = tmp;
173  }
174  swapMultiplier *= -1.0f;
175  };
176 
177  auto findNextValidPivotRowIdx = [&](unsigned int colIdx)
178  {
179  unsigned int result = std::numeric_limits<unsigned int>::max();
180 
181  // The original diagonal has been checked and is invalid
182  for(unsigned int rowIdx = colIdx+1; rowIdx < subMatAxisSize; rowIdx++)
183  {
184  if(!almostEquals(subMat[rowIdx][colIdx], 0.0f))
185  {
186  result = rowIdx;
187  break;
188  }
189  }
190  return result;
191  };
192 
193  auto eliminate = [&](const float& pivot, unsigned int pivotPos)
194  {
195  for(unsigned int rowIdx = pivotPos+1; rowIdx < subMatAxisSize; rowIdx++)
196  {
197  float multiplierNumerator = subMat[rowIdx][pivotPos];
198  if(almostEquals(multiplierNumerator, 0.0f))
199  {
200  continue;
201  }
202  float multiplier = multiplierNumerator / pivot; // Susceptible to floating point inaccuracies
203  // Hence the almostEquals usage to counteract this
204  for(unsigned int colIdx = pivotPos; colIdx < subMatAxisSize; colIdx++)
205  {
206  // We start at col=pivotPos as we have assumed that all elements
207  // to our left have been eliminated to zero already
208 
209  // We subtract based on the element directly above us in our pivot row
210  subMat[rowIdx][colIdx] -= multiplier * subMat[pivotPos][colIdx];
211  }
212  }
213  };
214 
215  auto cofactorOperation = [&](const std::vector<unsigned int>& curIdx)
216  {
217  auto row = curIdx[axesToAdjoint.first];
218  auto col = curIdx[axesToAdjoint.second];
219 
220  float minorMultiplier = static_cast<float>(std::pow(-1, (row + 1 + col + 1)));
221 
222  for(unsigned int subRow = 0; subRow < subMatAxisSize; subRow++)
223  {
224  for(unsigned int subCol = 0; subCol < subMatAxisSize; subCol++)
225  {
226  unsigned int outerRow = (subRow >= row)?subRow + 1:subRow;
227  unsigned int outerCol = (subCol >= col)?subCol + 1:subCol;
228  auto cloneIdx = curIdx;
229  cloneIdx[axesToAdjoint.first] = outerRow;
230  cloneIdx[axesToAdjoint.second] = outerCol;
231  subMat[subRow][subCol] = GetValueAt(type,cloneIdx,inputDataClone);
232  }
233  }
234 
235  float determinant = 1.0f;
236 
237  // Cover the edge cases and simple base cases before resorting to Gauss elimination for larger matrices
238  switch(subMatAxisSize)
239  {
240  case 0:
241  {
242  determinant = GetValueAt(type, curIdx, inputDataClone);
243  break;
244  }
245  case 1:
246  {
247  // If the resultant sub-matrix is just one element - that's the determinant
248  determinant = subMat[0][0];
249  break;
250  }
251  case 2:
252  {
253  // For a 2x2 sub-matrix, the determinant is just a*d-b*c
254  determinant = subMat[0][0] * subMat[1][1] -
255  subMat[0][1] * subMat[1][0];
256  break;
257  }
258  default:
259  {
260  // Gaussian elimination to find the determinant of this sub-matrix
261  swapMultiplier = 1.0f;
262  // March diagonally down the pivots and if it's invalid (a zero), swap the row with the
263  // nearest non-zero down within the column
264  for(unsigned int pivotRow = 0, pivotCol = 0;
265  pivotRow < subMatAxisSize;
266  pivotRow++, pivotCol++)
267  {
268  float& pivot = subMat[pivotRow][pivotCol];
269 
270  if(almostEquals(pivot, 0.0f))
271  {
272  unsigned int nextValidPivotRowIdx = findNextValidPivotRowIdx(pivotCol);
273  if(nextValidPivotRowIdx == std::numeric_limits<unsigned int>::max())
274  {
275  // No valid pivot down this column, which means that this pivot remains a zero.
276  // This results in the determinant for this entire sub-matrix to just be zero.
277  determinant = 0.0f;
278  break;
279  }
280  swapRows(pivotRow, nextValidPivotRowIdx);
281  }
282  determinant *= pivot;
283  // The actual elimination bit (which will update/propagate to the pivots down the line)
284  eliminate(pivot, pivotRow); // Synonymous with pivotCol
285  }
286 
287  determinant *= swapMultiplier;
288  break;
289  }
290  }
291  float cofactor = minorMultiplier * determinant;
292  SetValueAt(cofactor, type, curIdx);
293  };
294 
295  auto startIdx = std::vector<unsigned int>(inputInfo.GetNumDimensions(), 0);
296  RecurseTensor(inputInfo,
297  cofactorOperation,
298  startIdx,
299  0);
300 
301  Transpose(type);
302 }
303 
304 void BatchMatMul::RecurseTensor(const TensorInfo& tensorInfo,
305  const std::function<void(const std::vector<unsigned int>&)>& operation,
306  std::vector<unsigned int>& curIdx,
307  unsigned int curDim)
308 {
309  if(!(curDim < tensorInfo.GetNumDimensions()))
310  {
311  // We're at the leaf level of this call tree, so we operate here (each leaf is a data point)
312  operation(curIdx);
313  return;
314  }
315 
316  for(unsigned int i = 0; i < tensorInfo.GetShape()[curDim]; i++)
317  {
318  curIdx[curDim] = i;
319  RecurseTensor(tensorInfo,
320  operation,
321  curIdx,
322  curDim + 1);
323  }
324 }
325 
326 void BatchMatMul::AdjustAxesToMulForUnequalRanks(std::pair<unsigned int, unsigned int>& axesXToMul,
327  std::pair<unsigned int, unsigned int>& axesYToMul)
328 {
329  int rankDiff = static_cast<int>(inputXInfo.GetNumDimensions()) -
330  static_cast<int>(inputYInfo.GetNumDimensions());
331  if(rankDiff == 0)
332  {
333  return;
334  }
335  else if(rankDiff < 0)
336  {
337  // Y is the larger one
338  axesXToMul.first += static_cast<std::make_unsigned<unsigned int>::type>(std::abs(rankDiff));
339  axesXToMul.second += static_cast<std::make_unsigned<unsigned int>::type>(std::abs(rankDiff));
340  }
341  else if(rankDiff > 0)
342  {
343  // X is the larger one
344  axesYToMul.first += static_cast<std::make_unsigned<unsigned int>::type>(std::abs(rankDiff));
345  axesYToMul.second += static_cast<std::make_unsigned<unsigned int>::type>(std::abs(rankDiff));
346  }
347 }
348 
349 float BatchMatMul::GetValueAt(DataSlot type, std::vector<unsigned int> idx, const std::vector<float>& customData)
350 {
351  // This gets the data from the input vector that we have, Not the decoder
352  // But for the output, it is operating on the encoder itself
353 
354  AdjustToSafeIdx(type, idx);
355  unsigned int flatIdx = CalcFlatIdx(type, idx);
356  float value = 0.0f;
357  switch(type)
358  {
359  case DataSlot::InputX:
360  value = customData.empty() ? inputXData[flatIdx] : customData[flatIdx];
361  break;
362  case DataSlot::InputY:
363  value = customData.empty() ? inputYData[flatIdx] : customData[flatIdx];
364  break;
365  case DataSlot::Output:
366  outputEncoder[flatIdx];
367  value = outputEncoder.Get();
368  break;
369  default:
370  break;
371  }
372 
373  return value;
374 }
375 
376 void BatchMatMul::SetValueAt(float value, DataSlot type, std::vector<unsigned int> idx)
377 {
378  AdjustToSafeIdx(type, idx);
379  unsigned int flatIdx = CalcFlatIdx(type, idx);
380  switch(type)
381  {
382  case DataSlot::InputX:
383  inputXData[flatIdx] = value;
384  break;
385  case DataSlot::InputY:
386  inputYData[flatIdx] = value;
387  break;
388  case DataSlot::Output:
389  outputEncoder[flatIdx];
390  outputEncoder.Set(value);
391  break;
392  default:
393  break;
394  }
395 }
396 
397 void BatchMatMul::AdjustToSafeIdx(DataSlot type, std::vector<unsigned int>& idx)
398 {
399  for(unsigned int dim = 0; dim < idx.size(); dim++)
400  {
401  switch(type)
402  {
403  case DataSlot::InputX:
404  {
405  auto xRank = inputXInfo.GetNumDimensions();
406  auto xDiff = outputInfo.GetNumDimensions() - xRank;
407  if (dim < xDiff ||
408  idx[dim] > inputXInfo.GetShape()[dim-xDiff]-1)
409  {
410  idx[dim] = 0; // Broadcasting
411  }
412  break;
413  }
414  case DataSlot::InputY:
415  {
416  auto yRank = inputYInfo.GetNumDimensions();
417  auto yDiff = outputInfo.GetNumDimensions() - yRank;
418  if (dim < yDiff ||
419  idx[dim] > inputYInfo.GetShape()[dim-yDiff]-1)
420  {
421  idx[dim] = 0;
422  }
423  break;
424  }
425  case DataSlot::Output:
426  {
427  // Our indices are based off the output
428  break;
429  }
430  default:
431  break;
432  }
433  }
434 }
435 
436 unsigned int BatchMatMul::CalcFlatIdx(DataSlot type, const std::vector<unsigned int>& idx)
437 {
438  unsigned int result = idx[idx.size()-1];
439  unsigned int dimMultiplier = 1;
440  unsigned int offset;
441 
442  // -2 because final dim is already accounted for in the multiplier (last dim is just a multiplier of 1x)
443  for(unsigned int i = static_cast<unsigned int>(idx.size()-2); static_cast<int>(i) >= 0; i--)
444  {
445  switch(type)
446  {
447  case DataSlot::InputX:
448  offset = outputInfo.GetNumDimensions() - inputXInfo.GetNumDimensions();
449  dimMultiplier *= inputXInfo.GetShape()[i + 1 - offset];
450  break;
451  case DataSlot::InputY:
452  offset = outputInfo.GetNumDimensions() - inputYInfo.GetNumDimensions();
453  dimMultiplier *= inputYInfo.GetShape()[i + 1 - offset];
454  break;
455  case DataSlot::Output:
456  dimMultiplier *= outputInfo.GetShape()[i+1];
457  break;
458  default:
459  break;
460  }
461  result += (idx[i] * dimMultiplier);
462  }
463  return result;
464 }
465 
466 } // namespace armnn
armnn::Decoder< float >
armnn::BatchMatMulDescriptor::m_TransposeX
bool m_TransposeX
Transpose the slices of each input tensor Transpose and Adjoint can not both be set to true for the s...
Definition: Descriptors.hpp:1612
armnn::Encoder::Set
virtual void Set(IType right)=0
WorkloadData.hpp
armnn::BatchMatMulDescriptor::m_AdjointX
bool m_AdjointX
Adjoint the slices of each input tensor Transpose and Adjoint can not both be set to true for the sam...
Definition: Descriptors.hpp:1617
armnn::BatchMatMulDescriptor::GetAxesToMul
static std::pair< unsigned int, unsigned int > GetAxesToMul(DataLayout dataLayout, const TensorShape &tensorShape)
Static helper to get the two axes (for each input) for multiplication.
Definition: Descriptors.cpp:485
BatchMatMulImpl.hpp
armnn::Encoder::Get
virtual IType Get() const =0
armnn::BatchMatMulDescriptor::m_DataLayoutX
DataLayout m_DataLayoutX
Data layout of each input tensor, such as NHWC/NDHWC (leave as default for arbitrary layout)
Definition: Descriptors.hpp:1621
armnn::TensorInfo
Definition: Tensor.hpp:152
armnn::TensorInfo::GetNumDimensions
unsigned int GetNumDimensions() const
Definition: Tensor.hpp:197
armnn::BatchMatMulDescriptor::GetPermuteVec
static PermutationVector GetPermuteVec(DataLayout dataLayout, const TensorShape &tensorShape)
Static helper to get the axes which will be transposed.
Definition: Descriptors.cpp:523
armnn::BatchMatMulDescriptor::m_AdjointY
bool m_AdjointY
Definition: Descriptors.hpp:1618
armnnUtils::Permute
void Permute(const armnn::TensorShape &dstShape, const armnn::PermutationVector &mappings, const void *src, void *dst, size_t dataTypeSize)
Definition: Permute.cpp:164
armnnUtils::Permuted
armnn::TensorShape Permuted(const armnn::TensorShape &srcShape, const armnn::PermutationVector &mappings)
Definition: Permute.cpp:125
armnn::Encoder< float >
Logging.hpp
armnn::BatchMatMulDescriptor::m_TransposeY
bool m_TransposeY
Definition: Descriptors.hpp:1613
armnn::BatchMatMulDescriptor::m_DataLayoutY
DataLayout m_DataLayoutY
Definition: Descriptors.hpp:1622
armnn::BatchMatMulDescriptor
A BatchMatMulDescriptor for the BatchMatMul operator.
Definition: Descriptors.hpp:1584
armnn::Decoder::DecodeTensor
virtual std::vector< float > DecodeTensor(const TensorShape &tensorShape, bool isDepthwise=false)=0
Permute.hpp
armnn::TensorInfo::GetShape
const TensorShape & GetShape() const
Definition: Tensor.hpp:193
armnn
Copyright (c) 2021 ARM Limited and Contributors.
Definition: 01_00_quick_start.dox:6
armnn::BatchMatMul::BatchMatMul
BatchMatMul(const BatchMatMulDescriptor &params, const TensorInfo &inputXInfo, const TensorInfo &inputYInfo, const TensorInfo &outputInfo, Decoder< float > &inputXDecoder, Decoder< float > &inputYDecoder, Encoder< float > &outputEncoder)
Definition: BatchMatMulImpl.cpp:15