main
hpp 424 lines 16.7 KB
Raw
1 // SPDX-License-Identifier: Apache-2.0
2 // SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
4 #ifndef CPU_MICRO_GEMM_INT8_NEON_HPP
5 #define CPU_MICRO_GEMM_INT8_NEON_HPP
6
7 #include <algorithm>
8 #include <cstdint>
9
10 #include "cpu/micro_gemm/cpu_micro_gemm_impl.hpp"
11
12 #include <arm_bf16.h>
13 #include <arm_neon.h>
14 #include <c10/util/BFloat16.h>
15 #include <c10/util/Exception.h>
16 #include <c10/util/Half.h>
17
18 namespace cpu_micro_gemm {
19
20 namespace neon_smmla {
21
22 constexpr int32_t K = 8;
23 constexpr int32_t Cols = 2;
24 constexpr int32_t TileSize = K * Cols;
25
26 FORCE_INLINE float32x4x2_t load_as_f32(const float* input) {
27 float32x4x2_t result;
28 result.val[0] = vld1q_f32(input);
29 result.val[1] = vld1q_f32(input + 4);
30 return result;
31 }
32
33 FORCE_INLINE float32x4x2_t load_as_f32(const c10::Half* input) {
34 const auto input_vec = vld1q_f16(reinterpret_cast<const float16_t*>(input));
35 float32x4x2_t result;
36 result.val[0] = vcvt_f32_f16(vget_low_f16(input_vec));
37 result.val[1] = vcvt_f32_f16(vget_high_f16(input_vec));
38 return result;
39 }
40
41 FORCE_INLINE float32x4x2_t load_as_f32(const c10::BFloat16* input) {
42 const auto input_vec = vld1q_bf16(reinterpret_cast<const bfloat16_t*>(input));
43 float32x4x2_t result;
44 result.val[0] = vcvt_f32_bf16(vget_low_bf16(input_vec));
45 result.val[1] = vcvt_f32_bf16(vget_high_bf16(input_vec));
46 return result;
47 }
48
49 FORCE_INLINE void store_acc_rowpair(const int32x4_t acc01,
50 const int32x4_t acc23,
51 const int32x4_t acc45,
52 const int32x4_t acc67,
53 int32_t* __restrict__ c_ptr,
54 const int64_t ldc, const int32_t m_rows) {
55 if (m_rows == 0) {
56 return;
57 }
58
59 vst1q_s32(c_ptr, vcombine_s32(vget_low_s32(acc01), vget_low_s32(acc23)));
60 vst1q_s32(c_ptr + 4, vcombine_s32(vget_low_s32(acc45), vget_low_s32(acc67)));
61
62 if (m_rows == 2) {
63 vst1q_s32(c_ptr + ldc,
64 vcombine_s32(vget_high_s32(acc01), vget_high_s32(acc23)));
65 vst1q_s32(c_ptr + ldc + 4,
66 vcombine_s32(vget_high_s32(acc45), vget_high_s32(acc67)));
67 }
68 }
69
70 FORCE_INLINE void gemm_micro_smmla_8x8_packed_a(
71 const int8_t* __restrict__ a_packed, const int8_t* __restrict__ b_packed,
72 int32_t* __restrict__ c_ptr, const int32_t m, const int32_t k_size,
73 const int64_t ldc) {
74 const int32x4_t zero = vdupq_n_s32(0);
75 int32x4_t acc0101 = zero, acc0123 = zero, acc0145 = zero, acc0167 = zero;
76 int32x4_t acc2301 = zero, acc2323 = zero, acc2345 = zero, acc2367 = zero;
77 int32x4_t acc4501 = zero, acc4523 = zero, acc4545 = zero, acc4567 = zero;
78 int32x4_t acc6701 = zero, acc6723 = zero, acc6745 = zero, acc6767 = zero;
79
80 const int8_t* __restrict__ a_tile = a_packed;
81 const int8_t* __restrict__ b_tile = b_packed;
82
83 #pragma GCC unroll 8
84 for (int32_t k_idx = 0; k_idx < k_size; k_idx += K) {
85 const int8x16_t a_tile01 = vld1q_s8(a_tile);
86 const int8x16_t a_tile23 = vld1q_s8(a_tile + TileSize);
87 const int8x16_t a_tile45 = vld1q_s8(a_tile + 2 * TileSize);
88 const int8x16_t a_tile67 = vld1q_s8(a_tile + 3 * TileSize);
89
90 const int8x16_t b_tile01 = vld1q_s8(b_tile);
91 const int8x16_t b_tile23 = vld1q_s8(b_tile + TileSize);
92 const int8x16_t b_tile45 = vld1q_s8(b_tile + 2 * TileSize);
93 const int8x16_t b_tile67 = vld1q_s8(b_tile + 3 * TileSize);
94
95 acc0101 = vmmlaq_s32(acc0101, a_tile01, b_tile01);
96 acc2301 = vmmlaq_s32(acc2301, a_tile23, b_tile01);
97 acc4501 = vmmlaq_s32(acc4501, a_tile45, b_tile01);
98 acc6701 = vmmlaq_s32(acc6701, a_tile67, b_tile01);
99
100 acc0123 = vmmlaq_s32(acc0123, a_tile01, b_tile23);
101 acc2323 = vmmlaq_s32(acc2323, a_tile23, b_tile23);
102 acc4523 = vmmlaq_s32(acc4523, a_tile45, b_tile23);
103 acc6723 = vmmlaq_s32(acc6723, a_tile67, b_tile23);
104
105 acc0145 = vmmlaq_s32(acc0145, a_tile01, b_tile45);
106 acc2345 = vmmlaq_s32(acc2345, a_tile23, b_tile45);
107 acc4545 = vmmlaq_s32(acc4545, a_tile45, b_tile45);
108 acc6745 = vmmlaq_s32(acc6745, a_tile67, b_tile45);
109
110 acc0167 = vmmlaq_s32(acc0167, a_tile01, b_tile67);
111 acc2367 = vmmlaq_s32(acc2367, a_tile23, b_tile67);
112 acc4567 = vmmlaq_s32(acc4567, a_tile45, b_tile67);
113 acc6767 = vmmlaq_s32(acc6767, a_tile67, b_tile67);
114
115 a_tile += 4 * TileSize;
116 b_tile += 4 * TileSize;
117 }
118
119 store_acc_rowpair(acc0101, acc0123, acc0145, acc0167, c_ptr, ldc,
120 std::min(2, m));
121 store_acc_rowpair(acc2301, acc2323, acc2345, acc2367, c_ptr + 2 * ldc, ldc,
122 std::min(2, std::max(0, m - 2)));
123 store_acc_rowpair(acc4501, acc4523, acc4545, acc4567, c_ptr + 4 * ldc, ldc,
124 std::min(2, std::max(0, m - 4)));
125 store_acc_rowpair(acc6701, acc6723, acc6745, acc6767, c_ptr + 6 * ldc, ldc,
126 std::min(2, std::max(0, m - 6)));
127 }
128
129 FORCE_INLINE void gemm_micro_smmla_4x16_packed_a(
130 const int8_t* __restrict__ a_packed, const int8_t* __restrict__ b_packed,
131 int32_t* __restrict__ c_ptr, const int32_t m, const int32_t k_size,
132 const int64_t b_n_group_stride, const int64_t ldc) {
133 const int32_t m_rows_01 = std::min(2, m);
134 const int32_t m_rows_23 = std::min(2, std::max(0, m - 2));
135 const int32x4_t zero = vdupq_n_s32(0);
136
137 int32x4_t acc0101 = zero, acc0123 = zero, acc0145 = zero, acc0167 = zero;
138 int32x4_t acc2301 = zero, acc2323 = zero, acc2345 = zero, acc2367 = zero;
139 int32x4_t acc0189 = zero, acc011011 = zero, acc011213 = zero,
140 acc011415 = zero;
141 int32x4_t acc2389 = zero, acc231011 = zero, acc231213 = zero,
142 acc231415 = zero;
143
144 const int8_t* __restrict__ a_tile = a_packed;
145 // note: b packs 8 panels contiguously, so we need 2 b_tile ptrs
146 // for the 4x16 microkernel
147 const int8_t* __restrict__ b_tile0 = b_packed;
148 const int8_t* __restrict__ b_tile1 = b_packed + b_n_group_stride;
149
150 #pragma GCC unroll 8
151 for (int32_t k_idx = 0; k_idx < k_size; k_idx += K) {
152 const int8x16_t a_tile01 = vld1q_s8(a_tile);
153 const int8x16_t a_tile23 = vld1q_s8(a_tile + TileSize);
154 const int8x16_t b_tile01 = vld1q_s8(b_tile0);
155 const int8x16_t b_tile23 = vld1q_s8(b_tile0 + TileSize);
156 const int8x16_t b_tile45 = vld1q_s8(b_tile0 + 2 * TileSize);
157 const int8x16_t b_tile67 = vld1q_s8(b_tile0 + 3 * TileSize);
158 const int8x16_t b_tile89 = vld1q_s8(b_tile1);
159 const int8x16_t b_tile1011 = vld1q_s8(b_tile1 + TileSize);
160 const int8x16_t b_tile1213 = vld1q_s8(b_tile1 + 2 * TileSize);
161 const int8x16_t b_tile1415 = vld1q_s8(b_tile1 + 3 * TileSize);
162
163 acc0101 = vmmlaq_s32(acc0101, a_tile01, b_tile01);
164 acc2301 = vmmlaq_s32(acc2301, a_tile23, b_tile01);
165 acc0123 = vmmlaq_s32(acc0123, a_tile01, b_tile23);
166 acc2323 = vmmlaq_s32(acc2323, a_tile23, b_tile23);
167
168 acc0145 = vmmlaq_s32(acc0145, a_tile01, b_tile45);
169 acc2345 = vmmlaq_s32(acc2345, a_tile23, b_tile45);
170 acc0167 = vmmlaq_s32(acc0167, a_tile01, b_tile67);
171 acc2367 = vmmlaq_s32(acc2367, a_tile23, b_tile67);
172
173 acc0189 = vmmlaq_s32(acc0189, a_tile01, b_tile89);
174 acc2389 = vmmlaq_s32(acc2389, a_tile23, b_tile89);
175 acc011011 = vmmlaq_s32(acc011011, a_tile01, b_tile1011);
176 acc231011 = vmmlaq_s32(acc231011, a_tile23, b_tile1011);
177
178 acc011213 = vmmlaq_s32(acc011213, a_tile01, b_tile1213);
179 acc231213 = vmmlaq_s32(acc231213, a_tile23, b_tile1213);
180 acc011415 = vmmlaq_s32(acc011415, a_tile01, b_tile1415);
181 acc231415 = vmmlaq_s32(acc231415, a_tile23, b_tile1415);
182
183 a_tile += 2 * TileSize;
184 b_tile0 += 4 * TileSize;
185 b_tile1 += 4 * TileSize;
186 }
187
188 // rows 0-1, columns 0-7
189 store_acc_rowpair(acc0101, acc0123, acc0145, acc0167, c_ptr, ldc, m_rows_01);
190 // rows 0-1, columns 8-15
191 store_acc_rowpair(acc0189, acc011011, acc011213, acc011415, c_ptr + 8, ldc,
192 m_rows_01);
193 // rows 2-3, columns 0-7
194 store_acc_rowpair(acc2301, acc2323, acc2345, acc2367, c_ptr + 2 * ldc, ldc,
195 m_rows_23);
196 // rows 2-3, columns 8-15
197 store_acc_rowpair(acc2389, acc231011, acc231213, acc231415,
198 c_ptr + 2 * ldc + 8, ldc, m_rows_23);
199 }
200
201 } // namespace neon_smmla
202
203 template <typename scalar_t>
204 class MicroGemmINT8<cpu_utils::ISA::NEON, scalar_t> {
205 public:
206 static constexpr int32_t K = neon_smmla::K;
207 static constexpr int32_t Mr = 8;
208 static constexpr int32_t Nr = 8;
209 static constexpr int32_t NrGemv = 16;
210 static constexpr int32_t MaxMSize = 8;
211 static constexpr int32_t NSize = 32;
212 static constexpr int32_t WeightOCGroupSize = Nr;
213 static_assert(MaxMSize % Mr == 0);
214
215 static FORCE_INLINE void quantize_row(const scalar_t* input, int8_t* output,
216 float& scale, const int32_t size) {
217 TORCH_CHECK_EQ(size % K, 0);
218 float32x4_t max_vec = vdupq_n_f32(0.0f);
219
220 for (int32_t i = 0; i < size; i += K) {
221 const float32x4x2_t input_vec = neon_smmla::load_as_f32(input + i);
222 max_vec = vmaxq_f32(max_vec, vabsq_f32(input_vec.val[0]));
223 max_vec = vmaxq_f32(max_vec, vabsq_f32(input_vec.val[1]));
224 }
225
226 const float abs_max = std::max(vmaxvq_f32(max_vec), 1.0e-7f);
227 scale = abs_max / 127.0f;
228 const float32x4_t inv_scale_vec = vdupq_n_f32(127.0f / abs_max);
229
230 for (int32_t i = 0; i < size; i += K) {
231 const float32x4x2_t input_vec = neon_smmla::load_as_f32(input + i);
232 const int32x4_t output_low =
233 vcvtnq_s32_f32(vmulq_f32(input_vec.val[0], inv_scale_vec));
234 const int32x4_t output_high =
235 vcvtnq_s32_f32(vmulq_f32(input_vec.val[1], inv_scale_vec));
236 const int16x8_t output_s16 =
237 vcombine_s16(vqmovn_s32(output_low), vqmovn_s32(output_high));
238 vst1_s8(output + i, vqmovn_s16(output_s16));
239 }
240 }
241
242 // with current code, fusing this into the gemm micro kernel didn't move the
243 // needle
244 static FORCE_INLINE void dequantize_tile(
245 int32_t* input, float* output, const float* __restrict__ input_scales,
246 const float* __restrict__ weight_scales, const int32_t m, const int32_t n,
247 const int32_t stride) {
248 TORCH_CHECK_EQ(n % 4, 0);
249 for (int32_t m_idx = 0; m_idx < m; ++m_idx) {
250 const float32x4_t input_scale_vec = vdupq_n_f32(input_scales[m_idx]);
251 for (int32_t n_idx = 0; n_idx < n; n_idx += 4) {
252 const int32x4_t input_vec = vld1q_s32(input + m_idx * stride + n_idx);
253 const float32x4_t weight_scale_vec = vld1q_f32(weight_scales + n_idx);
254 const float32x4_t output_vec =
255 vmulq_f32(vcvtq_f32_s32(input_vec),
256 vmulq_f32(input_scale_vec, weight_scale_vec));
257 vst1q_f32(output + m_idx * stride + n_idx, output_vec);
258 }
259 }
260 }
261
262 // physical layout [
263 // M / (8 or 4); Mr is 8 or 4
264 // K / 8; K for smmla is 8
265 // 4, ; 4 row-pairs for each 8 rows
266 // 2, ; row-pair is 2 rows
267 // 4 ; 4 elements per row
268 // ]
269 static void pack_input_from_rows(const int8_t* const* __restrict__ rows,
270 int8_t* __restrict__ a_packed,
271 const int32_t m, const int32_t k) {
272 TORCH_CHECK(m > 0 && m <= MaxMSize);
273 TORCH_CHECK(k % K == 0);
274 const int8x8_t zero = vdup_n_s8(0);
275
276 for (int32_t row_base = 0; row_base < m; row_base += Mr) {
277 const int32_t panel_m = std::min(Mr, m - row_base);
278 const int8_t* const* panel_rows = rows + row_base;
279 int8_t* __restrict__ out = a_packed + row_base * k;
280
281 // fast path for full 8-row panels (fast path for 4-row panels didn't move
282 // the needle)
283 if (panel_m == Mr) {
284 const int8_t* __restrict__ row0 = panel_rows[0];
285 const int8_t* __restrict__ row1 = panel_rows[1];
286 const int8_t* __restrict__ row2 = panel_rows[2];
287 const int8_t* __restrict__ row3 = panel_rows[3];
288 const int8_t* __restrict__ row4 = panel_rows[4];
289 const int8_t* __restrict__ row5 = panel_rows[5];
290 const int8_t* __restrict__ row6 = panel_rows[6];
291 const int8_t* __restrict__ row7 = panel_rows[7];
292 int32_t k_idx = 0;
293 for (; k_idx + 2 * K <= k; k_idx += 2 * K) {
294 int8_t* __restrict__ block0 = out;
295 int8_t* __restrict__ block1 = out + 4 * neon_smmla::TileSize;
296
297 int8x16_t a0 = vld1q_s8(row0 + k_idx);
298 int8x16_t a1 = vld1q_s8(row1 + k_idx);
299 vst1q_s8(block0, vcombine_s8(vget_low_s8(a0), vget_low_s8(a1)));
300 vst1q_s8(block1, vcombine_s8(vget_high_s8(a0), vget_high_s8(a1)));
301
302 a0 = vld1q_s8(row2 + k_idx);
303 a1 = vld1q_s8(row3 + k_idx);
304 vst1q_s8(block0 + neon_smmla::TileSize,
305 vcombine_s8(vget_low_s8(a0), vget_low_s8(a1)));
306 vst1q_s8(block1 + neon_smmla::TileSize,
307 vcombine_s8(vget_high_s8(a0), vget_high_s8(a1)));
308
309 a0 = vld1q_s8(row4 + k_idx);
310 a1 = vld1q_s8(row5 + k_idx);
311 vst1q_s8(block0 + 2 * neon_smmla::TileSize,
312 vcombine_s8(vget_low_s8(a0), vget_low_s8(a1)));
313 vst1q_s8(block1 + 2 * neon_smmla::TileSize,
314 vcombine_s8(vget_high_s8(a0), vget_high_s8(a1)));
315
316 a0 = vld1q_s8(row6 + k_idx);
317 a1 = vld1q_s8(row7 + k_idx);
318 vst1q_s8(block0 + 3 * neon_smmla::TileSize,
319 vcombine_s8(vget_low_s8(a0), vget_low_s8(a1)));
320 vst1q_s8(block1 + 3 * neon_smmla::TileSize,
321 vcombine_s8(vget_high_s8(a0), vget_high_s8(a1)));
322
323 out += 8 * neon_smmla::TileSize;
324 }
325
326 for (; k_idx < k; k_idx += K) {
327 int8x8_t a0 = vld1_s8(row0 + k_idx);
328 int8x8_t a1 = vld1_s8(row1 + k_idx);
329 vst1q_s8(out, vcombine_s8(a0, a1));
330
331 a0 = vld1_s8(row2 + k_idx);
332 a1 = vld1_s8(row3 + k_idx);
333 vst1q_s8(out + neon_smmla::TileSize, vcombine_s8(a0, a1));
334
335 a0 = vld1_s8(row4 + k_idx);
336 a1 = vld1_s8(row5 + k_idx);
337 vst1q_s8(out + 2 * neon_smmla::TileSize, vcombine_s8(a0, a1));
338
339 a0 = vld1_s8(row6 + k_idx);
340 a1 = vld1_s8(row7 + k_idx);
341 vst1q_s8(out + 3 * neon_smmla::TileSize, vcombine_s8(a0, a1));
342
343 out += 4 * neon_smmla::TileSize;
344 }
345 continue;
346 }
347
348 const int32_t row_pairs = (panel_m <= 4) ? 2 : Mr / 2;
349 for (int32_t k_idx = 0; k_idx < k; k_idx += K) {
350 for (int32_t pair_idx = 0; pair_idx < row_pairs; ++pair_idx) {
351 const int32_t row_idx = pair_idx * 2;
352 const int8x8_t row0 =
353 (row_idx < panel_m) ? vld1_s8(panel_rows[row_idx] + k_idx) : zero;
354 const int8x8_t row1 = (row_idx + 1 < panel_m)
355 ? vld1_s8(panel_rows[row_idx + 1] + k_idx)
356 : zero;
357 vst1q_s8(out, vcombine_s8(row0, row1));
358 out += neon_smmla::TileSize;
359 }
360 }
361 }
362 }
363
364 // physical layout [
365 // N / 8; Nr is 8
366 // K / 8; K for smmla is 8
367 // 4, ; 4 col-pairs for each 8 cols
368 // 2, ; col-pair is 2 cols
369 // 4 ; 4 elements per col
370 // ]
371 static void pack_weight(const int8_t* __restrict__ weight,
372 int8_t* __restrict__ packed_weight,
373 const int32_t output_size, const int32_t input_size) {
374 TORCH_CHECK(output_size % NSize == 0);
375 TORCH_CHECK(input_size % K == 0);
376
377 for (int32_t o_idx = 0; o_idx < output_size; o_idx += Nr) {
378 int8_t* __restrict__ dst = packed_weight + o_idx * input_size;
379 for (int32_t k_idx = 0; k_idx < input_size; k_idx += K) {
380 for (int32_t pair_idx = 0; pair_idx < Nr;
381 pair_idx += neon_smmla::Cols) {
382 const int8_t* __restrict__ row0 =
383 weight + (o_idx + pair_idx) * input_size + k_idx;
384 const int8_t* __restrict__ row1 = row0 + input_size;
385 vst1q_s8(dst, vcombine_s8(vld1_s8(row0), vld1_s8(row1)));
386 dst += neon_smmla::TileSize;
387 }
388 }
389 }
390 }
391
392 void gemm(const int8_t* __restrict__ a_packed,
393 const int8_t* __restrict__ b_packed, int32_t* __restrict__ c,
394 const int32_t m, const int32_t k, const int64_t b_n_group_stride,
395 const int64_t ldc) const {
396 TORCH_CHECK(m > 0 && m <= MaxMSize);
397 TORCH_CHECK(k % K == 0);
398
399 for (int32_t n_idx = 0; n_idx < NSize; n_idx += NrGemv) {
400 const int8_t* __restrict__ b_panel = b_packed + n_idx * k;
401
402 for (int32_t row_base = 0; row_base < m; row_base += Mr) {
403 const int32_t panel_m = std::min(Mr, m - row_base);
404 const int8_t* __restrict__ a_panel = a_packed + row_base * k;
405 int32_t* __restrict__ c_panel = c + row_base * ldc + n_idx;
406
407 if (panel_m <= 4) {
408 neon_smmla::gemm_micro_smmla_4x16_packed_a(
409 a_panel, b_panel, c_panel, panel_m, k, b_n_group_stride, ldc);
410 } else {
411 neon_smmla::gemm_micro_smmla_8x8_packed_a(a_panel, b_panel, c_panel,
412 panel_m, k, ldc);
413 neon_smmla::gemm_micro_smmla_8x8_packed_a(
414 a_panel, b_panel + b_n_group_stride, c_panel + Nr, panel_m, k,
415 ldc);
416 }
417 }
418 }
419 }
420 };
421
422 } // namespace cpu_micro_gemm
423
424 #endif