blob: e549da86d449c5e6f739884959e119b9c686e83a [file] [log] [blame]
SiCong Lia8d80582023-05-19 14:23:37 +01001/*
2 * Copyright (c) 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#include "helpers.h"
25#include "tile_helpers.h"
26
Mohammed Suhail Munshi8e2dede2023-06-27 14:25:58 +010027#ifdef BIAS
28// This function performs in-place bias addition for float and half datatypes when bias is enabled.
29// Note The tile's dimensions used for the LHS and RHS matrices (M0, N0) must be passed at compile time using -DN0, -DM0 (e.g. -DN0=8, -DM0=4).
30inline void perform_bias_addition(uchar *bias_ptr, uint bias_offset_first_element_in_bytes, TILE(DATA_TYPE, M0, N0, acc), uint x)
31{
32 TILE(DATA_TYPE, 1, N0, bias_tile);
33
34 // below expands to use bias_ptr and bias_offset_first_element_in_bytes
35 T_LOAD(DATA_TYPE, 1, N0, BUFFER, bias, x, 0, 1, 0, bias_tile);
36
37 // c = c + bias[broadcasted]
38 T_ELTWISE_BROADCAST_ADD_X(DATA_TYPE, M0, N0, acc, bias_tile, acc);
39}
40#endif // defined(BIAS)
41
SiCong Lia8d80582023-05-19 14:23:37 +010042#if defined(MAT_MUL_NATIVE_MMUL_NT_NT)
43/** This OpenCL kernel performs the batch matrix multiplication (BatchMatMul) using MMUL: LHS non-transposed, RHS non-transposed - buffer only
44 *
45 * @note the "batch" here expresses the number of matrix multiplications to run in parallel. However, it
46 * should NOT be confused with the batch size of the model. For NHWC the "batch" is the "H" dimension
47 * @note The data type must be passed at compile time using -DDATA_TYPE (e.g. -DDATA_TYPE=float)
48 * @note The tile's dimensions used for the LHS and RHS matrices (M0, N0 and K0) must be passed at compile time using -DN0, -DM0 and -DK0 (e.g. -DN0=8, -DM0=4, -DK0=1).
49 * @note The number of leftover outputs rows/columns must be passed using -DN0_LEFTOVER and -DM0_LEFTOVER (e.g. -DN0_LEFTOVER=2, -DM0_LEFTOVER=3)
50 * @note The MMUL block dimension (MMUL_M0, MMUL_N0, MMUL_K0) must be passed at compile time using -DMMUL_M0, -DMMUL_N0 and -DMMUL_K0 (e.g. -DMMUL_M0=4, -DMMUL_N0=4, -DMMUL_K0=4).
SiCong Lia8d80582023-05-19 14:23:37 +010051 * @note The kernel name in uppercase must be passed at compile time (e.g. -DMAT_MUL_NATIVE_MMUL_NT_NT)
52 * @note Only the following configurations of M0, N0 and K0 are currently supported:
53 * - M0 > 0
54 * - N0 = 1, 2, 3, 4, 8, 16
55 * - K0 = 1
56 * @note Values > 8 for M0 are not expected to be efficient
57 *
Mohammed Suhail Munshi8e2dede2023-06-27 14:25:58 +010058 * @param[in] lhs_ptr Pointer to the lhs matrix. Supported data types: F32/F16
59 * @param[in] lhs_stride_y Stride of the lhs matrix in Y (2nd) dimension (in bytes)
60 * @param[in] lhs_stride_z Stride of the lhs tensor in Z (3rd) dimension (in bytes)
61 * @param[in] lhs_w The width of the lhs tensor
62 * @param[in] lhs_h The height of the lhs tensor
63 * @param[in] lhs_n Number of the matrices (buffers) in the batch
64 * @param[in] lhs_offset_first_element_in_bytes The offset of the first element in the lhs matrix
65 * @param[in] rhs_ptr Pointer to the rhs matrix. Supported data types: same as @p lhs_ptr
66 * @param[in] rhs_stride_y Stride of the rhs matrix in Y (2nd) dimension (in bytes)
67 * @param[in] rhs_stride_z Stride of the rhs tensor in Z (3rd) dimension (in bytes)
68 * @param[in] rhs_w The width of the rhs tensor
69 * @param[in] rhs_h The height of the rhs tensor
70 * @param[in] rhs_n Number of the matrices (buffers) in the batch
71 * @param[in] rhs_offset_first_element_in_bytes The offset of the first element in the rhs matrix
72 * @param[in] bias_ptr (Optional) Pointer to the bias tensor. Supported data type: same as @p lhs_ptr
73 * @param[in] bias_stride_y (Optional) Stride of the bias tensor in Y dimension (in bytes)
74 * @param[in] bias_stride_z (Optional) Stride of the bias tensor in Z dimension (in bytes)
75 * @param[in] bias_w (Optional) The size of the width dimension of the bias tensor
76 * @param[in] bias_h (Optional) The size of the height dimension of the bias tensor
77 * @param[in] bias_n (Optional) The size of the depth dimension of the bias tensor
78 * @param[in] bias_offset_first_element_in_bytes (Optional) The offset of the first element in the bias tensor
79 * @param[out] dst_ptr Pointer to the dst matrix. Supported data types: same as @p lhs_ptr
80 * @param[in] dst_stride_y Stride of the dst matrix in Y (2nd) dimension (in bytes)
81 * @param[in] dst_stride_z Stride of the dst tensor in Z (3rd) dimension (in bytes)
82 * @param[in] dst_w The width of the dst tensor
83 * @param[in] dst_h The height of the dst tensor
84 * @param[in] dst_n Number of the matrices (buffers) in the batch
85 * @param[in] dst_offset_first_element_in_bytes The offset of the first element in the dst matrix
86 * @param[in] M Number of rows in LHS matrix
87 * @param[in] N Number of columns in RHS matrix
88 * @param[in] K Number of columns in LHS matrix and rows in RHS matrix, which is multiple of MMUL_K0.
SiCong Lia8d80582023-05-19 14:23:37 +010089 */
90__kernel void mat_mul_native_mmul_nt_nt(
91 TENSOR3D_T(lhs, BUFFER),
92 TENSOR3D_T(rhs, BUFFER),
Mohammed Suhail Munshi8e2dede2023-06-27 14:25:58 +010093#ifdef BIAS
94 TENSOR3D_T(bias, BUFFER),
95#endif // defined(BIAS)
SiCong Lia8d80582023-05-19 14:23:37 +010096 TENSOR3D_T(dst, BUFFER),
97 const int M,
Ramy Elgammalc9525962023-05-19 14:23:37 +010098 const int N,
99 const int K)
SiCong Lia8d80582023-05-19 14:23:37 +0100100{
Gunes Bayir00474e92023-06-19 21:33:51 +0100101#define MMUL_BLOCK_SIZE (MMUL_M0 * MMUL_N0) // MMUL block size for the output matrix
SiCong Lia8d80582023-05-19 14:23:37 +0100102
Gunes Bayir00474e92023-06-19 21:33:51 +0100103 // The output/destination matrix is divided into "sections". Each section is filled by a group of
104 // threads of size MMUL_BLOCK_SIZE, bundled together according to GWS_x.
105 // Each thread writes to a tile of M0 x N0 (the usual output block size for a thread) in the output matrix.
106 // Therefore, the section dimensions are (MMUL_M0 x M0) x (MMUL_N0 x N0).
107
108 // The GWS is constructed in such a way that the y global id is the y section coordinate,
109 // and the x global id is a transformed thread id: MMUL_BLOCK_SIZE number of consecutive threads
110 // in the x dimension corresponding to a section.
111 // This can be visualized as first obtaining the coordinates of all the sections:
112 // x = [0, (N / N0) / MMUL_N0) --> (N / N0) / MMUL_N0 is the number of sections in x dimension
113 // y = [0, (M / M0) / MMUL_M0) --> (M / M0) / MMUL_M0 is the number of sections in y dimension
114 // Then multiply the x coordinates with MMUL_SECTION_NUM_THREADS to get the consecutive thread ids in the x dimension
115 // x = [0, ((N / N0) / MMUL_N0) * MMUL_N0 * MMUL_M0)
116 // x = [0, (N / N0) * MMUL_MO)
117 const uint x0 = get_global_id(0); // [0, (N / N0) * MMUL_M0)
Mohammed Suhail Munshi8e2dede2023-06-27 14:25:58 +0100118 // The upper limit is a simplified version of (N / N0) / MMUL_N0) * MMUL_BLOCK_SIZE)
Gunes Bayir00474e92023-06-19 21:33:51 +0100119 const uint y0 = get_global_id(1); // [0, (M / M0) / MMUL_M0)
SiCong Lia8d80582023-05-19 14:23:37 +0100120 const uint z = get_global_id(2); // Batch
121
Gunes Bayir00474e92023-06-19 21:33:51 +0100122 // Get section coordinates
123 const uint section_x = (x0 / MMUL_BLOCK_SIZE);
124 const uint section_y = y0;
SiCong Lia8d80582023-05-19 14:23:37 +0100125
Gunes Bayir00474e92023-06-19 21:33:51 +0100126 // Within these sections, each thread writes onto a small output block of size M0 x N0
127 // in row major order. A section divided into tiles can be visualized as below.
128 //
129 // (Figure 1)
130 // A Section in the Output Matrix
131 //
132 // _____N0__________N0____________________N0____
133 // | | | | |
134 // | | | | |
135 // M0 | Thread 1 | Thread 2 | ... | Thread |
136 // | | | | MMUL_N0 |
137 // |___________|__________|_________|___________|
138 // | | | |
139 // | | | |
140 // M0 | Thread | . | |
141 // | MMUL_N0+1 | . | | (M0 x MMUL_M0)
142 // |___________| . | |
143 // | . | |
144 // | . | |
145 // | . | |
146 // | |___________|
147 // | | |
148 // | | Thread |
149 // M0 | | MMUL_N0 x |
150 // | | MMUL_M0 |
151 // |________________________________|___________|
152 // N0 x MMUL_N0
153 //
154 // The output matrix has several of these sections. As shown above, each section
155 // will be filled by a separate thread group of size MMUL_BLOCK_SIZE. The overall
156 // section layout of the output matrix is as below. For instance, S(1,1) will be filled
157 // by MMUL_BLOCK_SIZE (possibly equal to 16) threads, so as S(0,1) and others.
158 //
159 // (Figure 2)
160 // DST Matrix
161 // ____________________________________
162 // | | | | |
163 // | S(0,0) | S(0,1) | ... | S(0, X) |
164 // |________|________|_______|_________|
165 // | | | | |
166 // | S(1,0) | S(1,1) | ... | S(1, X) |
167 // |________|________|_______|_________|
168 // | . | | |
169 // | . | | | Y = (M / M0) / MMUL_M0 - 1 (Max possible section y coordinate)
170 // | . | | | X = (N / N0) / MMUL_N0 - 1 (Max possible section x coordinate)
171 // |________|________|_________________|
172 // | | | | | S(y, x) denotes the section, and y and x are computed in
173 // | S(Y,0) | S(Y,1) | | S(Y, X) | section_y, section_x respectively.
174 // |________|________|_______|_________|
175 //
176 //
177 //
178 //
179 // A complete view involving the three matrices is given below. It examplifies how the section S(0,0) is computed.
180 //
181 // (Figure 3)
182 // Complete View
183 //
184 // LHS Matrix RHS Matrix DST Matrix
185 //
186 // ___MMUL_K0___________ __MMUL_N0 x N0____________ ___MMUL_N0 x N0____________________
187 // /|xxxxxxxxxx| | /|xxxxxxxxxxxxxxx| | /|xxxxxxxxxxxxxxxxxxx| |
188 // / |xxxxxxxxxx| | MMUK_K0 ||xxxxxxxxxxxxxxx| | / |xxxxxxxxxxxxxxxxxxx| |
189 // MMUL_M0 | |xxxxxxxxxx| ---> | ||xxxxxxxxxxxxxxx| . . . | MMUL_M0 | |xxxxxxxxxxxxxxxxxxx| |
190 // x M0 | |xxxxxxxxxx| | \|_______________|_________| x M0 | |xxxxxxxxxxxxxxxxxxx| ... |
191 // | |xxxxxxxxxx| | | | | |xxxxxxxxxxxxxxxxxxx| |
192 // | |xxxxxxxxxx| | x | | | = \ |xxxxxxxxxxxxxxxxxxx| |
193 // \|__________|_________| | | | \|___________________| |
194 // | | | \/ | | |
195 // | , | |_________________________| | . |
196 // | , | | . |
197 // | , | | . |
198 // |____________________| |_________________________________|
199 //
200 // Horizontal and vertical arrows show the direction of K loop (main loop in the kernel).
201 // Each output section shown above is a zooomed out version of Figure 1.
202 //
203 // In each iteration of the main loop, LHS matrix traverses towards rightward, and RHS matrix traverses towards downward,
204 // the LHS section of (MMUL_M0 x M0) x MMUL_K0 and RHS section of MMUL_K0 x (MMUL_N0 x N0) is multiplied
205 // "cooperatively" using arm_matrix_multiply calls, and the result is accummulated over the output (DST) section
206 // of size (MMUL_M0 x M0) x (MMUL_N0 x N0) shown with 'x' signs.
207 //
208 // If it was a single thread, this multiplication would have been straightforward with a T_MMUL call.
209 // However, since it involves multiple threads working together using the aforementioned extension, it
210 // works slightly differently.
211 //
212 // Here is how threads access the LHS and RHS matrices:
213 // (Assume MMUL_K0 = MMUL_N0 = MMUL_M0 = 4 because the following diagram is heavily dependent on this)
214 //
215 // (Figure 4)
216 // Thread Access Layouts in LHS & RHS matrices
217 //
218 // LHS matrix RHS Matrix
219 // ___________________________ __________N0 times______N0 times____________________N0 times_______
220 // |__T0__|__T1__|__T2__|__T3__| |__T0__| ... |__T0__|__T1__| ... |__T1__| ... |__T3__| ... |__T3__|
221 // |__T0__| ... | |__T4__| ... |__T4__|__T5__| ... |__T5__| ... |__T7__| ... |__T7__|
222 // M0 | . . | |__T8__| ... |__T8__|__T9__| ... |__T9__| ... |__T11_| ... |__T11_|
223 // Times | . . | |__T12_|_____|__T12_|__T13_|______|__T13_|_____|__T15_|_____|__T15_|
224 // | . . | X
225 // |__T0__|__T1__|__T2__|__T3__|
226 // |__T4__|__T5__|__T6__|__T7__|
227 // |__T4__|__T5__|__T6__|__T7__|
228 // M0 | . . |
229 // Times | . . |
230 // | . . |
231 // |__T4__|__T5__|__T6__|__T7__|
232 // |__T8__|__T9__|__T10_|__T11_|
233 // M0 | . |
234 // Times | . |
235 // | . |
236 // |__T12_|__T13_|__T14_|__T15_|
237 // M0 | . |
238 // Times | . |
239 // | . |
240 // |__T12_|__T13_|__T14_|__T15_|
241 //
242 //
243 // This access layout is designed such that the threads access continuous elements of each matrix (in terms of row/column).
244 // To multiply these large sections, the arm_matrix_multiply call is made for each of the M0xN0 elements. So, for each
245 // combination of m0 and n0 (iterators of M0 and N0 from 0 to M0-1 and N0-1 respectively), one arm_matrix_multiply call is
246 // made, and MMUL_BLOCK_SIZE number of threads compute the result.
247 //
248 // The matrix multiplication taking place in this extension
249 // is an "interleaved" one, because, for example, if m0=0 and n0=0, i.e. the first iteration, we would use the first,
250 // M0-th, 2M0-th and 3M0-th rows of the LHS matrix. Similarly, we jump N0 steps in the RHS matrix. This is how we access
251 // one element for each thread in a single (m0, n0) loop.
252 //
253 // For example, if we have
254 // - a 8 x 4 LHS section
255 // - 4 x 8 RHS section
256 // - Each vector variable ai, bj represent a 4x1 vector
257 // - ^T (superscript T) denotes transpose
258 // - M0 = N0 = 2
259 // - MMUL_N0 = MMUL_M0 = MMUL_K0 = 4
260 //
261 // (Figure 5)
262 // Mathematical view of the Matrix Multiplication
263 //
264 // LHS RHS DST
265 // [ a1^T ] [ b1 b2 b3 b4 b5 b6 b7 ] [ a1^Tb1 a1^Tb2 a1^Tb3 ... a1^Tb7 ]
266 // [ a2^T ] 4 x 8 [ a2^Tb1 a2^Tb2 a2^Tb3 ... a2^Tb7 ]
267 // [ a3^T ] [ ]
268 // [ a4^T ] = [ . . ]
269 // [ a5^T ] X [ . . ]
270 // [ a6^T ] [ . . ]
271 // [ a7^T ] [ ]
272 // [ a8^T ] [ a7^Tb1 a7^Tb2 a7^Tb3 ... a7^Tb7 ]
273 // 8 x 4 8 x 8
274 //
275 //
276 // For the first iteration, i.e. (m0, n0) = (0, 0), the arm_matrix_multiply would multiply the following matrices:
277 //
278 // [ a1^T ] [ b1 b3 b5 b7 ] [ a1^Tb1 a1^Tb3 a1^Tb5 a1^Tb7 ]
279 // [ a3^T ] x 4 x 4 = [ a3^Tb1 a1^Tb3 a1^Tb5 a1^Tb7 ]
280 // [ a5^T ] [ a5^Tb1 a1^Tb3 a1^Tb5 a1^Tb7 ]
281 // [ a7^T ] [ a7^Tb1 a7^Tb3 a7^Tb5 a7^Tb7 ]
282 // 4 x 4 4 x 4
283 // The elements calculated in the 4x4 output block are the "interleaved" elements in the DST above.
284 // When we follow for each combination of (m0, n0), every element of the DST matrix "section" is filled.
285 //
286
287 // Get thread coordinates within an mmul block (of size MMUL_BLOCK_SIZE)
288 // Since threads are grouped in x dimension, the modular of x-dim global id
289 // wrt the MMUL_BLOCK_SIZE is the thread id in the group, ranging from 0 to
290 // MMUL_BLOCK_SIZE-1. Because the thread numbering is in row-major order.
SiCong Lia8d80582023-05-19 14:23:37 +0100291 const uint thread_id = (x0 % MMUL_BLOCK_SIZE);
292 const uint thread_x = thread_id % MMUL_N0;
293 const uint thread_y = (thread_id / MMUL_N0);
294
295 // Starting destination coordinates
296 // Note: We need to clamp dst_x and dst_y because we always need to execute a complete MMUL block! Only after the matrix multiplication
297 // part can we exit the kernel if it is out-of-bound. Remember, we have a cooperative matrix multiplication. Therefore, we need a full block to get the correct results
298 // Although we will never write out-of-bound, we still need this clamp to ensure that we do not read out-of-bound either.
Gunes Bayir00474e92023-06-19 21:33:51 +0100299 // The unclamped dst coordinates can be calculated easily from the output section coordinates and the thread coordinates (see above figure).
300
301 // See Figure 1 & 2. Thread step size is N0 and M0,
302 // Section step size is N0 x MMUL_N0 and M0 x MMUL_M0
303 // respectively for x, y dimensions.
304 const uint dst_x_unclamped = thread_x * N0 + section_x * N0 * MMUL_N0;
305 const uint dst_y_unclamped = thread_y * M0 + section_y * M0 * MMUL_M0;
SiCong Lia8d80582023-05-19 14:23:37 +0100306 const uint dst_x = min(dst_x_unclamped, (uint)(N - N0));
307 const uint dst_y = min(dst_y_unclamped, (uint)(M - M0));
308
309 // Starting LHS coordinates
310 const uint lhs_x = thread_x;
311 const uint lhs_y = dst_y;
312
313 // Starting RHS coordinates
314 const uint rhs_x = dst_x;
315 const uint rhs_y = thread_y;
316
317 // Compute LHS/RHS/DST matrix address
318 lhs_offset_first_element_in_bytes += lhs_x * sizeof(DATA_TYPE) + lhs_y * lhs_stride_y + z * lhs_stride_z;
319 rhs_offset_first_element_in_bytes += rhs_x * sizeof(DATA_TYPE) + rhs_y * rhs_stride_y + z * rhs_stride_z;
320 dst_offset_first_element_in_bytes += dst_x * sizeof(DATA_TYPE) + dst_y * dst_stride_y + z * dst_stride_z;
321
322 // Initialize the accumulators
323 // MMUL extension accumulate the result in F32 for both F32 and F16
324 TILE(float, M0, N0, c_f32);
325
326 LOOP_UNROLLING(int, i, 0, 1, M0,
327 {
328 c_f32[i].v = 0;
329 })
330
331 for(int k = 0; k < K; k += MMUL_K0)
332 {
333 // A tile of M0xK0 but K0 must be set to 1
334 TILE(DATA_TYPE, M0, 1, a);
335 // A tile of K0xN0 but K0 must be set to 1
336 TILE(DATA_TYPE, 1, N0, b);
337
338 // Load tile from the lhs/rhs tensors
339 T_LOAD(DATA_TYPE, M0, 1, BUFFER, lhs, 0, 0, 1, lhs_stride_y, a);
340 T_LOAD(DATA_TYPE, 1, N0, BUFFER, rhs, 0, 0, 1, rhs_stride_y, b);
341
342 LOOP_UNROLLING(int, m0, 0, 1, M0,
343 {
344 LOOP_UNROLLING(int, n0, 0, 1, N0,
345 {
346 c_f32[m0].s[n0] = arm_matrix_multiply(a[m0].s[0], b[0].s[n0], c_f32[m0].s[n0]);
347 })
348 })
349
350 lhs_offset_first_element_in_bytes += MMUL_K0 * sizeof(DATA_TYPE);
351 rhs_offset_first_element_in_bytes += MMUL_K0 * rhs_stride_y;
352 }
353
354 // For threads "outside" of the dst bound, we do not write but we have to "read" (arm_matrix_multiply). That's why this needs to happen after arm_matrix_multiply
355 if(dst_x_unclamped >= N || dst_y_unclamped >= M)
356 {
357 return;
358 }
359
360#if defined(HALF_PRECISION)
361 TILE(DATA_TYPE, M0, N0, c);
362
363 // Conversion required for the half precision
364 LOOP_UNROLLING(int, m0, 0, 1, M0,
365 {
366 LOOP_UNROLLING(int, n0, 0, 1, N0,
367 {
368 c[m0].s[n0] = c_f32[m0].s[n0];
369 })
370 })
371#else // defined(HALF_PRECISION)
372#define c c_f32
373#endif // defined(HALF_PRECISION)
374
Mohammed Suhail Munshi8e2dede2023-06-27 14:25:58 +0100375#ifdef BIAS
376 perform_bias_addition(bias_ptr, bias_offset_first_element_in_bytes, c, dst_x);
377#endif // defined(BIAS)
378
SiCong Lia8d80582023-05-19 14:23:37 +0100379 if(dst_x + N0 <= N || N0_LEFTOVER == 0)
380 {
381 LOOP_UNROLLING(int, m0, 0, 1, M0,
382 {
383 if(dst_y + m0 < M || M0_LEFTOVER == 0)
384 {
385 VSTORE(N0)
386 (c[m0].v, 0, (__global DATA_TYPE *)(dst_ptr + dst_offset_first_element_in_bytes + m0 * dst_stride_y));
387 }
388 })
389 }
390 else
391 {
392 LOOP_UNROLLING(int, m0, 0, 1, M0,
393 {
394 if(dst_y + m0 < M || M0_LEFTOVER == 0)
395 {
396 VSTORE_PARTIAL(N0, N0_LEFTOVER)
397 (c[m0].v, 0, (__global DATA_TYPE *)(dst_ptr + dst_offset_first_element_in_bytes + m0 * dst_stride_y));
398 }
399 })
400 }
401
402#undef MMUL_BLOCK_SIZE
403}
404#endif // defined(MAT_MUL_NATIVE_MMUL_NT_NT)
Ramy Elgammalc9525962023-05-19 14:23:37 +0100405
Gunes Bayir00474e92023-06-19 21:33:51 +0100406#if defined(MAT_MUL_NATIVE_MMUL_T_NT)
407/** This OpenCL kernel performs the batch matrix multiplication (BatchMatMul) using MMUL: LHS transposed, RHS non-transposed - buffer only
408 *
409 * @note the "batch" here expresses the number of matrix multiplications to run in parallel. However, it
410 * should NOT be confused with the batch size of the model. For NHWC the "batch" is the "H" dimension
411 * @note The data type must be passed at compile time using -DDATA_TYPE (e.g. -DDATA_TYPE=float)
412 * @note The tile's dimensions used for the LHS and RHS matrices (M0, N0 and K0) must be passed at compile time using -DN0, -DM0 and -DK0 (e.g. -DN0=8, -DM0=4, -DK0=1).
413 * @note The number of leftover outputs rows/columns must be passed using -DN0_LEFTOVER and -DM0_LEFTOVER (e.g. -DN0_LEFTOVER=2, -DM0_LEFTOVER=3)
414 * @note The MMUL block dimension (MMUL_M0, MMUL_N0, MMUL_K0) must be passed at compile time using -DMMUL_M0, -DMMUL_N0 and -DMMUL_K0 (e.g. -DMMUL_M0=4, -DMMUL_N0=4, -DMMUL_K0=4).
415 * @note The dimension K must be passed at compile time using -DK (e.g. -DK=4). K must be a multiple of MMUL_K0
416 * @note The kernel name in uppercase must be passed at compile time (e.g. -DMAT_MUL_NATIVE_MMUL_T_NT)
417 * @note Only the following configurations of M0, N0 and K0 are currently supported:
418 * - M0 = 1, 2, 3, 4, 8, 16
419 * - N0 = 1, 2, 3, 4, 8, 16
420 * - K0 = 1
421 * @note Values > 8 for M0 are not expected to be efficient
422 *
Mohammed Suhail Munshi8e2dede2023-06-27 14:25:58 +0100423 * @param[in] lhs_ptr Pointer to the lhs matrix. Supported data types: F32/F16
424 * @param[in] lhs_stride_y Stride of the lhs matrix in Y (2nd) dimension (in bytes)
425 * @param[in] lhs_stride_z Stride of the lhs tensor in Z (3rd) dimension (in bytes)
426 * @param[in] lhs_w The width of the lhs tensor
427 * @param[in] lhs_h The height of the lhs tensor
428 * @param[in] lhs_n Number of the matrices (buffers) in the batch
429 * @param[in] lhs_offset_first_element_in_bytes The offset of the first element in the lhs matrix
430 * @param[in] rhs_ptr Pointer to the rhs matrix. Supported data types: same as @p lhs_ptr
431 * @param[in] rhs_stride_y Stride of the rhs matrix in Y (2nd) dimension (in bytes)
432 * @param[in] rhs_stride_z Stride of the rhs tensor in Z (3rd) dimension (in bytes)
433 * @param[in] rhs_w The width of the rhs tensor
434 * @param[in] rhs_h The height of the rhs tensor
435 * @param[in] rhs_n Number of the matrices (buffers) in the batch
436 * @param[in] rhs_offset_first_element_in_bytes The offset of the first element in the rhs matrix
437 * @param[in] bias_ptr (Optional) Pointer to the bias tensor. Supported data type: same as @p lhs_ptr
438 * @param[in] bias_stride_y (Optional) Stride of the bias tensor in Y dimension (in bytes)
439 * @param[in] bias_stride_z (Optional) Stride of the bias tensor in Z dimension (in bytes)
440 * @param[in] bias_w (Optional) The size of the width dimension of the bias tensor
441 * @param[in] bias_h (Optional) The size of the height dimension of the bias tensor
442 * @param[in] bias_n (Optional) The size of the depth dimension of the bias tensor
443 * @param[in] bias_offset_first_element_in_bytes (Optional) The offset of the first element in the bias tensor
444 * @param[out] dst_ptr Pointer to the dst matrix. Supported data types: same as @p lhs_ptr
445 * @param[in] dst_stride_y Stride of the dst matrix in Y (2nd) dimension (in bytes)
446 * @param[in] dst_stride_z Stride of the dst tensor in Z (3rd) dimension (in bytes)
447 * @param[in] dst_w The width of the dst tensor
448 * @param[in] dst_h The height of the dst tensor
449 * @param[in] dst_n Number of the matrices (buffers) in the batch
450 * @param[in] dst_offset_first_element_in_bytes The offset of the first element in the dst matrix
451 * @param[in] M Number of rows in DST matrix
452 * @param[in] N Number of columns in DST matrix
453 * @param[in] K Number of rows in LHS and RHS matrices, which is multiple of MMUL_K0.
Gunes Bayir00474e92023-06-19 21:33:51 +0100454 */
455__kernel void mat_mul_native_mmul_t_nt(
456 TENSOR3D_T(lhs, BUFFER),
457 TENSOR3D_T(rhs, BUFFER),
Mohammed Suhail Munshi8e2dede2023-06-27 14:25:58 +0100458#ifdef BIAS
459 TENSOR3D_T(bias, BUFFER),
460#endif // defined(BIAS)
Gunes Bayir00474e92023-06-19 21:33:51 +0100461 TENSOR3D_T(dst, BUFFER),
462 const int M,
463 const int N,
464 const int K)
465{
466#define MMUL_BLOCK_SIZE (MMUL_M0 * MMUL_N0)
467 // For explanations on how this kernel works, please refer to NT/NT kernel. This kernel makes little modifications to it.
468
469 const uint x0 = get_global_id(0); // [0, (N / N0) * MMUL_M0)
Mohammed Suhail Munshi8e2dede2023-06-27 14:25:58 +0100470 // The upper limit is a simplified version of (N / N0) / MMUL_N0) * MMUL_BLOCK_SIZE)
Gunes Bayir00474e92023-06-19 21:33:51 +0100471 const uint y0 = get_global_id(1); // [0, (M / M0) / MMUL_M0)
472 const uint z = get_global_id(2); // Batch
473
474 // Get section coordinates
475 const uint section_x = (x0 / MMUL_BLOCK_SIZE);
476 const uint section_y = y0;
477
478 // Get thread coordinates
479 uint thread_id = (x0 % MMUL_BLOCK_SIZE);
480 uint thread_x = thread_id % MMUL_N0;
481 uint thread_y = (thread_id / MMUL_N0);
482
483 // See Nt/Nt kernel for explanations
484 const uint dst_x_unclamped = thread_x * N0 + section_x * N0 * MMUL_N0;
485 const uint dst_y_unclamped = thread_y * M0 + section_y * M0 * MMUL_M0;
486 const uint dst_x = min(dst_x_unclamped, (uint)(N - N0));
487 const uint dst_y = min(dst_y_unclamped, (uint)(M - M0));
488
489 // Starting LHS coordinates
490 uint lhs_x = dst_y;
491 uint lhs_y = thread_x;
492
493 // Starting RHS coordinates
494 uint rhs_x = dst_x;
495 uint rhs_y = thread_y;
496
497 // Compute LHS/RHS/DST matrix address
498 lhs_offset_first_element_in_bytes += lhs_x * sizeof(DATA_TYPE) + lhs_y * lhs_stride_y + z * lhs_stride_z;
499 rhs_offset_first_element_in_bytes += rhs_x * sizeof(DATA_TYPE) + rhs_y * rhs_stride_y + z * rhs_stride_z;
500 dst_offset_first_element_in_bytes += dst_x * sizeof(DATA_TYPE) + dst_y * dst_stride_y + z * dst_stride_z;
501
502 // Initialize the accumulators
503 // MMUL extension accumulate the result in F32 for both F32 and F16
504 TILE(float, M0, N0, c_f32);
505
506 LOOP_UNROLLING(int, i, 0, 1, M0,
507 {
508 c_f32[i].v = 0;
509 })
510
511 for(int k = 0; k < K; k += MMUL_K0)
512 {
513 TILE(DATA_TYPE, 1, M0, a);
514 TILE(DATA_TYPE, 1, N0, b);
515
516 // Load tile from the lhs/rhs tensors
517 T_LOAD(DATA_TYPE, 1, M0, BUFFER, lhs, 0, 0, 1, lhs_stride_y, a);
518 T_LOAD(DATA_TYPE, 1, N0, BUFFER, rhs, 0, 0, 1, rhs_stride_y, b);
519
520 LOOP_UNROLLING(int, m0, 0, 1, M0,
521 {
522 LOOP_UNROLLING(int, n0, 0, 1, N0,
523 {
524 c_f32[m0].s[n0] = arm_matrix_multiply(a[0].s[m0], b[0].s[n0], c_f32[m0].s[n0]);
525 })
526 })
527
528 lhs_offset_first_element_in_bytes += MMUL_K0 * lhs_stride_y;
529 rhs_offset_first_element_in_bytes += MMUL_K0 * rhs_stride_y;
530 }
531
532 // For threads "outside" of the dst bound, we do not write but we have to "read" (arm_matrix_multiply). That's why this needs to happen after arm_matrix_multiply
533 if(dst_x_unclamped >= N || dst_y_unclamped >= M)
534 {
535 return;
536 }
537
538#if defined(HALF_PRECISION)
539 TILE(DATA_TYPE, M0, N0, c);
540
541 // Conversion required for the half precision
542 LOOP_UNROLLING(int, m0, 0, 1, M0,
543 {
544 LOOP_UNROLLING(int, n0, 0, 1, N0,
545 {
546 c[m0].s[n0] = c_f32[m0].s[n0];
547 })
548 })
549#else // defined(HALF_PRECISION)
550#define c c_f32
551#endif // defined(HALF_PRECISION)
552
Mohammed Suhail Munshi8e2dede2023-06-27 14:25:58 +0100553#ifdef BIAS
554 perform_bias_addition(bias_ptr, bias_offset_first_element_in_bytes, c, dst_x);
555#endif // defined(BIAS)
556
Gunes Bayir00474e92023-06-19 21:33:51 +0100557 if(dst_x + N0 <= N || N0_LEFTOVER == 0)
558 {
559 LOOP_UNROLLING(int, m0, 0, 1, M0,
560 {
561 if(dst_y + m0 < M || M0_LEFTOVER == 0)
562 {
563 VSTORE(N0)
564 (c[m0].v, 0, (__global DATA_TYPE *)(dst_ptr + dst_offset_first_element_in_bytes + m0 * dst_stride_y));
565 }
566 })
567 }
568 else
569 {
570 LOOP_UNROLLING(int, m0, 0, 1, M0,
571 {
572 if(dst_y + m0 < M || M0_LEFTOVER == 0)
573 {
574 VSTORE_PARTIAL(N0, N0_LEFTOVER)
575 (c[m0].v, 0, (__global DATA_TYPE *)(dst_ptr + dst_offset_first_element_in_bytes + m0 * dst_stride_y));
576 }
577 })
578 }
579
580#undef MMUL_BLOCK_SIZE
581}
582#endif // defined(MAT_MUL_NATIVE_MMUL_T_NT)
583
Ramy Elgammalc9525962023-05-19 14:23:37 +0100584#if defined(MAT_MUL_NATIVE_MMUL_NT_T)
585/** This OpenCL kernel performs the batch matrix multiplication (BatchMatMul) using MMUL: LHS non-transposed, RHS transposed - buffer only
586 *
587 * @note the "batch" here expresses the number of matrix multiplications to run in parallel. However, it
588 * should NOT be confused with the batch size of the model. For NHWC the "batch" is the "H" dimension
589 * @note The data type must be passed at compile time using -DDATA_TYPE (e.g. -DDATA_TYPE=float)
590 * @note The tile's dimensions used for the LHS and RHS matrices (M0, N0 and K0) must be passed at compile time using -DN0, -DM0 and -DK0 (e.g. -DN0=8, -DM0=4, -DK0=1).
591 * @note The number of leftover outputs rows/columns must be passed using -DN0_LEFTOVER and -DM0_LEFTOVER (e.g. -DN0_LEFTOVER=2, -DM0_LEFTOVER=3)
592 * @note The MMUL block dimension (MMUL_M0, MMUL_N0, MMUL_K0) must be passed at compile time using -DMMUL_M0, -DMMUL_N0 and -DMMUL_K0 (e.g. -DMMUL_M0=4, -DMMUL_N0=4, -DMMUL_K0=4).
593 * @note The kernel name in uppercase must be passed at compile time (e.g. -DMAT_MUL_NATIVE_MMUL_NT_T)
594 * @note Only the following configurations of M0, N0 and K0 are currently supported:
595 * - M0 > 0
596 * - N0 = 1, 2, 3, 4, 8, 16
597 * - K0 = 1
598 * @note Values > 8 for M0 are not expected to be efficient
599 *
Mohammed Suhail Munshi8e2dede2023-06-27 14:25:58 +0100600 * @param[in] lhs_ptr Pointer to the lhs matrix. Supported data types: F32/F16
601 * @param[in] lhs_stride_y Stride of the lhs matrix in Y (2nd) dimension (in bytes)
602 * @param[in] lhs_stride_z Stride of the lhs tensor in Z (3rd) dimension (in bytes)
603 * @param[in] lhs_w The width of the lhs tensor
604 * @param[in] lhs_h The height of the lhs tensor
605 * @param[in] lhs_n Number of the matrices (buffers) in the batch
606 * @param[in] lhs_offset_first_element_in_bytes The offset of the first element in the lhs matrix
607 * @param[in] rhs_ptr Pointer to the rhs matrix. Supported data types: same as @p lhs_ptr
608 * @param[in] rhs_stride_y Stride of the rhs matrix in Y (2nd) dimension (in bytes)
609 * @param[in] rhs_stride_z Stride of the rhs tensor in Z (3rd) dimension (in bytes)
610 * @param[in] rhs_w The width of the rhs tensor
611 * @param[in] rhs_h The height of the rhs tensor
612 * @param[in] rhs_n Number of the matrices (buffers) in the batch
613 * @param[in] rhs_offset_first_element_in_bytes The offset of the first element in the rhs matrix
614 * @param[in] bias_ptr (Optional) Pointer to the bias tensor. Supported data type: same as @p lhs_ptr
615 * @param[in] bias_stride_y (Optional) Stride of the bias tensor in Y dimension (in bytes)
616 * @param[in] bias_stride_z (Optional) Stride of the bias tensor in Z dimension (in bytes)
617 * @param[in] bias_w (Optional) The size of the width dimension of the bias tensor
618 * @param[in] bias_h (Optional) The size of the height dimension of the bias tensor
619 * @param[in] bias_n (Optional) The size of the depth dimension of the bias tensor
620 * @param[in] bias_offset_first_element_in_bytes (Optional) The offset of the first element in the bias tensor
621 * @param[out] dst_ptr Pointer to the dst matrix. Supported data types: same as @p lhs_ptr
622 * @param[in] dst_stride_y Stride of the dst matrix in Y (2nd) dimension (in bytes)
623 * @param[in] dst_stride_z Stride of the dst tensor in Z (3rd) dimension (in bytes)
624 * @param[in] dst_w The width of the dst tensor
625 * @param[in] dst_h The height of the dst tensor
626 * @param[in] dst_n Number of the matrices (buffers) in the batch
627 * @param[in] dst_offset_first_element_in_bytes The offset of the first element in the dst matrix
628 * @param[in] M Number of rows in LHS matrix
629 * @param[in] N Number of columns in RHS matrix
630 * @param[in] K Number of columns in LHS matrix and columns in RHS matrix, which is multiple of MMUL_K0.
Ramy Elgammalc9525962023-05-19 14:23:37 +0100631 */
632__kernel void mat_mul_native_mmul_nt_t(
633 TENSOR3D_T(lhs, BUFFER),
634 TENSOR3D_T(rhs, BUFFER),
Mohammed Suhail Munshi8e2dede2023-06-27 14:25:58 +0100635#ifdef BIAS
636 TENSOR3D_T(bias, BUFFER),
637#endif // defined(BIAS)
Ramy Elgammalc9525962023-05-19 14:23:37 +0100638 TENSOR3D_T(dst, BUFFER),
639 const int M,
640 const int N,
641 const int K)
642{
643#define MMUL_BLOCK_SIZE (MMUL_M0 * MMUL_N0)
Gunes Bayir00474e92023-06-19 21:33:51 +0100644 // For explanations on how this kernel works, please refer to NT/NT kernel. This kernel makes little modifications to it.
Ramy Elgammalc9525962023-05-19 14:23:37 +0100645
Gunes Bayir00474e92023-06-19 21:33:51 +0100646 const uint x0 = get_global_id(0); // [0, (N / N0) * MMUL_M0)
Mohammed Suhail Munshi8e2dede2023-06-27 14:25:58 +0100647 // The upper limit is a simplified version of (N / N0) / MMUL_N0) * MMUL_BLOCK_SIZE)
Gunes Bayir00474e92023-06-19 21:33:51 +0100648 const uint y0 = get_global_id(1); // [0, (M / M0) / MMUL_M0)
Ramy Elgammalc9525962023-05-19 14:23:37 +0100649 const uint z = get_global_id(2); // Batch
650
651 // Get block coordinates
Gunes Bayir00474e92023-06-19 21:33:51 +0100652 const uint section_x = (x0 / MMUL_BLOCK_SIZE);
653 const uint section_y = y0;
Ramy Elgammalc9525962023-05-19 14:23:37 +0100654
655 // Get thread coordinates within a block
656 const uint thread_id = (x0 % MMUL_BLOCK_SIZE);
657 const uint thread_x = thread_id % MMUL_N0;
658 const uint thread_y = (thread_id / MMUL_N0);
659
660 // Starting destination coordinates
661 // Note: We need to clamp dst_x and dst_y because we always need to execute a complete MMUL block! Only after the matrix multiplication
662 // part can we exit the kernel if it is out-of-bound. Remember, we have a cooperative matrix multiplication. Therefore, we need a full block to get the correct results
663 // Although we will never write out-of-bound, we still need this clamp to ensure that we do not read out-of-bound either.
Gunes Bayir00474e92023-06-19 21:33:51 +0100664 const uint dst_x_unclamped = thread_x * N0 + section_x * N0 * MMUL_N0;
665 const uint dst_y_unclamped = thread_y * M0 + section_y * M0 * MMUL_M0;
Ramy Elgammalc9525962023-05-19 14:23:37 +0100666 const uint dst_x = min(dst_x_unclamped, (uint)(N - N0));
667 const uint dst_y = min(dst_y_unclamped, (uint)(M - M0));
668
669 // Starting LHS coordinates
670 const uint lhs_x = thread_x;
671 const uint lhs_y = dst_y;
672
673 // Starting RHS coordinates
674 const uint rhs_x = thread_y;
675 const uint rhs_y = dst_x;
676
677 // Compute LHS/RHS/DST matrix address
678 lhs_offset_first_element_in_bytes += lhs_x * sizeof(DATA_TYPE) + lhs_y * lhs_stride_y + z * lhs_stride_z;
679 rhs_offset_first_element_in_bytes += rhs_x * sizeof(DATA_TYPE) + rhs_y * rhs_stride_y + z * rhs_stride_z;
680 dst_offset_first_element_in_bytes += dst_x * sizeof(DATA_TYPE) + dst_y * dst_stride_y + z * dst_stride_z;
681
682 // Initialize the accumulators
683 // MMUL extension accumulate the result in F32 for both F32 and F16
684 TILE(float, M0, N0, c_f32);
685
686 LOOP_UNROLLING(int, i, 0, 1, M0,
687 {
688 c_f32[i].v = 0;
689 })
690
691 for(int k = 0; k < K; k += MMUL_K0)
692 {
693 // A tile of M0xK0 but K0 must be set to 1
694 TILE(DATA_TYPE, M0, 1, a);
695 // A tile of N0xK0 but K0 must be set to 1
696 TILE(DATA_TYPE, N0, 1, b);
697
698 // Load tile from the lhs/rhs tensors
699 T_LOAD(DATA_TYPE, M0, 1, BUFFER, lhs, 0, 0, 1, lhs_stride_y, a);
700 T_LOAD(DATA_TYPE, N0, 1, BUFFER, rhs, 0, 0, 1, rhs_stride_y, b);
701
702 LOOP_UNROLLING(int, m0, 0, 1, M0,
703 {
704 LOOP_UNROLLING(int, n0, 0, 1, N0,
705 {
706 c_f32[m0].s[n0] = arm_matrix_multiply(a[m0].s[0], b[n0].s[0], c_f32[m0].s[n0]);
707 })
708 })
709
710 lhs_offset_first_element_in_bytes += MMUL_K0 * sizeof(DATA_TYPE);
711 rhs_offset_first_element_in_bytes += MMUL_N0 * sizeof(DATA_TYPE);
712 }
713
714 // For threads "outside" of the dst bound, we do not write but we have to "read" (arm_matrix_multiply). That's why this needs to happen after arm_matrix_multiply
715 if(dst_x_unclamped >= N || dst_y_unclamped >= M)
716 {
717 return;
718 }
719
720#if defined(HALF_PRECISION)
721 TILE(DATA_TYPE, M0, N0, c);
722
723 // Conversion required for the half precision
724 LOOP_UNROLLING(int, m0, 0, 1, M0,
725 {
726 LOOP_UNROLLING(int, n0, 0, 1, N0,
727 {
728 c[m0].s[n0] = c_f32[m0].s[n0];
729 })
730 })
731#else // defined(HALF_PRECISION)
732#define c c_f32
733#endif // defined(HALF_PRECISION)
734
Mohammed Suhail Munshi8e2dede2023-06-27 14:25:58 +0100735#ifdef BIAS
736 perform_bias_addition(bias_ptr, bias_offset_first_element_in_bytes, c, dst_x);
737#endif // defined(BIAS)
738
Ramy Elgammalc9525962023-05-19 14:23:37 +0100739 if(dst_x + N0 <= N || N0_LEFTOVER == 0)
740 {
741 LOOP_UNROLLING(int, m0, 0, 1, M0,
742 {
743 if(dst_y + m0 < M || M0_LEFTOVER == 0)
744 {
745 VSTORE(N0)
746 (c[m0].v, 0, (__global DATA_TYPE *)(dst_ptr + dst_offset_first_element_in_bytes + m0 * dst_stride_y));
747 }
748 })
749 }
750 else
751 {
752 LOOP_UNROLLING(int, m0, 0, 1, M0,
753 {
754 if(dst_y + m0 < M || M0_LEFTOVER == 0)
755 {
756 VSTORE_PARTIAL(N0, N0_LEFTOVER)
757 (c[m0].v, 0, (__global DATA_TYPE *)(dst_ptr + dst_offset_first_element_in_bytes + m0 * dst_stride_y));
758 }
759 })
760 }
761
762#undef MMUL_BLOCK_SIZE
763}
764#endif // defined(MAT_MUL_NATIVE_MMUL_NT_T)
Gunes Bayir00474e92023-06-19 21:33:51 +0100765
766#if defined(MAT_MUL_NATIVE_MMUL_T_T)
767/** This OpenCL kernel performs the batch matrix multiplication (BatchMatMul) using MMUL: LHS non-transposed, RHS transposed - buffer only
768 *
769 * @note the "batch" here expresses the number of matrix multiplications to run in parallel. However, it
770 * should NOT be confused with the batch size of the model. For NHWC the "batch" is the "H" dimension
771 * @note The data type must be passed at compile time using -DDATA_TYPE (e.g. -DDATA_TYPE=float)
772 * @note The tile's dimensions used for the LHS and RHS matrices (M0, N0 and K0) must be passed at compile time using -DN0, -DM0 and -DK0 (e.g. -DN0=8, -DM0=4, -DK0=1).
773 * @note The number of leftover outputs rows/columns must be passed using -DN0_LEFTOVER and -DM0_LEFTOVER (e.g. -DN0_LEFTOVER=2, -DM0_LEFTOVER=3)
774 * @note The MMUL block dimension (MMUL_M0, MMUL_N0, MMUL_K0) must be passed at compile time using -DMMUL_M0, -DMMUL_N0 and -DMMUL_K0 (e.g. -DMMUL_M0=4, -DMMUL_N0=4, -DMMUL_K0=4).
775 * @note The kernel name in uppercase must be passed at compile time (e.g. -DMAT_MUL_NATIVE_MMUL_NT_T)
776 * @note Only the following configurations of M0, N0 and K0 are currently supported:
777 * - M0 = 1, 2, 3, 4, 8, 16
778 * - N0 = 1, 2, 3, 4, 8, 16
779 * - K0 = 1
780 * @note Values > 8 for M0 are not expected to be efficient
781 *
Mohammed Suhail Munshi8e2dede2023-06-27 14:25:58 +0100782 * @param[in] lhs_ptr Pointer to the lhs matrix. Supported data types: F32/F16
783 * @param[in] lhs_stride_y Stride of the lhs matrix in Y (2nd) dimension (in bytes)
784 * @param[in] lhs_stride_z Stride of the lhs tensor in Z (3rd) dimension (in bytes)
785 * @param[in] lhs_w The width of the lhs tensor
786 * @param[in] lhs_h The height of the lhs tensor
787 * @param[in] lhs_n Number of the matrices (buffers) in the batch
788 * @param[in] lhs_offset_first_element_in_bytes The offset of the first element in the lhs matrix
789 * @param[in] rhs_ptr Pointer to the rhs matrix. Supported data types: same as @p lhs_ptr
790 * @param[in] rhs_stride_y Stride of the rhs matrix in Y (2nd) dimension (in bytes)
791 * @param[in] rhs_stride_z Stride of the rhs tensor in Z (3rd) dimension (in bytes)
792 * @param[in] rhs_w The width of the rhs tensor
793 * @param[in] rhs_h The height of the rhs tensor
794 * @param[in] rhs_n Number of the matrices (buffers) in the batch
795 * @param[in] rhs_offset_first_element_in_bytes The offset of the first element in the rhs matrix
796 * @param[in] bias_ptr (Optional) Pointer to the bias tensor. Supported data type: same as @p lhs_ptr
797 * @param[in] bias_stride_y (Optional) Stride of the bias tensor in Y dimension (in bytes)
798 * @param[in] bias_stride_z (Optional) Stride of the bias tensor in Z dimension (in bytes)
799 * @param[in] bias_w (Optional) The size of the width dimension of the bias tensor
800 * @param[in] bias_h (Optional) The size of the height dimension of the bias tensor
801 * @param[in] bias_n (Optional) The size of the depth dimension of the bias tensor
802 * @param[in] bias_offset_first_element_in_bytes (Optional) The offset of the first element in the bias tensor
803 * @param[out] dst_ptr Pointer to the dst matrix. Supported data types: same as @p lhs_ptr
804 * @param[in] dst_stride_y Stride of the dst matrix in Y (2nd) dimension (in bytes)
805 * @param[in] dst_stride_z Stride of the dst tensor in Z (3rd) dimension (in bytes)
806 * @param[in] dst_w The width of the dst tensor
807 * @param[in] dst_h The height of the dst tensor
808 * @param[in] dst_n Number of the matrices (buffers) in the batch
809 * @param[in] dst_offset_first_element_in_bytes The offset of the first element in the dst matrix
810 * @param[in] M Number of rows in LHS matrix
811 * @param[in] N Number of columns in RHS matrix
812 * @param[in] K Number of rows in LHS matrix and columns in RHS matrix, which is multiple of MMUL_K0.
Gunes Bayir00474e92023-06-19 21:33:51 +0100813 */
814__kernel void mat_mul_native_mmul_t_t(
815 TENSOR3D_T(lhs, BUFFER),
816 TENSOR3D_T(rhs, BUFFER),
Mohammed Suhail Munshi8e2dede2023-06-27 14:25:58 +0100817#ifdef BIAS
818 TENSOR3D_T(bias, BUFFER),
819#endif // defined(BIAS)
Gunes Bayir00474e92023-06-19 21:33:51 +0100820 TENSOR3D_T(dst, BUFFER),
821 const int M,
822 const int N,
823 const int K)
824{
825#define MMUL_BLOCK_SIZE (MMUL_M0 * MMUL_N0)
826 // For explanations on how this kernel works, please refer to NT/NT kernel. This kernel makes little modifications to it.
827
828 const uint x0 = get_global_id(0); // [0, (N / N0) * MMUL_M0)
Mohammed Suhail Munshi8e2dede2023-06-27 14:25:58 +0100829 // The upper limit is a simplified version of (N / N0) / MMUL_N0) * MMUL_BLOCK_SIZE)
Gunes Bayir00474e92023-06-19 21:33:51 +0100830 const uint y0 = get_global_id(1); // [0, (M / M0) / MMUL_M0)
831 const uint z = get_global_id(2); // Batch
832
833 // Get block coordinates
834 const uint section_x = (x0 / MMUL_BLOCK_SIZE);
835 const uint section_y = y0;
836
837 // Get thread coordinates within a block
838 const uint thread_id = (x0 % MMUL_BLOCK_SIZE);
839 const uint thread_x = thread_id % MMUL_N0;
840 const uint thread_y = (thread_id / MMUL_N0);
841
842 // Starting destination coordinates
843 // Note: We need to clamp dst_x and dst_y because we always need to execute a complete MMUL block! Only after the matrix multiplication
844 // part can we exit the kernel if it is out-of-bound. Remember, we have a cooperative matrix multiplication. Therefore, we need a full block to get the correct results
845 // Although we will never write out-of-bound, we still need this clamp to ensure that we do not read out-of-bound either.
846 const uint dst_x_unclamped = thread_x * N0 + section_x * N0 * MMUL_N0;
847 const uint dst_y_unclamped = thread_y * M0 + section_y * M0 * MMUL_M0;
848 const uint dst_x = min(dst_x_unclamped, (uint)(N - N0));
849 const uint dst_y = min(dst_y_unclamped, (uint)(M - M0));
850
851 // Starting LHS coordinates
852 const uint lhs_x = dst_y;
853 const uint lhs_y = thread_x;
854
855 // Starting RHS coordinates
856 const uint rhs_x = thread_y;
857 const uint rhs_y = dst_x;
858
859 // Compute LHS/RHS/DST matrix address
860 lhs_offset_first_element_in_bytes += lhs_x * sizeof(DATA_TYPE) + lhs_y * lhs_stride_y + z * lhs_stride_z;
861 rhs_offset_first_element_in_bytes += rhs_x * sizeof(DATA_TYPE) + rhs_y * rhs_stride_y + z * rhs_stride_z;
862 dst_offset_first_element_in_bytes += dst_x * sizeof(DATA_TYPE) + dst_y * dst_stride_y + z * dst_stride_z;
863
864 // Initialize the accumulators
865 // MMUL extension accumulate the result in F32 for both F32 and F16
866 TILE(float, M0, N0, c_f32);
867
868 LOOP_UNROLLING(int, i, 0, 1, M0,
869 {
870 c_f32[i].v = 0;
871 })
872
873 for(int k = 0; k < K; k += MMUL_K0)
874 {
875 // A tile of K0xM0 but K0 must be set to 1
876 TILE(DATA_TYPE, 1, M0, a);
877 // A tile of N0xK0 but K0 must be set to 1
878 TILE(DATA_TYPE, N0, 1, b);
879
880 // Load tile from the lhs/rhs tensors
881 T_LOAD(DATA_TYPE, 1, M0, BUFFER, lhs, 0, 0, 1, lhs_stride_y, a);
882 T_LOAD(DATA_TYPE, N0, 1, BUFFER, rhs, 0, 0, 1, rhs_stride_y, b);
883
884 LOOP_UNROLLING(int, m0, 0, 1, M0,
885 {
886 LOOP_UNROLLING(int, n0, 0, 1, N0,
887 {
888 c_f32[m0].s[n0] = arm_matrix_multiply(a[0].s[m0], b[n0].s[0], c_f32[m0].s[n0]);
889 })
890 })
891
892 lhs_offset_first_element_in_bytes += MMUL_K0 * lhs_stride_y;
893 rhs_offset_first_element_in_bytes += MMUL_N0 * sizeof(DATA_TYPE);
894 }
895
896 // For threads "outside" of the dst bound, we do not write but we have to "read" (arm_matrix_multiply). That's why this needs to happen after arm_matrix_multiply
897 if(dst_x_unclamped >= N || dst_y_unclamped >= M)
898 {
899 return;
900 }
901
902#if defined(HALF_PRECISION)
903 TILE(DATA_TYPE, M0, N0, c);
904
905 // Conversion required for the half precision
906 LOOP_UNROLLING(int, m0, 0, 1, M0,
907 {
908 LOOP_UNROLLING(int, n0, 0, 1, N0,
909 {
910 c[m0].s[n0] = c_f32[m0].s[n0];
911 })
912 })
913#else // defined(HALF_PRECISION)
914#define c c_f32
915#endif // defined(HALF_PRECISION)
916
Mohammed Suhail Munshi8e2dede2023-06-27 14:25:58 +0100917#ifdef BIAS
918 perform_bias_addition(bias_ptr, bias_offset_first_element_in_bytes, c, dst_x);
919#endif // defined(BIAS)
920
Gunes Bayir00474e92023-06-19 21:33:51 +0100921 if(dst_x + N0 <= N || N0_LEFTOVER == 0)
922 {
923 LOOP_UNROLLING(int, m0, 0, 1, M0,
924 {
925 if(dst_y + m0 < M || M0_LEFTOVER == 0)
926 {
927 VSTORE(N0)
928 (c[m0].v, 0, (__global DATA_TYPE *)(dst_ptr + dst_offset_first_element_in_bytes + m0 * dst_stride_y));
929 }
930 })
931 }
932 else
933 {
934 LOOP_UNROLLING(int, m0, 0, 1, M0,
935 {
936 if(dst_y + m0 < M || M0_LEFTOVER == 0)
937 {
938 VSTORE_PARTIAL(N0, N0_LEFTOVER)
939 (c[m0].v, 0, (__global DATA_TYPE *)(dst_ptr + dst_offset_first_element_in_bytes + m0 * dst_stride_y));
940 }
941 })
942 }
943
944#undef MMUL_BLOCK_SIZE
945}
946#endif // defined(MAT_MUL_NATIVE_MMUL_T_T)