ArmNN
 26.07
Sme2ShapePolicy.cpp
Go to the documentation of this file.
1 //
2 // Copyright © 2026 Arm Ltd and Contributors. All rights reserved.
3 // SPDX-License-Identifier: MIT
4 //
5 
6 #include "Sme2ShapePolicy.hpp"
7 
8 #include "Graph.hpp"
9 #include "Layer.hpp"
11 
12 #include <armnn/Descriptors.hpp>
13 #include <armnn/Tensor.hpp>
14 
15 #include <algorithm>
16 #include <cstdint>
17 
18 namespace armnn
19 {
20 namespace
21 {
22 
23 struct Sme2ShapeProfile
24 {
25  unsigned int m_GemmLikeOps = 0;
26  unsigned int m_DepthwiseConvolution2dOps = 0;
27  unsigned int m_SmallDenseProjectionOps = 0;
28  int64_t m_GemmMacs = 0;
30  bool m_HasFp16 = false;
31  bool m_HasQuantized = false;
32  bool m_HasSegmentationShape = false;
34  bool m_HasPoseShape = false;
36 };
37 
38 bool IsQuantizedDataType(DataType dataType)
39 {
40  switch (dataType)
41  {
42  case DataType::QAsymmU8:
43  case DataType::QAsymmS8:
44  case DataType::QSymmS8:
45  case DataType::QSymmS16:
46  return true;
47  default:
48  return false;
49  }
50 }
51 
52 void RecordTensorType(Sme2ShapeProfile& profile, const TensorInfo& tensorInfo)
53 {
54  const DataType dataType = tensorInfo.GetDataType();
55  profile.m_HasFp16 |= dataType == DataType::Float16;
56  profile.m_HasQuantized |= IsQuantizedDataType(dataType);
57 }
58 
59 bool HasSpecifiedShape(const TensorInfo& tensorInfo)
60 {
61  const TensorShape& shape = tensorInfo.GetShape();
62  return shape.GetDimensionality() == Dimensionality::Specified &&
63  shape.AreAllDimensionsSpecified();
64 }
65 
66 int64_t NumElements(const TensorShape& shape)
67 {
68  if (shape.GetDimensionality() != Dimensionality::Specified ||
69  !shape.AreAllDimensionsSpecified())
70  {
71  return 0;
72  }
73 
74  int64_t elements = 1;
75  for (unsigned int i = 0; i < shape.GetNumDimensions(); ++i)
76  {
77  elements *= static_cast<int64_t>(std::max(shape[i], 1U));
78  }
79  return elements;
80 }
81 
82 int64_t Dimension(const TensorShape& shape, unsigned int index)
83 {
84  if (shape.GetDimensionality() != Dimensionality::Specified ||
85  !shape.AreAllDimensionsSpecified() ||
86  index >= shape.GetNumDimensions())
87  {
88  return 0;
89  }
90  return static_cast<int64_t>(shape[index]);
91 }
92 
93 int64_t DimensionFromEnd(const TensorShape& shape, unsigned int offset)
94 {
95  if (offset == 0 || shape.GetNumDimensions() < offset)
96  {
97  return 0;
98  }
99  return Dimension(shape, shape.GetNumDimensions() - offset);
100 }
101 
102 void RecordGemmShape(Sme2ShapeProfile& profile,
103  int64_t m,
104  int64_t n,
105  int64_t k,
106  int64_t kernelH,
107  int64_t kernelW,
108  bool isDenseProjection)
109 {
110  if (m <= 0 || n <= 0 || k <= 0)
111  {
112  return;
113  }
114 
115  ++profile.m_GemmLikeOps;
116 
117  const bool is1x1 = kernelH == 1 && kernelW == 1;
118  const int64_t macs = m * n * k;
119  profile.m_GemmMacs += macs;
120  if (!is1x1)
121  {
122  profile.m_NonPointwiseGemmMacs += macs;
123  }
124  if (isDenseProjection && is1x1 && m <= 256 && n <= 1024 && k <= 1024)
125  {
126  ++profile.m_SmallDenseProjectionOps;
127  }
128 
129  const bool hasModerateSpatialM = m >= 2048 && m <= 2560;
130  if (is1x1 && hasModerateSpatialM && ((n >= 900 && k <= 384) || (n <= 384 && k >= 900)))
131  {
132  profile.m_HasSegmentationShape = true;
133  }
134 
135  if (!is1x1 && ((m >= 25000 && m <= 30000 && n == 64 && k >= 2000) ||
136  (m >= 60000 && m <= 75000 && n == 32 && k >= 500)))
137  {
138  profile.m_HasStyleTransferShape = true;
139  }
140 
141  if (!is1x1 && m >= 100000 && n >= 64 && k >= 500)
142  {
143  profile.m_HasPoseShape = true;
144  }
145 
146  if (m <= 64 && n >= 4096 && k >= 64 && k <= 1024)
147  {
148  profile.m_HasSmallMLargeNProjection = true;
149  }
150 }
151 
152 void RecordConvolution2d(Sme2ShapeProfile& profile, const Layer& layer)
153 {
154  if (layer.GetNumInputSlots() < 2 ||
155  layer.GetNumOutputSlots() == 0 ||
156  !layer.GetInputSlot(0).IsTensorInfoSet() ||
157  !layer.GetInputSlot(1).IsTensorInfoSet() ||
158  !layer.GetOutputSlot(0).IsTensorInfoSet())
159  {
160  return;
161  }
162 
163  const TensorInfo& inputInfo = layer.GetInputSlot(0).GetTensorInfo();
164  const TensorInfo& filterInfo = layer.GetInputSlot(1).GetTensorInfo();
165  const TensorInfo& outputInfo = layer.GetOutputSlot(0).GetTensorInfo();
166  RecordTensorType(profile, inputInfo);
167  RecordTensorType(profile, filterInfo);
168  RecordTensorType(profile, outputInfo);
169 
170  if (!HasSpecifiedShape(filterInfo) || !HasSpecifiedShape(outputInfo))
171  {
172  return;
173  }
174 
175  const Convolution2dDescriptor& descriptor =
176  static_cast<const Convolution2dDescriptor&>(layer.GetParameters());
177  const TensorShape& filterShape = filterInfo.GetShape();
178  const TensorShape& outputShape = outputInfo.GetShape();
179  const armnnUtils::DataLayoutIndexed dataLayoutIndex(descriptor.m_DataLayout);
180 
181  if (filterShape.GetNumDimensions() != 4 || outputShape.GetNumDimensions() != 4)
182  {
183  return;
184  }
185 
186  const int64_t n = Dimension(filterShape, 0);
187  const int64_t kernelH = Dimension(filterShape, dataLayoutIndex.GetHeightIndex());
188  const int64_t kernelW = Dimension(filterShape, dataLayoutIndex.GetWidthIndex());
189  const int64_t filterElements = NumElements(filterShape);
190  const int64_t inputChannels = n > 0 && kernelH > 0 && kernelW > 0 ?
191  filterElements / (n * kernelH * kernelW) : 0;
192  const int64_t k = kernelH * kernelW * inputChannels;
193  const int64_t outputElements = NumElements(outputShape);
194  const int64_t m = n > 0 ? outputElements / n : 0;
195 
196  RecordGemmShape(profile, m, n, k, kernelH, kernelW, false);
197 }
198 
199 void RecordFullyConnected(Sme2ShapeProfile& profile, const Layer& layer)
200 {
201  if (layer.GetNumInputSlots() < 2 ||
202  layer.GetNumOutputSlots() == 0 ||
203  !layer.GetInputSlot(0).IsTensorInfoSet() ||
204  !layer.GetInputSlot(1).IsTensorInfoSet() ||
205  !layer.GetOutputSlot(0).IsTensorInfoSet())
206  {
207  return;
208  }
209 
210  const TensorInfo& inputInfo = layer.GetInputSlot(0).GetTensorInfo();
211  const TensorInfo& weightsInfo = layer.GetInputSlot(1).GetTensorInfo();
212  const TensorInfo& outputInfo = layer.GetOutputSlot(0).GetTensorInfo();
213  RecordTensorType(profile, inputInfo);
214  RecordTensorType(profile, weightsInfo);
215  RecordTensorType(profile, outputInfo);
216 
217  if (!HasSpecifiedShape(inputInfo) || !HasSpecifiedShape(weightsInfo) || !HasSpecifiedShape(outputInfo))
218  {
219  return;
220  }
221 
222  const TensorShape& weightsShape = weightsInfo.GetShape();
223  if (weightsShape.GetNumDimensions() < 2)
224  {
225  return;
226  }
227 
228  const FullyConnectedDescriptor& descriptor =
229  static_cast<const FullyConnectedDescriptor&>(layer.GetParameters());
230  const unsigned int nIndex = descriptor.m_TransposeWeightMatrix ? 0U : 1U;
231  const unsigned int kIndex = descriptor.m_TransposeWeightMatrix ? 1U : 0U;
232  const int64_t n = Dimension(weightsShape, nIndex);
233  const int64_t k = Dimension(weightsShape, kIndex);
234  const int64_t outputElements = NumElements(outputInfo.GetShape());
235  const int64_t m = n > 0 ? outputElements / n : 0;
236 
237  RecordGemmShape(profile, m, n, k, 1, 1, true);
238 }
239 
240 void RecordBatchMatMul(Sme2ShapeProfile& profile, const Layer& layer)
241 {
242  if (layer.GetNumInputSlots() < 2 ||
243  layer.GetNumOutputSlots() == 0 ||
244  !layer.GetInputSlot(0).IsTensorInfoSet() ||
245  !layer.GetInputSlot(1).IsTensorInfoSet() ||
246  !layer.GetOutputSlot(0).IsTensorInfoSet())
247  {
248  return;
249  }
250 
251  const TensorInfo& lhsInfo = layer.GetInputSlot(0).GetTensorInfo();
252  const TensorInfo& rhsInfo = layer.GetInputSlot(1).GetTensorInfo();
253  const TensorInfo& outputInfo = layer.GetOutputSlot(0).GetTensorInfo();
254  RecordTensorType(profile, lhsInfo);
255  RecordTensorType(profile, rhsInfo);
256  RecordTensorType(profile, outputInfo);
257 
258  if (!HasSpecifiedShape(lhsInfo) || !HasSpecifiedShape(rhsInfo) || !HasSpecifiedShape(outputInfo))
259  {
260  return;
261  }
262 
263  const TensorShape& lhsShape = lhsInfo.GetShape();
264  const TensorShape& outputShape = outputInfo.GetShape();
265  const int64_t n = DimensionFromEnd(outputShape, 1);
266  const int64_t m = n > 0 ? NumElements(outputShape) / n : 0;
267  int64_t k = DimensionFromEnd(lhsShape, 1);
268  if (k == n)
269  {
270  k = DimensionFromEnd(lhsShape, 2);
271  }
272 
273  RecordGemmShape(profile, m, n, k, 1, 1, true);
274 }
275 
276 void RecordDepthwiseConvolution2d(Sme2ShapeProfile& profile, const Layer& layer)
277 {
278  ++profile.m_DepthwiseConvolution2dOps;
279 
280  for (unsigned int i = 0; i < layer.GetNumInputSlots(); ++i)
281  {
282  if (layer.GetInputSlot(i).IsTensorInfoSet())
283  {
284  RecordTensorType(profile, layer.GetInputSlot(i).GetTensorInfo());
285  }
286  }
287  for (unsigned int i = 0; i < layer.GetNumOutputSlots(); ++i)
288  {
289  if (layer.GetOutputSlot(i).IsTensorInfoSet())
290  {
291  RecordTensorType(profile, layer.GetOutputSlot(i).GetTensorInfo());
292  }
293  }
294 }
295 
296 Sme2ShapeProfile BuildSme2ShapeProfile(const Graph& graph, bool reduceFp32ToFp16)
297 {
298  Sme2ShapeProfile profile;
299  profile.m_HasFp16 = reduceFp32ToFp16;
300 
301  for (const Layer* layer : graph)
302  {
303  switch (layer->GetType())
304  {
306  RecordConvolution2d(profile, *layer);
307  break;
309  RecordFullyConnected(profile, *layer);
310  break;
312  RecordBatchMatMul(profile, *layer);
313  break;
315  RecordDepthwiseConvolution2d(profile, *layer);
316  break;
317  default:
318  for (unsigned int i = 0; i < layer->GetNumInputSlots(); ++i)
319  {
320  if (layer->GetInputSlot(i).IsTensorInfoSet())
321  {
322  RecordTensorType(profile, layer->GetInputSlot(i).GetTensorInfo());
323  }
324  }
325  for (unsigned int i = 0; i < layer->GetNumOutputSlots(); ++i)
326  {
327  if (layer->GetOutputSlot(i).IsTensorInfoSet())
328  {
329  RecordTensorType(profile, layer->GetOutputSlot(i).GetTensorInfo());
330  }
331  }
332  break;
333  }
334  }
335 
336  return profile;
337 }
338 
339 unsigned int CapWorkerCount(unsigned int workers, unsigned int cap)
340 {
341  if (workers == 0 || cap == 0 || cap >= workers)
342  {
343  return workers;
344  }
345  return cap;
346 }
347 
348 unsigned int GetCpuAccNumberOfThreads(const ModelOptions& modelOptions)
349 {
350  unsigned int numberOfThreads = 0;
351  ParseOptions(modelOptions, "CpuAcc", [&](std::string name, const BackendOptions::Var& value)
352  {
353  if (name == "NumberOfThreads")
354  {
355  if (value.IsUnsignedInt())
356  {
357  numberOfThreads = value.AsUnsignedInt();
358  }
359  else if (value.IsInt() && value.AsInt() > 0)
360  {
361  numberOfThreads = static_cast<unsigned int>(value.AsInt());
362  }
363  }
364  });
365  return numberOfThreads;
366 }
367 
368 bool HasFloatSmeRegressionRisk(const Sme2ShapeProfile& profile)
369 {
370  const bool isFloatOnly = !profile.m_HasFp16 && !profile.m_HasQuantized;
371  if (!isFloatOnly)
372  {
373  return false;
374  }
375 
376  const bool hasHeavySpatialConvolution =
377  profile.m_GemmMacs > 0 &&
378  profile.m_NonPointwiseGemmMacs * 2 >= profile.m_GemmMacs &&
379  !profile.m_HasSegmentationShape;
380 
381  const bool hasSmallDenseGraph =
382  profile.m_DepthwiseConvolution2dOps == 0 &&
383  profile.m_SmallDenseProjectionOps >= 4 &&
384  !profile.m_HasSmallMLargeNProjection;
385 
386  return profile.m_HasPoseShape ||
387  profile.m_HasStyleTransferShape ||
388  hasHeavySpatialConvolution ||
389  hasSmallDenseGraph;
390 }
391 
392 bool ShouldDisableSme(const Sme2ShapeProfile& profile)
393 {
394  if (profile.m_GemmLikeOps == 0)
395  {
396  return false;
397  }
398 
399  if (profile.m_HasFp16)
400  {
401  return true;
402  }
403 
404  if (profile.m_HasQuantized)
405  {
406  return !profile.m_HasSmallMLargeNProjection;
407  }
408 
409  return HasFloatSmeRegressionRisk(profile);
410 }
411 
412 unsigned int SelectNumberOfThreads(const Sme2ShapeProfile& profile, unsigned int requestedThreads)
413 {
414  if (!profile.m_HasQuantized || ShouldDisableSme(profile))
415  {
416  return requestedThreads;
417  }
418 
419  if (profile.m_GemmLikeOps == 0)
420  {
421  return CapWorkerCount(requestedThreads, 1);
422  }
423 
424  if (profile.m_HasSegmentationShape || profile.m_HasStyleTransferShape)
425  {
426  return requestedThreads;
427  }
428 
429  if (profile.m_HasPoseShape)
430  {
431  return CapWorkerCount(requestedThreads, 4);
432  }
433 
434  return CapWorkerCount(requestedThreads, 1);
435 }
436 
437 } // namespace
438 
439 void ApplySme2ShapePolicy(const Graph& graph, bool reduceFp32ToFp16, ModelOptions& modelOptions)
440 {
441  const Sme2ShapeProfile profile = BuildSme2ShapeProfile(graph, reduceFp32ToFp16);
442  if (profile.m_GemmLikeOps == 0)
443  {
444  return;
445  }
446 
447  const bool smeEnabled = !ShouldDisableSme(profile);
448  const bool sveEnabled = true;
449  const unsigned int requestedThreads = GetCpuAccNumberOfThreads(modelOptions);
450  const unsigned int selectedThreads = SelectNumberOfThreads(profile, requestedThreads);
451 
452  modelOptions.push_back(BackendOptions("CpuAcc", {{"SmeEnabled", smeEnabled}, {"SveEnabled", sveEnabled}}));
453  if (selectedThreads != requestedThreads)
454  {
455  modelOptions.push_back(BackendOptions("CpuAcc", {{"NumberOfThreads", selectedThreads}}));
456  }
457 }
458 
459 } // namespace armnn
bool m_HasFp16
unsigned int m_GemmLikeOps
bool m_HasStyleTransferShape
int64_t m_GemmMacs
bool m_HasPoseShape
bool m_HasSegmentationShape
bool m_HasQuantized
int64_t m_NonPointwiseGemmMacs
bool m_HasSmallMLargeNProjection
unsigned int m_DepthwiseConvolution2dOps
unsigned int m_SmallDenseProjectionOps
Provides access to the appropriate indexes for Channels, Height and Width based on DataLayout.
Copyright (c) 2021 ARM Limited and Contributors.
std::vector< BackendOptions > ModelOptions
void ApplySme2ShapePolicy(const Graph &graph, bool reduceFp32ToFp16, ModelOptions &modelOptions)
DataType
Definition: Types.hpp:49
void ParseOptions(const std::vector< BackendOptions > &options, BackendId backend, F f)
Struct for the users to pass backend specific options.