Compute Library
 23.08
gemm_interleaved.hpp
Go to the documentation of this file.
1 /*
2  * Copyright (c) 2017-2023 Arm Limited.
3  *
4  * SPDX-License-Identifier: MIT
5  *
6  * Permission is hereby granted, free of charge, to any person obtaining a copy
7  * of this software and associated documentation files (the "Software"), to
8  * deal in the Software without restriction, including without limitation the
9  * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
10  * sell copies of the Software, and to permit persons to whom the Software is
11  * furnished to do so, subject to the following conditions:
12  *
13  * The above copyright notice and this permission notice shall be included in all
14  * copies or substantial portions of the Software.
15  *
16  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22  * SOFTWARE.
23  */
24 #pragma once
25 
26 #include <algorithm>
27 #include <cassert>
28 
29 #include "arm_gemm.hpp"
30 #include "bfloat.hpp"
31 #include "convolver.hpp"
32 #include "kernel_weight_format.hpp"
33 #include "kernel_traits.hpp"
34 #include "kernel_weight_format.hpp"
35 #include "mergeresults.hpp"
37 #include "quantized.hpp"
38 #include "transform.hpp"
39 #include "utils.hpp"
40 
41 #ifdef CYCLE_PROFILING
42 #include "profiler.hpp"
43 #endif
44 
45 // Some macros used to decide how much working space to allocate.
46 // Round allocations up to the next cache line.
47 #define ALLOC_ROUND 64
48 #define ROUND_UP(x) ((((x) + ALLOC_ROUND-1) / ALLOC_ROUND) * ALLOC_ROUND)
49 
50 // Implementation of the GemmCommon abstract class.
51 //
52 // This implementation interleaves the source matrices in blocks - good for
53 // larger matrices.
54 
55 namespace arm_gemm {
56 
57 namespace {
58 
59 // Some kernels output to a linear buffer and require a separate merge step.
60 // Others output directly to the matrix result. This helper class calls the
61 // appropriate functions, using templating to avoid calling non-existent
62 // functions.
63 template<bool MergeStep, bool FixedFormat, typename OutputStage>
64 class kernel_and_merge {
65 public:
66  template<typename strategy, typename To, typename Tr, typename Tri, typename Tab>
67  static void run (
68 #ifdef CYCLE_PROFILING
69  profiler &prof,
70 #endif
71  strategy &strat, const To *a_ptr, const To *b_panel, size_t b_stride, Tri *c_panel,
72  Tr *c_ptr, int ldc, int kern_k, unsigned int m_0,
73  unsigned int m_max, unsigned int n_0, unsigned int n_max, const Tr *biasptr,
74  const Activation &act, bool accumulate, const OutputStage &os, const int32_t *col_bias,
75  Tab *acc_buff);
76 };
77 
78 // Run a kernel and call the separate merge step
79 template<>
80 template<typename strategy, typename To, typename Tr, typename Tri, typename Tab>
82 #ifdef CYCLE_PROFILING
83  profiler &prof,
84 #endif
85  strategy &strat, const To *a_ptr, const To *b_panel, size_t, Tri *c_panel,
86  Tr *c_ptr, int ldc, int kern_k, unsigned int m_0,
87  unsigned int m_max, unsigned int n_0, unsigned int n_max, const Tr *biasptr,
88  const Activation &act, bool accumulate, const Nothing &, const int32_t *, Tab *)
89 {
90  const int bblocks = iceildiv(n_max - n_0, strategy::out_width());
91 
92  {
93 #ifdef CYCLE_PROFILING
94  auto p=prof.ScopedProfiler(PROFILE_KERNEL, (strategy::out_height() * bblocks * strategy::out_width() * kern_k));
95 #endif
96 
97  strat.kernel(a_ptr, b_panel, c_panel, 1, bblocks, kern_k);
98  }
99 
100  {
101 #ifdef CYCLE_PROFILING
102  auto p=prof.ScopedProfiler(PROFILE_MERGE, (strategy::out_height() * bblocks * strategy::out_width() * sizeof(Tr)));
103 #endif
104  strat.transforms.Merge(c_ptr, c_panel, ldc, m_0, m_max, n_0, n_max, biasptr, act, accumulate);
105  }
106 }
107 
108 // Run a fixed-format kernel and call the separate merge step
109 template<>
110 template<typename strategy, typename To, typename Tr, typename Tri, typename Tab>
112 #ifdef CYCLE_PROFILING
113  profiler &prof,
114 #endif
115  strategy &strat, const To *a_ptr, const To *b_panel, size_t b_stride, Tri *c_panel,
116  Tr *c_ptr, int ldc, int kern_k, unsigned int m_0,
117  unsigned int m_max, unsigned int n_0, unsigned int n_max, const Tr *biasptr,
118  const Activation &act, bool accumulate, const Nothing &, const int32_t *, Tab *)
119 {
120  {
121 #ifdef CYCLE_PROFILING
122  const int bblocks = iceildiv(n_max - n_0, strategy::out_width());
123  auto p=prof.ScopedProfiler(PROFILE_KERNEL, (strategy::out_height() * bblocks * strategy::out_width() * kern_k));
124 #endif
125 
126  strat.kernel(a_ptr, b_panel, b_stride, c_panel, 1, (n_max - n_0), kern_k);
127  }
128 
129  {
130 #ifdef CYCLE_PROFILING
131  const int bblocks = iceildiv(n_max - n_0, strategy::out_width());
132  auto p=prof.ScopedProfiler(PROFILE_MERGE, (strategy::out_height() * bblocks * strategy::out_width() * sizeof(Tr)));
133 #endif
134  strat.transforms.Merge(c_ptr, c_panel, ldc, m_0, m_max, n_0, n_max, biasptr, act, accumulate);
135  }
136 }
137 
138 // Run a kernel with integrated merge
139 template<>
140 template<typename strategy, typename To, typename Tr, typename Tri, typename Tab>
142 #ifdef CYCLE_PROFILING
143  profiler &prof,
144 #endif
145  strategy &strat, const To *a_ptr, const To *b_panel, size_t, Tri *,
146  Tr *c_ptr, int ldc, int kern_k, unsigned int m_0, unsigned int m_max,
147  unsigned int n_0, unsigned int n_max, const Tr *biasptr,
148  const Activation &act, bool accumulate, const Nothing &, const int32_t *,
149  Tab *acc_buff)
150 {
151 #ifdef CYCLE_PROFILING
152  auto p=prof.ScopedProfiler(PROFILE_KERNEL, (m_max - m_0) * (n_max - n_0) * kern_k);
153 #endif
154 
155  // We need to offset the C pointer, but as it might be NULL (requesting output to accumulation buffer) we need
156  // to be careful not to offset a null pointer.
157  Tri *offset_c_ptr;
158 
159  if (c_ptr == nullptr) {
160  offset_c_ptr = nullptr;
161  } else {
162  offset_c_ptr = c_ptr + m_0 * ldc + n_0;
163  }
164 
165  strat.kernel(// A and B pointers are just the packed panels.
166  a_ptr, b_panel,
167  // Provide relevant part of output array and row stride.
168  offset_c_ptr, ldc,
169  // M, N, K sizes
170  m_max-m_0, n_max - n_0, kern_k,
171  // Bias, activation, accumulation. Need to offset the bias as needed.
172  biasptr ? biasptr + n_0 : nullptr, act, accumulate,
173  // Accumulation buffer.
174  acc_buff );
175 }
176 
177 // Run a kernel with integrated merge, quantizing
178 template<>
179 template<typename strategy, typename To, typename Tr, typename Tri, typename Tab>
181 #ifdef CYCLE_PROFILING
182  profiler &prof,
183 #endif
184  strategy &strat, const To *a_ptr, const To *b_panel, size_t, Tri *,
185  Tr *c_ptr, int ldc, int kern_k, unsigned int m_0, unsigned int m_max,
186  unsigned int n_0, unsigned int n_max, const Tr *,
187  const Activation &, bool accumulate, const Requantize32 &qp, const int32_t *col_bias,
188  Tab *acc_buff)
189 {
190 #ifdef CYCLE_PROFILING
191  auto p=prof.ScopedProfiler(PROFILE_KERNEL, (m_max - m_0) * (n_max - n_0) * kern_k);
192 #endif
193 
194  strat.kernel(// A and B pointers are just the packed panels.
195  a_ptr, b_panel,
196  // Provide relevant part of output array and row stride.
197  c_ptr + m_0 * ldc + n_0, ldc,
198  // M, N, K sizes
199  m_max-m_0, n_max - n_0, kern_k,
200  // Bias, activation, accumulation. Need to offset the bias as needed.
201  col_bias + n_0, qp, n_0, accumulate, acc_buff);
202 }
203 
204 // Run a kernel and call the separate quantize step
205 template<>
206 template<typename strategy, typename To, typename Tr, typename Tri, typename Tab>
208 #ifdef CYCLE_PROFILING
209  profiler &prof,
210 #endif
211  strategy &strat, const To *a_ptr, const To *b_panel, size_t, Tri *c_panel,
212  Tr *c_ptr, int ldc, int kern_k, unsigned int m_0,
213  unsigned int m_max, unsigned int n_0, unsigned int n_max, const Tr *,
214  const Activation &, bool, const Requantize32 &qp, const int32_t *col_bias,
215  Tab *)
216 {
217  const int bblocks = iceildiv(n_max - n_0, strategy::out_width());
218 
219  {
220 #ifdef CYCLE_PROFILING
221  auto p=prof.ScopedProfiler(PROFILE_KERNEL, (strategy::out_height() * bblocks * strategy::out_width() * kern_k));
222 #endif
223 
224  strat.kernel(a_ptr, b_panel, c_panel, 1, bblocks, kern_k);
225  }
226 
227  {
228 #ifdef CYCLE_PROFILING
229  auto p=prof.ScopedProfiler(PROFILE_QUANTIZE, ((m_max-m_0) * bblocks * strategy::out_width() * sizeof(Tr)));
230 #endif
231  // The interleaved kernel outputs in blocks - each block is a
232  // row-major matrix of size out_width * out_height. The merge
233  // kernels are designed to deal with this but the requantizer is
234  // not, so we need to requantize one block at a time.
235  for (int i=0; i<bblocks; i++) {
236  unsigned int n_start = n_0 + (strategy::out_width() * i);
237  unsigned int n_end = std::min(n_start + strategy::out_width(), n_max);
238 
239  // The row bias is interleaved with the transposed A data, get a pointer to it here.
240  const int32_t *row_bias = reinterpret_cast<const int32_t *>(a_ptr + strategy::out_height() * kern_k);
241 
242  requantize_block_32(qp, (n_end - n_start), (m_max-m_0),
243  c_panel + (i * strategy::out_width() * strategy::out_height()), strategy::out_width(),
244  c_ptr + m_0 * ldc + n_start, ldc,
245  row_bias, col_bias + n_start, n_start);
246  }
247  }
248 }
249 
250 // Integer GEMMs can be used in two contexts - "normal" where the full 32-bit output is required, or in
251 // "requantizing" context where the output will be requantized.
252 //
253 // These require different input transforms, as if we are requantizing we want to sum the rows of the A input, and
254 // if we are not we don't.
255 //
256 // This helper class allows the appropriate transforms to be found, without requiring kernels that don't support
257 // quantization to define useless "quantized" transforms.
258 template<typename strategy, bool quantized>
259 class transform_type {
260 public:
261  typedef decltype(strategy::transforms) type;
262 };
263 
264 template<typename strategy>
265 class transform_type<strategy, true> {
266 public:
267  typedef decltype(strategy::transforms_quantized) type;
268 };
269 
270 // We need a similar trick here to figure out what type the accumulator buffer should be.
271 template<typename strategy, typename OutputStage, bool ForceFloat>
272 class accumulate_buffer_type {
273 public:
274  typedef typename strategy::result_type type;
275 };
276 
277 template<typename strategy>
278 class accumulate_buffer_type<strategy, Requantize32, false> {
279 public:
280  typedef int32_t type;
281 };
282 
283 template<typename strategy, typename OutputStage>
284 class accumulate_buffer_type<strategy, OutputStage, true> {
285 public:
286  typedef float type;
287 };
288 
289 // Stripe width is a concept only needed for FixedFormat kernels. Use an accessor to avoid issues in other scenarios.
290 template<typename strategy, bool FixedFormat>
291 struct get_stripe_width {
292  static unsigned int get() {
293  return 0;
294  }
295 };
296 
297 template<typename strategy>
298 struct get_stripe_width<strategy, true> {
299  static unsigned int get() {
300  return strategy::stripe_width();
301  }
302 };
303 
304 // KernelWeightFormat is a similar story.
305 template<typename strategy, bool FixedFormat, typename To>
306 struct get_kernel_weight_format {
307  static KernelWeightFormat get() {
309  }
310 };
311 
312 template<typename strategy, typename To>
313 struct get_kernel_weight_format<strategy, true, To> {
314  static KernelWeightFormat get() {
315  KernelWeightFormat kwf = strategy::kernel_weight_format();
316 
317  // If we are using a BF16 kernel to do an FP32 problem (fast mode) then we need to set the BF16 flag on the
318  // weight format.
319  if (std::is_same<To, float>::value && std::is_same<typename strategy::operand_type, bfloat16>::value) {
320  uint32_t kwf_i = static_cast<uint32_t>(kwf);
321  kwf_i |= 0x10;
322  kwf = static_cast<KernelWeightFormat>(kwf_i);
323  }
324 
325  return kwf;
326  }
327 };
328 
329 } // anonymous namespace
330 
331 template<typename strategy, typename To, typename Tr, typename OutputStage=Nothing, bool MergeStep=true, bool FixedFormat=false, bool ForceThreadColumns=false, bool ForceFloatAccumulate=false>
332 class GemmInterleaved : public GemmCommon<To, Tr> {
333  typedef typename strategy::operand_type Toi;
334  typedef typename strategy::result_type Tri;
335  typedef typename accumulate_buffer_type<strategy, OutputStage, ForceFloatAccumulate>::type Tab;
336 
337  /* const properties set by constructor */
338  const CPUInfo * const _ci;
339 
340  const unsigned int _Msize;
341  const unsigned int _Nsize;
342  const unsigned int _Ksize;
343  const unsigned int _Ksections;
344  const unsigned int _Ktotal;
345  const unsigned int _rounded_Ksize;
346 
347  const unsigned int _nbatches;
348  const unsigned int _nmulti;
349 
350  const bool _thread_columns;
351 
352  const Activation _act;
353 
354  const int _maxthreads;
355  int _nthreads;
356 
357  /* Blocking info */
358  unsigned int _k_block=0;
359  unsigned int _x_block=0;
360  unsigned int _Mround=0;
361 
362  /* Working space, pretransposed buffer, buffer manager */
363  const Toi *_B_transposed=nullptr;
364  void *_working_space=nullptr;
365 
366  Tab *_accumulation_buffer=nullptr;
367 
368  /* Output stage */
369  OutputStage _os;
370 
371  /* Quantized support (in addition to 'output stage' above */
372  int32_t *col_bias = nullptr;
373 
374  /* Indirect parameters. _indirect_buf doubles as a flag to indicate that "indirect" transform should be used. */
375  const To * const * const * _indirect_buf = nullptr;
376 
377  /* Convolver - only set up for convolution problems, so also doubles as a flag. */
378  std::unique_ptr<convolver<To>> _convolver = nullptr;
379 
380  unsigned int get_col_sum_size() const {
381  if (std::is_same<OutputStage, Requantize32>::value) {
382  return _Nsize * _nmulti * sizeof(int32_t);
383  } else {
384  return 0;
385  }
386  }
387 
388  /* We will need to walk through the blocks of B in a few contexts, so
389  * factor that out. */
390  class blockwalker {
391  private:
392  /* Size loops, etc. based on our parent's configuration */
394 
395  /* K, X and multi parameters for current iteration. */
396  unsigned int _k0=0, _x0=0, _multi=0;
397 
398  /* Range of X to iterate over - used in "ForceThreadColumns" cases */
399  unsigned int _x_start=0;
400  unsigned int _x_end=_parent._Nsize;
401 
402  unsigned int _index=0;
403  bool _done=false;
404  bool _newkblock=true;
405  bool _newmulti=true;
406 
407  public:
409 
411  unsigned int x_start, unsigned int x_end) : _parent(parent), _x0 (_x_start), _x_start(x_start), _x_end(x_end) { }
412 
413  unsigned int xmax() {
414  return std::min(_x0 + _parent._x_block, _x_end);
415  }
416 
417  unsigned int kmax() {
418  return std::min(_k0 + _parent._k_block, _parent._Ktotal);
419  }
420 
421  /* Advance to the next block, return false at the end. */
422  bool advance(void) {
423  if (_done) {
424  return false;
425  }
426 
427  _newkblock=false;
428  _x0 += _parent._x_block;
429  if (_x0 >= _x_end) {
430  _x0=_x_start;
431  _k0 += _parent._k_block;
432  if (_k0 >= _parent._Ktotal) {
433  _k0=0;
434  _multi++;
435  if (_multi >= _parent._nmulti) {
436  _done=true;
437  return false;
438  }
439  _newmulti=true;
440  }
441  _newkblock=true;
442  }
443  _index++;
444 
445  return true;
446  }
447 
448  unsigned int k0(void) { return _k0; }
449  unsigned int x0(void) { return _x0; }
450  unsigned int multi(void) { return _multi; }
451  unsigned int index(void) { return _index; }
452  bool done(void) { return _done; }
453  bool newkblock(void) { return _newkblock; }
454  };
455 
456  // "k block" has two distinct uses: figuring out which iterations of K
457  // to actually process, but also various size/pointer computations. The
458  // latter needs to take account of the extra space needed for the row
459  // sums, if appropriate.
460  unsigned int get_total_k_depth() const {
461  unsigned int k_depth = _k_block;
462 
463  if (std::is_same<OutputStage, Requantize32>::value) {
464  k_depth += sizeof(int32_t) / sizeof(Toi);
465  }
466 
467  return k_depth;
468  }
469 
470  // A working size.
471  size_t get_a_working_size() const {
472  if (_thread_columns) {
473  // For 2D threading: allocate a buffer of one block of rows per thread
474  return ROUND_UP(sizeof(Toi) * get_total_k_depth() * strategy::out_height() * _maxthreads);
475  } else {
476  // For 1D threaded: one of these needed, regardless of thread count. Divided according to window.
477  return ROUND_UP(sizeof(Toi) * get_total_k_depth() * _Mround * _nbatches);
478  }
479  }
480 
481  // C working size: One needed per thread. Not needed if there is no merge step.
482  size_t get_c_working_size() const {
483  if (MergeStep) {
484  return ROUND_UP(sizeof(Tri) * _x_block * strategy::out_height());
485  } else {
486  return 0;
487  }
488  }
489 
490  // Accumulation buffer size
491  size_t get_accumulation_buffer_size() const {
492  // We only support an accumulation buffer for non-merge cases.
493  if (MergeStep) {
494  return 0;
495  }
496 
497  // Check if we are actually blocking
498  if (_k_block == _Ktotal) {
499  return 0;
500  }
501 
502  // We are no-merge, non-quantized with active blocking: accumulation buffer needed.
503  size_t size_per_buffer = sizeof(Tab) * strategy::out_height() * strategy::out_width();
504  size_t num_buffers = iceildiv(_Msize, strategy::out_height()) * iceildiv(_Nsize, strategy::out_width()) * _nbatches * _nmulti;
505 
506  return num_buffers * size_per_buffer;
507  }
508 
509  // Get pointer into accumulation buffer
510  Tab *get_accumulation_buffer(unsigned int M, unsigned int N, unsigned int batch, unsigned int multi) const {
511  // Don't do anything if there's no buffer.
512  if (_accumulation_buffer == nullptr) {
513  return nullptr;
514  }
515 
516  // Here we are indexing an appropriately sized pointer, so no sizeof() needed to convert to bytes.
517  size_t size_per_buffer = strategy::out_height() * strategy::out_width();
518 
519  size_t buffer_rows = iceildiv(_Msize, strategy::out_height());
520  size_t buffer_cols = iceildiv(_Nsize, strategy::out_width());
521  size_t buffers_per_batch = (buffer_rows * buffer_cols);
522  size_t buffers_per_multi = buffers_per_batch * _nbatches;
523 
524  // M/N must reference the top-left corner of a block.
525  size_t row = M / strategy::out_height();
526  assert(M % strategy::out_height() == 0);
527  size_t col = N / strategy::out_width();
528  assert(N % strategy::out_width() == 0);
529 
530  size_t buffer_index = multi * buffers_per_multi + batch * buffers_per_batch + row * buffer_cols + col;
531 
532  return _accumulation_buffer + (buffer_index * size_per_buffer);
533  }
534 
535  int32_t row_sum_multiplier() const {
536  if (std::is_same<OutputStage, Requantize32>::value) {
537  const Requantize32 *qp = reinterpret_cast<const Requantize32 *>(&_os);
538 
539  return -qp->b_offset;
540  }
541 
542  return 0;
543  }
544 
545  // Heuristics to decide whether to use the 'thread columns' regime
546  static bool is_thread_columns(const GemmArgs &args) {
547  // For now, there is a templace parameter to force it.
548  if (ForceThreadColumns) {
549  return true;
550  }
551 
552  // Never do this for single threaded cases.
553  if (args._maxthreads == 1) {
554  return false;
555  }
556 
557  // How many blocks of work are available for threading on M?
558  int m_blocks = iceildiv(args._Msize, strategy::out_height()) * args._nbatches;
559 
560  // If we just can't share the work across threads with the row threading regime.
561  if (args._maxthreads > m_blocks) {
562  return true;
563  }
564 
565  // If the row threading regime is too wasteful (20% threshold)
566  if (((roundup(m_blocks, args._maxthreads) * 100) / m_blocks) > 120) {
567  return true;
568  }
569 
570  return false;
571  }
572 
573  static unsigned int get_ktotal(const GemmArgs &args) {
574  return args._Ksections * roundup(args._Ksize, strategy::k_unroll());
575  }
576 
577  static unsigned int get_k_block_size(const GemmArgs &args) {
578  if (args._cfg && args._cfg->inner_block_size) {
579  return roundup(args._cfg->inner_block_size, strategy::k_unroll());
580  }
581 
582  // K blocking not supported if we are requantizing.
583  if (std::is_same<OutputStage, Requantize32>::value) {
584  return get_ktotal(args);
585  }
586 
587  // Special blocking for SME
589  // Don't bother to block below this size threshold, experimentally determined to be 320 for FP32
590  unsigned int scaling_threshold = 1280 / sizeof(Toi);
591 
592  if (get_ktotal(args) <= scaling_threshold) {
593  return get_ktotal(args);
594  }
595 
596  // Once we are blocking, this (lower) threshold determines when we should use more blocks
597  // NOTE: Could be that some factor-based solution would work better here.
598  unsigned int max_block_size = 1024 / sizeof(Toi);
599 
600  unsigned int num_k_blocks = iceildiv(get_ktotal(args), max_block_size);
601 
602  unsigned int k_block = roundup(iceildiv(get_ktotal(args), num_k_blocks), strategy::k_unroll());
603 
604  return k_block;
605  }
606 
607  const unsigned int L1_size = args._ci->get_L1_cache_size();
608  unsigned int k_block;
609 
610  // k_block: Find out how much of the larger array can be loaded into half the cache.
611  // This should account for associative caches.
612  k_block = (L1_size / 2) / (sizeof(Toi) * (std::max(strategy::out_width(), strategy::out_height())));
613 
614  // Needs to be (at least a single) multiple of the K unroll level.
615  k_block /= strategy::k_unroll();
616  k_block = std::max(k_block, 1U) * strategy::k_unroll();
617 
618  // Now tune to presented problem size; this is how many blocks we need.
619  unsigned int num_k_blocks = iceildiv(get_ktotal(args), k_block);
620 
621  // So divide the space equally into that many blocks.
622  k_block = iceildiv(get_ktotal(args), num_k_blocks);
623 
624  // And round UP to the K unroll level required.
625  k_block = roundup(k_block, strategy::k_unroll());
626 
627  assert(k_block > 0);
628 
629  return k_block;
630  }
631 
632  static unsigned int get_x_block_size(const GemmArgs &args) {
633  if (is_thread_columns(args)) {
634  // In 2D mode, override X block, because we will process width first.
635  return roundup(args._Nsize, strategy::out_width());
636  }
637 
638  if (args._cfg && args._cfg->outer_block_size) {
639  return roundup(args._cfg->outer_block_size, strategy::out_width());
640  }
641 
642  unsigned int x_block;
643  const unsigned int L2_size = args._ci->get_L2_cache_size();
644  const unsigned int k_block = get_k_block_size(args);
645 
646  // x_block: Work out how many rows (of length k_block) will fit in the L2
647  // Don't allocate more than 90% of the L2 to allow for overheads, and subtract off the L1 contents.
648  const unsigned int scaled_l2_size = (L2_size * 9) / 10;
649  const unsigned int k_block_area = k_block * sizeof(Toi) * (strategy::out_width() + strategy::out_height());
650 
651  // .. if the L1 contents is bigger than the L2, just return a minimal size block.
652  if (k_block_area > scaled_l2_size) {
653  return strategy::out_width();
654  }
655 
656  x_block = (scaled_l2_size - k_block_area) / (sizeof(Toi) * k_block);
657 
658  // Needs to be (at least a single) multiple of the kernel output width.
659  x_block /= strategy::out_width();
660  x_block = std::max(x_block, 1u) * strategy::out_width();
661 
662  // And tune to the presented problem size.
663  unsigned int num_x_blocks = iceildiv(args._Nsize, x_block);
664  x_block = iceildiv(args._Nsize, num_x_blocks);
665 
666  x_block = roundup(x_block, strategy::out_width());
667 
668  assert(x_block > 0);
669 
670  return x_block;
671  }
672 
673 public:
674  GemmInterleaved(GemmInterleaved &) = delete;
676 
677  /* Constructor */
678  GemmInterleaved(const GemmArgs &args, const OutputStage &os)
679  : _ci(args._ci), _Msize(args._Msize), _Nsize(args._Nsize), _Ksize(args._Ksize),
680  _Ksections(args._Ksections), _Ktotal(get_ktotal(args)),
681  _rounded_Ksize(roundup(_Ksize, strategy::k_unroll())),
682  _nbatches(args._nbatches), _nmulti(args._nmulti), _thread_columns(is_thread_columns(args)),
683  _act(args._act), _maxthreads(args._maxthreads), _nthreads(args._maxthreads),
684  _k_block(get_k_block_size(args)), _x_block(get_x_block_size(args)), _Mround(roundup(args._Msize, strategy::out_height())),
685  _os(os) { }
686 
687  /* Constructor without OutputStage */
689  : _ci(args._ci), _Msize(args._Msize), _Nsize(args._Nsize), _Ksize(args._Ksize),
690  _Ksections(args._Ksections), _Ktotal(get_ktotal(args)),
691  _rounded_Ksize(roundup(_Ksize, strategy::k_unroll())),
692  _nbatches(args._nbatches), _nmulti(args._nmulti), _thread_columns(is_thread_columns(args)),
693  _act(args._act), _maxthreads(args._maxthreads), _nthreads(args._maxthreads),
694  _k_block(get_k_block_size(args)), _x_block(get_x_block_size(args)), _Mround(roundup(args._Msize, strategy::out_height())),
695  _os() { }
696 
697  // Interface implementation - Compulsory functions
698 
699  // Window size: Only the last thread should do a ragged block, so dole
700  // out work in units of out_height. Factor batches into the window, but
701  // not multi for now (as this would cause problems with the buffer
702  // manager).
703  ndrange_t get_window_size() const override {
704  unsigned int row_blocks = (_Mround / strategy::out_height()) * _nbatches;
705 
706  if (_thread_columns) {
707  return { row_blocks, iceildiv(_Nsize, strategy::out_width()) };
708  } else {
709  // _Mround is a multiple of out_height by definition.
710  return { row_blocks };
711  }
712  }
713 
714  // set_nthreads: pass on to buffer manager to avoid it waiting for non-existant threads.
715  void set_nthreads(int nthreads) override {
716  _nthreads = std::min(nthreads, _maxthreads);
717  }
718 
719  // Execute
720  void execute(const ndcoord_t &work_range, const ndcoord_t &, int threadid) override {
721 #ifdef CYCLE_PROFILING
722  profiler prof;
723 #endif
724 
725  /* Make sure we've been set up correctly. */
726  assert(FixedFormat || _B_transposed);
727  assert(_working_space);
728  int8_t *working_space_bytes = reinterpret_cast<int8_t *>(_working_space);
729 
730  /* Align if needed */
731  intptr_t working_space_v = reinterpret_cast<intptr_t>(_working_space);
732  if (working_space_v & 0x3f) {
733  intptr_t alignment_offset = 0x40 - (working_space_v & 0x3f);
734  working_space_bytes += alignment_offset;
735  }
736 
737  strategy strat(_ci);
738 
739  const auto start = work_range.get_position(0);
740  const auto end = work_range.get_position_end(0);
741 
742  /* Translate 'start' and 'end' into a position within the batches and rows. */
743  const unsigned int window_per_batch = _Mround / strategy::out_height();
744  unsigned int batch_0 = start / window_per_batch;
745  unsigned int batch_end = end / window_per_batch;
746 
747  // In ThreadColumns mode, process work one horizontal strip at a time.
748  // Transpose the block of needed rows at the start, then do all the work on that block.
749  if (_thread_columns) {
750  const auto start_x = work_range.get_position(1) * strategy::out_width();
751  const auto end_x = std::min(work_range.get_position_end(1) * strategy::out_width(), _Nsize);
752 
753  Tri * const c_panel = reinterpret_cast<Tri *>(working_space_bytes + (threadid * get_c_working_size()));
754  Toi * const a_panel = reinterpret_cast<Toi *>(working_space_bytes + (_maxthreads * get_c_working_size()) +
755  (threadid * sizeof(Toi) * get_total_k_depth() * strategy::out_height()));
756 
757  for (unsigned int multi=0; multi<_nmulti; multi++) {
758  for (unsigned int k0=0; k0<_Ktotal; k0+=_k_block) {
759  unsigned int kmax=std::min(k0+_k_block, _Ktotal);
760 
761  unsigned int rounded_width = roundup(_Nsize, strategy::out_width());
762 
763  const bool first_pass = (k0==0);
764  const bool last_pass = (kmax==_Ktotal);
765 
766  // Figure out how many "K" the kernel will actually process.
767  unsigned int kern_k = roundup(kmax - k0, strategy::k_unroll());
768 
769  const Toi *b_ptr = FixedFormat ?
770  reinterpret_cast<const Toi *>(this->_Bptr) + (multi * this->_B_multi_stride) +
771  ((start_x / get_stripe_width<strategy, FixedFormat>::get()) * this->_ldb) +
772  (k0 * get_stripe_width<strategy, FixedFormat>::get()) :
773  _B_transposed + (rounded_width * _Ktotal * multi) + (k0 * rounded_width) + (start_x * kern_k);
774 
775  unsigned int batch = batch_0;
776  unsigned int start_row = (start - (batch_0 * window_per_batch)) * strategy::out_height();
777 
778  for (unsigned int p=start; p<end; p++) {
779  unsigned int end_row = std::min(start_row + strategy::out_height(), _Msize);
780 
781  // Set up transposed 'A' block
782  {
783 #ifdef CYCLE_PROFILING
784  auto p=prof.ScopedProfiler(PROFILE_PREPA, strategy::out_height() * (kmax-k0) * sizeof(Toi));
785 #endif
786  // See comment above on transform_type<> class: this extracts either 'transforms' or
787  // 'transforms_quantized' as appropriate.
788  typename transform_type<strategy, MergeStep && std::is_same<OutputStage, Requantize32>::value>::type transforms;
789 
790  if (_indirect_buf != nullptr) {
791  transforms.PrepareA_indirect(a_panel,
792  _indirect_buf + (multi * _nbatches * _Ksections) + (batch * _Ksections), _Ksize,
793  _rounded_Ksize, start_row, end_row, k0, kmax, row_sum_multiplier());
794  } else if (_convolver) {
795  transforms.PrepareA_convolution(a_panel,
796  this->_Aptr + (batch * this->_A_batch_stride) + (multi * this->_A_multi_stride),
797  this->_lda, *_convolver, _rounded_Ksize, start_row, end_row, k0, kmax, row_sum_multiplier());
798  } else {
799  transforms.PrepareA(a_panel,
800  this->_Aptr + (batch * this->_A_batch_stride) + (multi * this->_A_multi_stride),
801  this->_lda, start_row, end_row, k0, std::min(kmax, _Ksize), row_sum_multiplier());
802  }
803  }
804 
805  // Perform the kernel and merge step, either separately or together as required.
807  #ifdef CYCLE_PROFILING
808  prof,
809  #endif
810  // Strategy and panel pointers
811  strat, a_panel, b_ptr, this->_ldb, c_panel,
812  // Result buffer pointers
813  this->_Cptr + (batch * this->_C_batch_stride) + (multi * this->_C_multi_stride), this->_ldc,
814  // K size, and M/N ranges
815  kern_k, start_row, end_row, start_x, end_x,
816  // Only do bias on the first pass
817  ((first_pass && this->_bias) ? this->_bias + (multi * this->_bias_multi_stride) : nullptr),
818  // Only do activation on the last pass, and accumulation on any non-first pass.
819  (last_pass ? _act : Activation()), !first_pass,
820  // Pass in quantization parameters for requantizing kernels (others will ignore)
821  _os, col_bias + (multi * _Nsize),
822  // Accumulation buffer
823  get_accumulation_buffer(start_row, start_x, batch, multi));
824 
825  /* Increment to the next block */
826  start_row += strategy::out_height();
827  if (start_row >= _Msize) {
828  start_row = 0;
829  batch++;
830  }
831  }
832  }
833  }
834  } else {
835  blockwalker current(*this);
836 
837  /* Compute the M values to operate on */
838  unsigned int m_0 = (start - (batch_0 * window_per_batch)) * strategy::out_height();
839  unsigned int m_max = (end - (batch_end * window_per_batch)) * strategy::out_height();
840 
841  // Private buffers. Treat working_space as an array of C buffers
842  // (one per thread) first, followed by the (window-divided) A
843  // buffer.
844  // Set a_panel to the base of the A buffers - compute offsets into it based on M/batches later.
845  Toi * const a_panel = reinterpret_cast<Toi *>(working_space_bytes + (_maxthreads * get_c_working_size()));
846  Tri * const c_panel = reinterpret_cast<Tri *>(working_space_bytes + (threadid * get_c_working_size()));
847 
848  const Toi *b_panel;
849  b_panel = _B_transposed;
850 
851  // newkblock() is always true on the first iteration, so these will be set properly on the first loop.
852 
853  // kern_k tracks the accumulation depth for the CURRENT K block a_panel_stride similarly tracks the total
854  // stride of the A panel (i.e. with 4 added for cases with embedded row sums)
855 
856  // These are distinct from k_block and get_total_k_depth() which are based on the target K block size, and
857  // used for addressing inside a_panel.
858 
859  // In cases where K blocking is in use and the blocks are not all the same size, the (smaller) final block
860  // won't use all the memory allocated.
861  unsigned int kern_k = 0;
862  unsigned int a_panel_stride = 0;
863 
864  for (;!current.done();current.advance()) {
865  if (current.newkblock()) {
866 #ifdef CYCLE_PROFILING
867  auto p=prof.ScopedProfiler(PROFILE_PREPA, (end - start) * strategy::out_height() * (current.kmax()-current.k0()) * sizeof(Toi));
868 #endif
869  // See comment above on transform_type<> class: this extracts either 'transforms' or
870  // 'transforms_quantized' as appropriate.
871  typename transform_type<strategy, MergeStep && std::is_same<OutputStage, Requantize32>::value>::type transforms;
872 
873  for (unsigned int batch = batch_0; batch <= batch_end; batch++) {
874  unsigned int first_m = (batch == batch_0) ? m_0 : 0;
875  unsigned int last_m = (batch == batch_end) ? m_max : _Msize;
876 
877  if (first_m >= last_m)
878  continue;
879 
880  if (_indirect_buf != nullptr) {
881  transforms.PrepareA_indirect(a_panel + ((batch * _Mround + first_m) * get_total_k_depth()),
882  _indirect_buf + (current.multi() * _nbatches * _Ksections) + (batch * _Ksections), _Ksize,
883  _rounded_Ksize, first_m, last_m, current.k0(), current.kmax(), row_sum_multiplier());
884  } else if (_convolver) {
885  transforms.PrepareA_convolution(a_panel + ((batch * _Mround + first_m) * get_total_k_depth()),
886  this->_Aptr + (batch * this->_A_batch_stride) + (current.multi() * this->_A_multi_stride),
887  this->_lda, *_convolver, _rounded_Ksize, first_m, last_m, current.k0(), current.kmax(), row_sum_multiplier());
888  } else {
889  transforms.PrepareA(a_panel + ((batch * _Mround + first_m) * get_total_k_depth()),
890  this->_Aptr + (batch * this->_A_batch_stride) + (current.multi() * this->_A_multi_stride),
891  this->_lda, first_m, last_m, current.k0(), std::min(_Ksize, current.kmax()), row_sum_multiplier());
892  }
893  }
894 
895  // Figure out how many "K" the kernel will actually process.
896  kern_k = roundup(current.kmax() - current.k0(), strategy::k_unroll());
897 
898  // Requantizing GEMMs have the row sums built in to the
899  // transposed data, so the stride between rows is 4 bytes
900  // larger than the (rounded) K value.
901 
902  if(std::is_same<OutputStage, Requantize32>::value) {
903  a_panel_stride = kern_k + (sizeof(int32_t) / sizeof(Toi));
904  } else {
905  a_panel_stride = kern_k;
906  }
907  }
908 
909  // For FixedFormat cases, figure out the B pointer. The loop below moves through batches and vertically through the output so this will be the same throughout.
910  if (FixedFormat) {
911  b_panel = reinterpret_cast<const Toi *>(this->_Bptr) + (current.multi() * this->_B_multi_stride) +
912  ((current.x0() / get_stripe_width<strategy, FixedFormat>::get()) * this->_ldb) +
913  (current.k0() * get_stripe_width<strategy, FixedFormat>::get());
914  }
915 
916  /* Do the actual work. */
917  for (unsigned int batch = batch_0; batch <= batch_end; batch++) {
918  unsigned int first_m = (batch == batch_0) ? m_0 : 0;
919  unsigned int last_m = (batch == batch_end) ? m_max : _Msize;
920 
921  const Toi *a_ptr = a_panel + (batch * _Mround + first_m) * get_total_k_depth();
922 
923  if (first_m >= last_m)
924  continue;
925 
926  // For the merge case we need to do this out_height() rows
927  // at a time, as that is the size of our intermediate
928  // buffer. If we are not doing that, we can do all the
929  // relevant rows in one go.
930  unsigned int m_step = MergeStep ? strategy::out_height() : (last_m - first_m);
931 
932  // But in the case where we have an accumulation buffer, we can't do that after all, unless
933  // there is no N blocking.
934  if (_accumulation_buffer && ((current.x0() != 0) || (current.xmax() < _Nsize))) {
935  m_step = strategy::out_height();
936  }
937 
938  for (unsigned int y=first_m; y<last_m; y+=m_step) {
939  unsigned int ymax = std::min(_Msize, y + m_step);
940 
941  const bool first_pass = (current.k0() == 0);
942  const bool last_pass = (current.kmax() == _Ktotal);
943 
944  // Pointer to appropriate part of result array.
945  Tr *result_ptr = this->_Cptr + (batch * this->_C_batch_stride) + (current.multi() * this->_C_multi_stride);
946 
947  // If we are using an accumulation buffer, we don't pass the result buffer to ask the kernel
948  // to write things into the accumulation buffer instead, except on the last pass.
949  if (_accumulation_buffer && !last_pass) {
950  result_ptr = nullptr;
951  }
952 
953  // Perform the kernel and merge step, either separately or together as required.
955  #ifdef CYCLE_PROFILING
956  prof,
957  #endif
958  // Strategy and panel pointers
959  strat, a_ptr, b_panel, this->_ldb, c_panel,
960  // Result buffer pointers
961  result_ptr, this->_ldc,
962  // K size, and M/N ranges
963  kern_k, y, ymax, current.x0(), current.xmax(),
964  // Only do bias on the first pass
965  ((first_pass && this->_bias) ? this->_bias + (current.multi() * this->_bias_multi_stride) : nullptr),
966  // Only do activation on the last pass, and accumulation on any non-first pass.
967  (last_pass ? _act : Activation()), !first_pass,
968  // Pass in quantization parameters for requantizing kernels (others will ignore)
969  _os, col_bias + (current.multi() * _Nsize),
970  // Accumulation buffer
971  get_accumulation_buffer(y, current.x0(), batch, current.multi()) );
972 
973  a_ptr += (strategy::out_height() * a_panel_stride);
974  }
975  }
976 
977  if (FixedFormat == false) {
978  b_panel += (roundup(current.xmax() - current.x0(), strategy::out_width()) * kern_k);
979  }
980  }
981  }
982  }
983 
984  // Interface implementation - working space
985  size_t get_working_size() const override {
986  // In all cases, we need one A buffer plus a C buffer per thread, plus an accumulation buffer.
987  size_t size = get_a_working_size() + (get_c_working_size() * _maxthreads) + get_accumulation_buffer_size();
988 
989  size += 128; // Add on two cache lines extra for alignment.
990 
991  return size;
992  }
993 
994  void set_working_space(void *working_space) override {
995  // Make sure everything ends up cache line aligned
996  int8_t *working_space_bytes = reinterpret_cast<int8_t *>(working_space);
997  intptr_t working_space_int = reinterpret_cast<intptr_t>(working_space);
998 
999  size_t diff=0;
1000 
1001  if (working_space_int & 0x3F) {
1002  diff = 0x40 - (working_space_int & 0x3F);
1003  }
1004 
1005  working_space_bytes += diff;
1006  working_space_int += diff;
1007 
1008  // Pretransposed case: just set internal pointer to parameter value.
1009  _working_space = reinterpret_cast<void *>(working_space_bytes);
1010 
1011  // Set up accumulation buffer
1012  if (get_accumulation_buffer_size() > 0) {
1013  intptr_t acc_buff_int = working_space_int + get_a_working_size() + (get_c_working_size() * _maxthreads);
1014  // Make sure the accumulation buffer is aligned (needed if the other blocks are not a multiple of cache line length)
1015  if (acc_buff_int & 0x3F) {
1016  acc_buff_int += (0x40 - (acc_buff_int & 0x3F));
1017  }
1018  _accumulation_buffer = reinterpret_cast<Tab *>(acc_buff_int);
1019  } else {
1020  _accumulation_buffer = nullptr;
1021  }
1022  }
1023 
1024  // Interface implementation - pretransposed
1025  bool B_is_pretransposed() const override {
1026  return (FixedFormat == false);
1027  }
1028 
1029  bool B_pretranspose_required() const override {
1030  return (FixedFormat == false) && (_B_transposed==nullptr);
1031  }
1032 
1033  size_t get_B_pretransposed_array_size() const override {
1034  if (FixedFormat) {
1035  return 0;
1036  }
1037 
1038  unsigned int x_size = roundup(_Nsize, strategy::out_width());
1039 
1040  return (x_size * _Ktotal * _nmulti * sizeof(Toi)) + get_col_sum_size();
1041  }
1042 
1043  size_t get_B_pretranspose_window_size() const override {
1044  size_t n_blocks = iceildiv(_Nsize, _x_block);
1045  size_t k_blocks = iceildiv(_Ktotal, _k_block);
1046 
1047  return n_blocks * k_blocks * _nmulti;
1048  }
1049 
1050  void requantize_bias(void *in_buffer, const To *B, const int ldb, const int B_multi_stride) override {
1051  if (std::is_same<OutputStage, Requantize32>::value) {
1052  col_bias = reinterpret_cast<int32_t *>(in_buffer);
1053 
1054  Requantize32 *qp_ptr = reinterpret_cast<Requantize32 *>(&_os);
1055 
1056  for (unsigned int i=0; i<_nmulti; i++) {
1057  // The input is assumed not to have any padding between sections, so straightforward Ksize * Ksections computation gets the total size.
1058  compute_col_sums(*qp_ptr, _Nsize, _Ksize * _Ksections, B + (i * B_multi_stride), ldb, col_bias + (i * _Nsize), _Ksize * _Ksections, i, 0);
1059  }
1060  }
1061  }
1062 
1063  void pretranspose_B_array(void *in_buffer, const To *B, const int ldb, const int B_multi_stride) override {
1064  pretranspose_B_array_part(in_buffer, B, ldb, B_multi_stride, 0, get_B_pretranspose_window_size());
1065  }
1066 
1067  void pretranspose_B_array_part(void *in_buffer, const To *B, const int ldb, const int B_multi_stride, size_t start, size_t end) override {
1068  // Perform column sums etc as part of the last block.
1070  requantize_bias(in_buffer, B, ldb, B_multi_stride);
1071  }
1072 
1073  // Put the transposed data after the column sums - in non-quantized cases get_col_sum_size() == 0
1074  uintptr_t buffer_int = reinterpret_cast<uintptr_t>(in_buffer);
1075  Toi *buffer = reinterpret_cast<Toi *>(buffer_int + get_col_sum_size());
1076  _B_transposed = buffer;
1077 
1078  blockwalker current(*this);
1079  strategy strat(_ci);
1080 
1081  // Skip over blocks we aren't doing
1082  for(size_t i = 0; i < start; i++) {
1083  buffer += roundup(current.xmax() - current.x0(), strategy::out_width()) * roundup(current.kmax() - current.k0(), strategy::k_unroll());
1084  current.advance();
1085  }
1086 
1087  size_t blocks_left = (end - start);
1088 
1089  // Double check that we haven't run out of work
1090  if (current.done()) {
1091  blocks_left = 0;
1092  }
1093 
1094  for (/* blocks_left initialized above */; blocks_left > 0; blocks_left--) {
1095  /* Figure out the size of each block. */
1096  unsigned int k_size = (current.kmax() - current.k0());
1097 
1098  if (_Ksections > 1) {
1099  // We need to insert padding at the end of each K section.
1100  // The computation needed is a little delicate - the coordinates from the block walker are expressed in
1101  // terms of the full, padded, _Ktotal.
1102  // But we need to transform each section with reference to the original, unpadded, input, letting the
1103  // transform pad each section as needed.
1104 
1105  // This is needed for computations below.
1106  const unsigned int rounded_section_size = roundup(_Ksize, strategy::k_unroll());
1107 
1108  // The expected output format is also an entire <out_width> columns interleaved, then the next set of
1109  // columns, and so on. This means, as we are breaking it up vertically, we have to do it one column at
1110  // a time.
1111  for (unsigned int x0=current.x0(); x0 < current.xmax(); x0 += strategy::out_width() ) {
1112  unsigned int xmax = std::min(x0 + strategy::out_width(), current.xmax());
1113 
1114  // Track where we are and how much work is left.
1115  unsigned int kpos = current.k0();
1116  unsigned int kleft = k_size;
1117 
1118  while (kleft) {
1119  // Which section are we in? Based on the rounded-up section size.
1120  unsigned int k_section_base = kpos / rounded_section_size;
1121  // How far into the section are we?
1122  unsigned int k_offset = kpos - (k_section_base * rounded_section_size);
1123 
1124  // We will either copy the rest of this section, or to the end of the requested length.
1125  unsigned int k_length = std::min(_Ksize - k_offset, kleft);
1126 
1127  strat.transforms.PrepareB(buffer, B + (current.multi() * B_multi_stride), ldb,
1128  x0, xmax,
1129  (k_section_base * _Ksize) + k_offset, // K starting point - compute row to read based on our section and the true section length.
1130  (k_section_base * _Ksize) + k_offset + k_length); // K end point - starting point plus length computed above.
1131 
1132  // We need to modify our position based on the ROUNDED version of what we just did.
1133  unsigned int padded_length = roundup(k_length, strategy::k_unroll());
1134 
1135  buffer += strategy::out_width() * padded_length;
1136 
1137  kpos += padded_length;
1138  kleft -= padded_length;
1139  }
1140  }
1141  } else {
1142  // In the single K section case, can process the whole lot in one go.
1143  // Caution: 'blockwalker::kmax()' rounds up, so clamp to valid _Ksize.
1144  strat.transforms.PrepareB(buffer, B + (current.multi() * B_multi_stride), ldb,
1145  current.x0(), current.xmax(), current.k0(), std::min(current.kmax(), _Ksize));
1146  buffer += roundup(current.xmax() - current.x0(), strategy::out_width()) * roundup(current.kmax() - current.k0(), strategy::k_unroll());
1147  }
1148 
1149  // Advance to the next block, break if we run off the end.
1150  if (!current.advance()) {
1151  break;
1152  }
1153  }
1154  }
1155 
1156  void set_pretransposed_B_data(void *in_buffer) override {
1157  // Put the transposed data after the column sums - in non-quantized cases get_col_sum_size() == 0
1158  uintptr_t buffer_int = reinterpret_cast<uintptr_t>(in_buffer);
1159  _B_transposed = reinterpret_cast<Toi *>(buffer_int + get_col_sum_size());
1160  col_bias = reinterpret_cast<int32_t *>(in_buffer);
1161  }
1162 
1163  void set_quantized_bias(const int32_t *bias, size_t bias_multi_stride) override {
1164  if (std::is_same<OutputStage, Requantize32>::value) {
1165  Requantize32 *qp = reinterpret_cast<Requantize32 *>(&_os);
1166 
1167  qp->bias = bias;
1168  qp->bias_multi_stride = bias_multi_stride;
1169  }
1170  }
1171 
1172  void set_indirect_parameters(size_t string_len, const To * const * const *ptr) override {
1173  assert(string_len == _Ksize);
1174  _indirect_buf = ptr;
1175  }
1176 
1178  assert(parms.input_channels == _Ksize);
1179  _convolver = std::unique_ptr<convolver<To>>(new convolver<To>(parms));
1180  }
1181 
1182  // Estimate cycles for given problem given provided parameters
1183  template<typename perf_type>
1184  static uint64_t estimate_cycles(const GemmArgs &args) {
1185  unsigned int k_blocks = iceildiv(args._Ksize, get_k_block_size(args));
1186 
1187  const PerformanceParameters &params = strategy::template get_performance_parameters<perf_type>(args._ci);
1188 
1189  uint64_t total_macs = static_cast<uint64_t>(args._nbatches) * args._nmulti * roundup(args._Msize, strategy::out_height()) * roundup(args._Nsize, strategy::out_width()) * get_ktotal(args);
1190  uint64_t prepare_bytes = static_cast<uint64_t>(args._nbatches) * args._nmulti * roundup(args._Msize, strategy::out_height()) * get_ktotal(args) * sizeof(Toi);
1191  uint64_t merge_bytes = static_cast<uint64_t>(args._nbatches) * args._nmulti * k_blocks * args._Msize * roundup(args._Nsize, strategy::out_width()) * sizeof(Tr);
1192 
1193  float mac_cycles = static_cast<float>(total_macs) / params.kernel_macs_cycle;
1194  float prepare_cycles = static_cast<float>(prepare_bytes) / params.prepare_bytes_cycle;
1195  float merge_cycles = static_cast<float>(merge_bytes) / params.merge_bytes_cycle;
1196 
1197  float total_cycles = mac_cycles + prepare_cycles + merge_cycles;
1198 
1199  // We can't thread over multis or width, which makes this a poor
1200  // choice in many threaded cases. Penalize that here.
1201  float parallelism_available = static_cast<float>(iceildiv(args._Msize, strategy::out_height()) * args._nbatches) * 0.9f;
1202 
1203  if (parallelism_available < args._maxthreads) {
1204  total_cycles *= (static_cast<float>(args._maxthreads) / parallelism_available);
1205  }
1206 
1207  return static_cast<uint64_t>(total_cycles);
1208  }
1209 
1210  GemmConfig get_config() override {
1211  GemmConfig c;
1212 
1214  c.inner_block_size = _k_block;
1215  c.outer_block_size = _x_block;
1216  c.filter = get_type_name<strategy>();
1217  c.weight_format = get_weight_format(get_kernel_weight_format<strategy, FixedFormat, To>::get(), sizeof(To));
1218 
1219  return c;
1220  }
1221 };
1222 
1223 // Aliases for the variations
1224 template<typename strategy, typename To, typename Tr, typename OutputStage=Nothing>
1226 
1227 template<typename strategy, typename To, typename Tr, typename OutputStage=Nothing>
1229 
1230 template<typename strategy, typename To, typename Tr>
1232 
1233 template<typename strategy, typename To, typename Tr>
1235 
1236 } // namespace arm_gemm
kernel_weight_format.hpp
arm_gemm::convolver
Definition: convolver.hpp:51
GemmTuner.args
args
Definition: GemmTuner.py:679
arm_gemm::PerformanceParameters
Definition: performance_parameters.hpp:28
arm_compute::test::validation::run
lstmq run()
N
unsigned int N
Definition: CpuGemmAssemblyDispatch.cpp:96
type
decltype(strategy::transforms) typedef type
Definition: gemm_interleaved.hpp:261
mergeresults.hpp
arm_gemm::NDCoordinate::get_position_end
int_t get_position_end(int_t d) const
Definition: ndrange.hpp:190
arm_gemm::NDCoordinate
NDCoordinate builds upon a range, but specifies a starting position in addition to a size which it in...
Definition: ndrange.hpp:151
arm_gemm::ConvolutionParameters
Definition: convolution_parameters.hpp:48
arm_gemm::requantize_block_32
void requantize_block_32(const Requantize32 &qp, unsigned int width, unsigned int height, const Tin *input, unsigned int in_stride, Tout *output, unsigned int out_stride, const int32_t *row_bias, const int32_t *col_bias, unsigned int start_col)
arm_gemm::GemmInterleaved::get_window_size
ndrange_t get_window_size() const override
Definition: gemm_interleaved.hpp:703
arm_gemm::GemmInterleaved::estimate_cycles
static uint64_t estimate_cycles(const GemmArgs &args)
Definition: gemm_interleaved.hpp:1184
strategy
const StratType * strategy
Definition: working_space.hpp:105
arm_gemm::GemmInterleaved::operator=
GemmInterleaved & operator=(GemmInterleaved &)=delete
arm_gemm::roundup
T roundup(const T a, const T b)
Definition: utils.hpp:70
x_start
const uint32_t x_start
Definition: impl.cpp:47
x_end
const uint32_t x_end
Definition: impl.cpp:48
arm_gemm::Requantize32::bias
const int32_t * bias
Definition: arm_gemm.hpp:172
arm_gemm::PerformanceParameters::prepare_bytes_cycle
float prepare_bytes_cycle
Definition: performance_parameters.hpp:30
kernel_traits.hpp
arm_gemm::compute_col_sums
void compute_col_sums(const Requantize32 &qp, unsigned int width, unsigned int height, const T *input, unsigned int in_stride, int32_t *col_bias, unsigned int depth, unsigned int multi, unsigned int first_col)
arm_compute::mlgo::parser::advance
void advance(CharPosition &pos, char ch)
Definition: MLGOParser.cpp:147
arm_compute::CPUInfo
Definition: CPPTypes.h:66
arm_gemm::GemmMethod::GEMM_INTERLEAVED
@ GEMM_INTERLEAVED
arm_gemm::is_sme
Definition: kernel_traits.hpp:47
arm_gemm::GemmConfig::weight_format
WeightFormat weight_format
Definition: arm_gemm.hpp:112
performance_parameters.hpp
arm_gemm::Requantize32::b_offset
int32_t b_offset
Definition: arm_gemm.hpp:175
arm_gemm::Activation
Definition: arm_gemm.hpp:123
arm_gemm::Requantize32::bias_multi_stride
size_t bias_multi_stride
Definition: arm_gemm.hpp:173
arm_gemm::NDRange
Definition: ndrange.hpp:34
arm_gemm::GemmConfig::filter
std::string filter
Definition: arm_gemm.hpp:109
arm_gemm::GemmInterleaved::pretranspose_B_array
void pretranspose_B_array(void *in_buffer, const To *B, const int ldb, const int B_multi_stride) override
Definition: gemm_interleaved.hpp:1063
arm_gemm
Definition: barrier.hpp:30
arm_gemm::GemmInterleaved::set_convolution_parameters
void set_convolution_parameters(ConvolutionParameters parms) override
Definition: gemm_interleaved.hpp:1177
ROUND_UP
#define ROUND_UP(x)
Definition: gemm_interleaved.hpp:48
arm_gemm::GemmConfig::outer_block_size
unsigned int outer_block_size
Definition: arm_gemm.hpp:111
arm_gemm::GemmInterleaved::GemmInterleaved
GemmInterleaved(const GemmArgs &args)
Definition: gemm_interleaved.hpp:688
arm_gemm::GemmConfig
Definition: arm_gemm.hpp:106
arm_gemm.hpp
arm_gemm::GemmInterleaved::get_B_pretransposed_array_size
size_t get_B_pretransposed_array_size() const override
Definition: gemm_interleaved.hpp:1033
bias
const int32_t * bias
Definition: working_space.hpp:322
arm_gemm::NDCoordinate::get_position
int_t get_position(int_t d) const
Definition: ndrange.hpp:176
arm_compute::test::validation::reference::accumulate
SimpleTensor< T2 > accumulate(const SimpleTensor< T1 > &src, DataType output_data_type)
Definition: Accumulate.cpp:38
convolver.hpp
arm_gemm::GemmInterleaved::set_quantized_bias
void set_quantized_bias(const int32_t *bias, size_t bias_multi_stride) override
Definition: gemm_interleaved.hpp:1163
arm_compute::test::validation::batch
const unsigned int batch
Definition: GEMMMatrixMultiplyNative.cpp:362
arm_gemm::GemmArgs
Definition: arm_gemm.hpp:142
arm_gemm::get_weight_format
WeightFormat get_weight_format(const KernelWeightFormat, size_t)
Definition: misc.cpp:40
arm_gemm::GemmConfig::method
GemmMethod method
Definition: arm_gemm.hpp:108
arm_gemm::GemmInterleaved::requantize_bias
void requantize_bias(void *in_buffer, const To *B, const int ldb, const int B_multi_stride) override
Definition: gemm_interleaved.hpp:1050
arm_gemm::PerformanceParameters::kernel_macs_cycle
float kernel_macs_cycle
Definition: performance_parameters.hpp:29
arm_gemm::iceildiv
T iceildiv(const T a, const T b)
Definition: utils.hpp:65
arm_gemm::GemmInterleaved::get_working_size
size_t get_working_size() const override
Definition: gemm_interleaved.hpp:985
quantized.hpp
arm_gemm::GemmInterleaved::set_nthreads
void set_nthreads(int nthreads) override
Definition: gemm_interleaved.hpp:715
arm_gemm::KernelWeightFormat::NON_FIXED
@ NON_FIXED
transform.hpp
arm_gemm::GemmInterleaved::GemmInterleaved
GemmInterleaved(GemmInterleaved &)=delete
arm_gemm::GemmInterleaved::set_pretransposed_B_data
void set_pretransposed_B_data(void *in_buffer) override
Definition: gemm_interleaved.hpp:1156
M
unsigned int M
Definition: CpuGemmAssemblyDispatch.cpp:95
arm_gemm::GemmInterleaved::set_working_space
void set_working_space(void *working_space) override
Definition: gemm_interleaved.hpp:994
arm_gemm::PerformanceParameters::merge_bytes_cycle
float merge_bytes_cycle
Definition: performance_parameters.hpp:31
arm_gemm::GemmInterleaved::get_B_pretranspose_window_size
size_t get_B_pretranspose_window_size() const override
Definition: gemm_interleaved.hpp:1043
arm_gemm::GemmInterleaved::B_is_pretransposed
bool B_is_pretransposed() const override
Definition: gemm_interleaved.hpp:1025
arm_gemm::ConvolutionParameters::input_channels
int64_t input_channels
Definition: convolution_parameters.hpp:52
utils.hpp
arm_gemm::KernelWeightFormat
KernelWeightFormat
Definition: kernel_weight_format.hpp:42
arm_compute::mlgo::parser::end
void end(TokenStream &in, bool &valid)
Definition: MLGOParser.cpp:290
arm_gemm::GemmConfig::inner_block_size
unsigned int inner_block_size
Definition: arm_gemm.hpp:110
arm_gemm::GemmInterleaved::pretranspose_B_array_part
void pretranspose_B_array_part(void *in_buffer, const To *B, const int ldb, const int B_multi_stride, size_t start, size_t end) override
Definition: gemm_interleaved.hpp:1067
arm_gemm::GemmCommon
Definition: gemm_common.hpp:169
arm_gemm::GemmInterleaved::B_pretranspose_required
bool B_pretranspose_required() const override
Definition: gemm_interleaved.hpp:1029
arm_gemm::GemmInterleaved::execute
void execute(const ndcoord_t &work_range, const ndcoord_t &, int threadid) override
Main execute member fucntion.
Definition: gemm_interleaved.hpp:720
bfloat.hpp
arm_gemm::GemmInterleaved
Definition: gemm_interleaved.hpp:332
arm_gemm::Requantize32
Definition: arm_gemm.hpp:169
arm_gemm::GemmInterleaved::get_config
GemmConfig get_config() override
Definition: gemm_interleaved.hpp:1210
arm_gemm::GemmInterleaved::set_indirect_parameters
void set_indirect_parameters(size_t string_len, const To *const *const *ptr) override
Definition: gemm_interleaved.hpp:1172
arm_gemm::GemmInterleaved::GemmInterleaved
GemmInterleaved(const GemmArgs &args, const OutputStage &os)
Definition: gemm_interleaved.hpp:678