[CPU][Perf] Accelerate unquantized MoE for AArch64 (#46353)

Signed-off-by: Fadi Arafeh <fadi.arafeh@arm.com>

Fadi Arafeh committed Jun 24, 2026 at 15:14 UTC 061043eacac0473769d86a1babb9adeb8402e833
15 files changed +672 -32
.buildkite/scripts/hardware_ci/run-cpu-test-arm.sh
+2 -2
@@ -8,7 +8,7 @@ set -ex
8 CORE_RANGE=${CORE_RANGE:-0-31}
9 OMP_CORE_RANGE=${OMP_CORE_RANGE:-0-31}
10
11 -export CMAKE_BUILD_PARALLEL_LEVEL=16
11 +export CMAKE_BUILD_PARALLEL_LEVEL=32
12
13 # Setup cleanup
14 remove_docker_container() {
@@ -37,7 +37,7 @@ function cpu_tests() {
37 pytest -x -v -s tests/kernels/test_onednn.py
38 pytest -x -v -s tests/kernels/attention/test_cpu_attn.py
39 pytest -x -v -s tests/kernels/core/test_cpu_activation.py
40 - pytest -x -v -s tests/kernels/moe/test_moe.py -k test_cpu_fused_moe_basic
40 + pytest -x -v -s tests/kernels/moe/test_cpu_fused_moe.py
41 pytest -x -v -s tests/kernels/mamba/cpu/test_cpu_gdn_ops.py"
42
43 # skip tests requiring model downloads if HF_TOKEN is not set
benchmarks/kernels/cpu/benchmark_cpu_fused_moe.py
+7 -8
@@ -7,6 +7,7 @@ import time
7 import numpy as np
8 import torch
9
10 +from vllm.platforms import CpuArchEnum, current_platform
11 from vllm.utils.argparse_utils import FlexibleArgumentParser
12 from vllm.utils.torch_utils import set_random_seed
13
@@ -14,17 +15,15 @@ from vllm.utils.torch_utils import set_random_seed
15 try:
16 from vllm._custom_ops import cpu_fused_moe, cpu_prepack_moe_weight
17 except (ImportError, AttributeError) as e:
17 - print("ERROR: CPU fused MoE operations are not available on this platform.")
18 - print("This benchmark requires x86 CPU with proper vLLM CPU extensions compiled.")
19 - print(
20 - "The cpu_fused_moe kernel is typically available on Linux x86_64 "
21 - "with AVX2/AVX512."
22 - )
18 print(f"Import error: {e}")
19 sys.exit(1)
20
21 # ISA selection following test_cpu_fused_moe.py pattern
27 -ISA_CHOICES = ["amx", "vec"] if torch.cpu._is_amx_tile_supported() else ["vec"]
22 +ISA_CHOICES = ["vec"]
23 +if torch.cpu._is_amx_tile_supported():
24 + ISA_CHOICES.append("amx")
25 +if current_platform.get_cpu_architecture() == CpuArchEnum.ARM:
26 + ISA_CHOICES.append("neon")
27
28
29 @torch.inference_mode()
@@ -145,7 +144,7 @@ if __name__ == "__main__":
144 "--isa",
145 type=str,
146 choices=ISA_CHOICES,
148 - default=ISA_CHOICES[0],
147 + default="vec",
148 help=f"ISA to use (available: {ISA_CHOICES})",
149 )
150 parser.add_argument("--seed", type=int, default=0)
cmake/cpu_extension.cmake
+1
@@ -423,6 +423,7 @@ if (ASIMD_FOUND AND NOT APPLE_SILICON_FOUND)
423 set(VLLM_EXT_SRC
424 "csrc/cpu/shm.cpp"
425 "csrc/cpu/activation_lut_bf16.cpp"
426 + "csrc/cpu/cpu_fused_moe.cpp"
427 ${VLLM_EXT_SRC})
428 endif()
429
csrc/cpu/cpu_fused_moe.cpp
+76 -14
@@ -1,5 +1,3 @@
1 -#include <sleef.h>
2 -
1 #include "cpu/cpu_types.hpp"
2 #include "cpu/utils.hpp"
3 #include "cpu/micro_gemm/cpu_micro_gemm_vec.hpp"
@@ -16,6 +14,18 @@
14 #define AMX_DISPATCH(...) case cpu_utils::ISA::AMX:
15 #endif
16
17 +#if defined(ARM_BF16_SUPPORT)
18 + #include "cpu/micro_gemm/cpu_micro_gemm_neon.hpp"
19 + #define NEON_DISPATCH(...) \
20 + case cpu_utils::ISA::NEON: { \
21 + using gemm_t = \
22 + cpu_micro_gemm::MicroGemm<cpu_utils::ISA::NEON, scalar_t>; \
23 + return __VA_ARGS__(); \
24 + }
25 +#else
26 + #define NEON_DISPATCH(...) case cpu_utils::ISA::NEON:
27 +#endif
28 +
29 #define CPU_ISA_DISPATCH_IMPL(ISA_TYPE, ...) \
30 [&] { \
31 switch (ISA_TYPE) { \
@@ -25,6 +35,7 @@
35 cpu_micro_gemm::MicroGemm<cpu_utils::ISA::VEC, scalar_t>; \
36 return __VA_ARGS__(); \
37 } \
38 + NEON_DISPATCH(__VA_ARGS__) \
39 default: { \
40 TORCH_CHECK(false, "Invalid CPU ISA type."); \
41 } \
@@ -59,10 +70,12 @@ void swigluoai_and_mul(float* __restrict__ input, scalar_t* __restrict__ output,
70 const int32_t input_stride,
71 const int32_t output_stride) {
72 using scalar_vec_t = typename cpu_utils::VecTypeTrait<scalar_t>::vec_t;
73 +#if !defined(__aarch64__)
74 // For GPT-OSS interleaved gate-up weights
75 alignas(64) static int32_t index[16] = {0, 2, 4, 6, 8, 10, 12, 14,
76 16, 18, 20, 22, 24, 26, 28, 30};
77 vec_op::INT32Vec16 index_vec(index);
78 +#endif
79 vec_op::FP32Vec16 gate_up_max_vec(7.0);
80 vec_op::FP32Vec16 up_min_vec(-7.0);
81 vec_op::FP32Vec16 alpha_vec(1.702);
@@ -72,8 +85,15 @@ void swigluoai_and_mul(float* __restrict__ input, scalar_t* __restrict__ output,
85
86 for (int32_t m = 0; m < m_size; ++m) {
87 for (int32_t n = 0; n < n_size; n += 32) {
88 + // Note: AdvSIMD does not support gather loads
89 +#if defined(__aarch64__)
90 + vec_op::FP32Vec16 gate_vec(vec_op::uninit);
91 + vec_op::FP32Vec16 up_vec(vec_op::uninit);
92 + vec_op::FP32Vec16::load_even_odd(input + n, gate_vec, up_vec);
93 +#else
94 vec_op::FP32Vec16 gate_vec(input + n, index_vec);
95 vec_op::FP32Vec16 up_vec(input + n + 1, index_vec);
96 +#endif
97 gate_vec = gate_vec.min(gate_up_max_vec);
98 up_vec = up_vec.clamp(up_min_vec, gate_up_max_vec);
99 auto sigmoid_vec = one_vec / (one_vec + fast_exp(-gate_vec * alpha_vec));
@@ -174,7 +194,7 @@ void gelu_tanh_and_mul(float* __restrict__ input, scalar_t* __restrict__ output,
194 auto inner_vec = w1_vec * (gate_vec + w3_vec * gate_pow3_vec);
195 // Note: can't use fast_exp form because diffusiongemma will generate
196 // wrong results
177 - vec_op::FP32Vec16 tanh_vec(Sleef_tanhf16_u10(inner_vec.reg));
197 + auto tanh_vec = inner_vec.tanh();
198 auto gelu_tanh = gate_vec * w2_vec * (one_vec + tanh_vec);
199 auto gated_output_fp32 = up_vec * gelu_tanh;
200 scalar_vec_t gated_output = scalar_vec_t(gated_output_fp32);
@@ -240,6 +260,7 @@ void fused_moe_impl(scalar_t* __restrict__ output, scalar_t* __restrict__ input,
260 constexpr int32_t gemm_n_tile_size = gemm_t::NSize;
261 constexpr int32_t gemm_m_tile_size = gemm_t::MaxMSize;
262 constexpr int32_t min_w13_n_tile_size = 2 * gemm_n_tile_size;
263 + constexpr bool pack_a = gemm_t::PackA;
264 static_assert(gemm_n_tile_size % 16 == 0);
265
266 TORCH_CHECK_EQ(output_size_13 % min_w13_n_tile_size, 0);
@@ -266,12 +287,18 @@ void fused_moe_impl(scalar_t* __restrict__ output, scalar_t* __restrict__ input,
287
288 const int32_t w2_input_tile_size = cpu_utils::round_up<64>(
289 gemm_m_tile_size * input_size_2 * sizeof(scalar_t));
290 + // use w2 input buffer only when we need to pack input
291 + const int32_t w2_input_buffer_size =
292 + pack_a ? cpu_utils::round_up<64>(gemm_m_tile_size * input_size_2 *
293 + sizeof(scalar_t))
294 + : 0;
295
296 const int32_t w2_n_tile_size = [&]() {
297 const int64_t cache_size = cpu_utils::get_available_l2_size();
272 - // input tile + weight
298 + // input tile + optional packed input + weight
299 const int32_t n_size_cache_limit =
274 - (cache_size - w2_input_tile_size) / (input_size_2 * sizeof(scalar_t));
300 + (cache_size - (pack_a ? w2_input_buffer_size : w2_input_tile_size)) /
301 + (input_size_2 * sizeof(scalar_t));
302 const int32_t n_size_thread_limit =
303 output_size_2 / std::max(1, thread_num / topk_num);
304 const int32_t n_size = cpu_utils::round_down<gemm_n_tile_size>(
@@ -324,6 +351,9 @@ void fused_moe_impl(scalar_t* __restrict__ output, scalar_t* __restrict__ input,
351 const int32_t w13_output_buffer_offset = w13_thread_buffer_offset;
352 w13_thread_buffer_offset += w13_output_buffer_size;
353
354 + const int32_t w2_input_buffer_offset = w13_thread_buffer_offset;
355 + w13_thread_buffer_offset += w2_input_buffer_size;
356 +
357 // Weighted sum thread buffer
358 const int32_t ws_output_buffer_size =
359 cpu_utils::round_up<64>(output_size_2 * sizeof(float));
@@ -403,7 +433,8 @@ void fused_moe_impl(scalar_t* __restrict__ output, scalar_t* __restrict__ input,
433 gemm_t gemm;
434
435 const int32_t input_size_13_bytes = input_size_13 * sizeof(scalar_t);
406 - const int32_t w13_n_group_stride = 16 * input_size_13;
436 + const int32_t w13_n_group_stride =
437 + gemm_t::WeightOCGroupSize * input_size_13;
438 const int32_t w13_n_tile_stride = gemm_n_tile_size * input_size_13;
439
440 for (;;) {
@@ -466,8 +497,23 @@ void fused_moe_impl(scalar_t* __restrict__ output, scalar_t* __restrict__ input,
497 token_idx += gemm_m_tile_size) {
498 const int32_t actual_token_num =
499 std::min(gemm_m_tile_size, curr_token_num - token_idx);
469 - // copy inputs
470 - {
500 +
501 + scalar_t* __restrict__ curr_w13_gemm_input_buffer = nullptr;
502 + if constexpr (pack_a) {
503 + // copy and pack inputs
504 + curr_w13_gemm_input_buffer = w13_input_buffer;
505 + const scalar_t* w13_input_rows[gemm_m_tile_size];
506 + for (int32_t i = 0; i < actual_token_num; ++i) {
507 + w13_input_rows[i] =
508 + input + curr_expand_token_id_buffer[i] * input_size_13;
509 + }
510 + gemm_t::pack_input_from_rows(w13_input_rows,
511 + curr_w13_gemm_input_buffer,
512 + actual_token_num, input_size_13);
513 + curr_expand_token_id_buffer += actual_token_num;
514 + } else {
515 + // copy inputs
516 + curr_w13_gemm_input_buffer = curr_w13_input_buffer;
517 scalar_t* __restrict__ curr_w13_input_buffer_iter =
518 curr_w13_input_buffer;
519 for (int32_t i = 0; i < actual_token_num; ++i) {
@@ -499,14 +545,12 @@ void fused_moe_impl(scalar_t* __restrict__ output, scalar_t* __restrict__ input,
545 scalar_t* __restrict__ w13_weight_ptr_1_iter = w13_weight_ptr_1;
546 scalar_t* __restrict__ w13_bias_ptr_0_iter = w13_bias_ptr_0;
547 scalar_t* __restrict__ w13_bias_ptr_1_iter = w13_bias_ptr_1;
502 - scalar_t* __restrict__ curr_w13_input_buffer_iter =
503 - curr_w13_input_buffer;
548 float* __restrict__ w13_output_buffer_0_iter = w13_output_buffer;
549 float* __restrict__ w13_output_buffer_1_iter =
550 w13_output_buffer + actual_n_tile_size / 2;
551 for (int32_t i = 0; i < actual_n_tile_size;
552 i += min_w13_n_tile_size) {
509 - gemm.gemm(curr_w13_input_buffer_iter, w13_weight_ptr_0_iter,
553 + gemm.gemm(curr_w13_gemm_input_buffer, w13_weight_ptr_0_iter,
554 w13_output_buffer_0_iter, actual_token_num,
555 input_size_13, input_size_13, w13_n_group_stride,
556 actual_n_tile_size, false);
@@ -519,7 +563,7 @@ void fused_moe_impl(scalar_t* __restrict__ output, scalar_t* __restrict__ input,
563 w13_bias_ptr_0_iter += gemm_n_tile_size;
564 }
565
522 - gemm.gemm(curr_w13_input_buffer_iter, w13_weight_ptr_1_iter,
566 + gemm.gemm(curr_w13_gemm_input_buffer, w13_weight_ptr_1_iter,
567 w13_output_buffer_1_iter, actual_token_num,
568 input_size_13, input_size_13, w13_n_group_stride,
569 actual_n_tile_size, false);
@@ -572,7 +616,8 @@ void fused_moe_impl(scalar_t* __restrict__ output, scalar_t* __restrict__ input,
616 gemm_t gemm;
617
618 const int32_t w2_n_tile_stride = gemm_n_tile_size * input_size_2;
575 - const int32_t w2_n_group_stride = 16 * input_size_2;
619 + const int32_t w2_n_group_stride =
620 + gemm_t::WeightOCGroupSize * input_size_2;
621
622 for (;;) {
623 int32_t task_id = counter_ptr->acquire_counter();
@@ -611,13 +656,30 @@ void fused_moe_impl(scalar_t* __restrict__ output, scalar_t* __restrict__ input,
656 token_idx += gemm_m_tile_size) {
657 const int32_t actual_token_num =
658 std::min(gemm_m_tile_size, curr_token_num - token_idx);
659 + scalar_t* __restrict__ curr_w2_gemm_input_buffer =
660 + curr_w13_gemm_output_buffer;
661 + if constexpr (pack_a) {
662 + uint8_t* __restrict__ thread_buffer =
663 + thread_buffer_start + thread_id * w13_thread_buffer_offset;
664 + scalar_t* __restrict__ w2_input_buffer =
665 + reinterpret_cast<scalar_t*>(thread_buffer +
666 + w2_input_buffer_offset);
667 + curr_w2_gemm_input_buffer = w2_input_buffer;
668 + const scalar_t* w2_input_rows[gemm_m_tile_size];
669 + for (int32_t i = 0; i < actual_token_num; ++i) {
670 + w2_input_rows[i] = curr_w13_gemm_output_buffer + i * input_size_2;
671 + }
672 + gemm_t::pack_input_from_rows(w2_input_rows,
673 + curr_w2_gemm_input_buffer,
674 + actual_token_num, input_size_2);
675 + }
676
677 scalar_t* __restrict__ w2_weight_ptr_iter = w2_weight_ptr;
678 scalar_t* __restrict__ w2_bias_ptr_iter = w2_bias_ptr;
679 float* __restrict__ curr_w2_gemm_output_buffer_iter =
680 curr_w2_gemm_output_buffer;
681 for (int32_t i = 0; i < actual_n_tile_size; i += gemm_n_tile_size) {
620 - gemm.gemm(curr_w13_gemm_output_buffer, w2_weight_ptr_iter,
682 + gemm.gemm(curr_w2_gemm_input_buffer, w2_weight_ptr_iter,
683 curr_w2_gemm_output_buffer_iter, actual_token_num,
684 input_size_2, input_size_2, w2_n_group_stride,
685 output_size_2, false);
csrc/cpu/cpu_types_arm.hpp
+30 -1
@@ -497,6 +497,26 @@ struct FP32Vec16 : public VectorizedRegWrapper<FP32Vec16, 4, float> {
497 reg.val[3] = Vectorized<float>(vcvt_f32_f16(vget_high_f16(v.reg.val[1])));
498 };
499
500 + static FORCE_INLINE void load_even_odd(const float* ptr, FP32Vec16& even,
501 + FP32Vec16& odd) noexcept {
502 + const float32x4x2_t x01 = vuzpq_f32(vld1q_f32(ptr), vld1q_f32(ptr + 4));
503 + const float32x4x2_t x23 =
504 + vuzpq_f32(vld1q_f32(ptr + 8), vld1q_f32(ptr + 12));
505 + const float32x4x2_t x45 =
506 + vuzpq_f32(vld1q_f32(ptr + 16), vld1q_f32(ptr + 20));
507 + const float32x4x2_t x67 =
508 + vuzpq_f32(vld1q_f32(ptr + 24), vld1q_f32(ptr + 28));
509 +
510 + even.reg.val[0] = VectorizedT(x01.val[0]);
511 + even.reg.val[1] = VectorizedT(x23.val[0]);
512 + even.reg.val[2] = VectorizedT(x45.val[0]);
513 + even.reg.val[3] = VectorizedT(x67.val[0]);
514 + odd.reg.val[0] = VectorizedT(x01.val[1]);
515 + odd.reg.val[1] = VectorizedT(x23.val[1]);
516 + odd.reg.val[2] = VectorizedT(x45.val[1]);
517 + odd.reg.val[3] = VectorizedT(x67.val[1]);
518 + }
519 +
520 FORCE_INLINE FP32Vec16 operator+(const FP32Vec16& b) const noexcept {
521 FP32Vec16 r(uninit);
522 r.reg.val[0] = reg.val[0] + b.reg.val[0];
@@ -515,6 +535,15 @@ struct FP32Vec16 : public VectorizedRegWrapper<FP32Vec16, 4, float> {
535 return r;
536 }
537
538 + FORCE_INLINE FP32Vec16 operator-() const noexcept {
539 + FP32Vec16 r(uninit);
540 + r.reg.val[0] = reg.val[0].neg();
541 + r.reg.val[1] = reg.val[1].neg();
542 + r.reg.val[2] = reg.val[2].neg();
543 + r.reg.val[3] = reg.val[3].neg();
544 + return r;
545 + }
546 +
547 FORCE_INLINE FP32Vec16 operator*(const FP32Vec16& b) const noexcept {
548 FP32Vec16 r(uninit);
549 r.reg.val[0] = reg.val[0] * b.reg.val[0];
@@ -933,4 +962,4 @@ inline void storeFP32<c10::BFloat16>(float v, c10::BFloat16* ptr) {
962
963 inline void prefetch(const void* addr) { __builtin_prefetch(addr, 0, 1); };
964
936 -}; // namespace vec_op
\ No newline at end of file
965 +}; // namespace vec_op
csrc/cpu/cpu_types_scalar.hpp
+7
@@ -363,6 +363,13 @@ struct FP32Vec16 : public Vec<FP32Vec16> {
363 return FP32Vec16(ret);
364 }
365
366 + FP32Vec16 tanh() const {
367 + f32x16_t ret;
368 + unroll_loop<int, VEC_ELEM_NUM>(
369 + [&ret, this](int i) { ret.val[i] = std::tanh(reg.val[i]); });
370 + return FP32Vec16(ret);
371 + }
372 +
373 float reduce_sum() const {
374 float result = 0.0f;
375 unroll_loop<int, VEC_ELEM_NUM>(
csrc/cpu/cpu_types_x86.hpp
+9
@@ -3,6 +3,7 @@
3 #define CPU_TYPES_X86_HPP
4
5 #include <immintrin.h>
6 +#include <sleef.h>
7 #include <torch/all.h>
8
9 #ifndef __AVX2__
@@ -592,6 +593,8 @@ struct FP32Vec16 : public Vec<FP32Vec16> {
593
594 FP32Vec16 abs() const { return FP32Vec16(_mm512_abs_ps(reg)); }
595
596 + FP32Vec16 tanh() const { return FP32Vec16(Sleef_tanhf16_u10(reg)); }
597 +
598 float reduce_sum() const { return _mm512_reduce_add_ps(reg); }
599
600 float reduce_max() const { return _mm512_reduce_max_ps(reg); }
@@ -789,6 +792,12 @@ struct FP32Vec16 : public Vec<FP32Vec16> {
792 _mm256_andnot_ps(sign_mask, reg_high));
793 }
794
795 + FP32Vec16 tanh() const {
796 + FP32Vec8 low(reg_low);
797 + FP32Vec8 high(reg_high);
798 + return FP32Vec16(low.tanh().reg, high.tanh().reg);
799 + }
800 +
801 FP32Vec16 min(const FP32Vec16& b) const {
802 return FP32Vec16(_mm256_min_ps(reg_low, b.reg_low),
803 _mm256_min_ps(reg_high, b.reg_high));
csrc/cpu/micro_gemm/cpu_micro_gemm_amx.hpp
+2
@@ -213,6 +213,8 @@ class MicroGemm<cpu_utils::ISA::AMX, scalar_t> {
213 public:
214 static constexpr int32_t MaxMSize = 32;
215 static constexpr int32_t NSize = 32;
216 + static constexpr int32_t WeightOCGroupSize = 16;
217 + static constexpr bool PackA = false;
218
219 public:
220 MicroGemm() : curr_m_(-1) {
csrc/cpu/micro_gemm/cpu_micro_gemm_impl.hpp
+3
@@ -21,6 +21,9 @@ class MicroGemm {
21 public:
22 static constexpr int32_t MaxMSize = 16;
23 static constexpr int32_t NSize = 16;
24 + static constexpr int32_t WeightOCGroupSize = 16;
25 + // callers must pack A matrix before GEMM
26 + static constexpr bool PackA = false;
27
28 public:
29 void gemm(DEFINE_CPU_MICRO_GEMM_PARAMS) {
csrc/cpu/micro_gemm/cpu_micro_gemm_neon.hpp new
+503
@@ -0,0 +1,503 @@
1 +#ifndef CPU_MICRO_GEMM_NEON_HPP
2 +#define CPU_MICRO_GEMM_NEON_HPP
3 +
4 +#include <algorithm>
5 +#include <cstdint>
6 +
7 +#include "cpu/micro_gemm/cpu_micro_gemm_impl.hpp"
8 +
9 +#include <arm_bf16.h>
10 +#include <arm_neon.h>
11 +
12 +namespace cpu_micro_gemm {
13 +
14 +namespace {
15 +
16 +constexpr int32_t K = 4;
17 +constexpr int32_t Cols = 2;
18 +constexpr int32_t TileSize = K * Cols;
19 +constexpr int32_t Mr = 8;
20 +constexpr int32_t Nr = 8;
21 +constexpr int32_t Nr_gemv = 16;
22 +
23 +// a = [a0, a1, a2, a3], b = [b0, b1, b2, b3] -> [a0, a1, b0, b1]
24 +FORCE_INLINE float32x4_t zip1_f32x4(const float32x4_t a, const float32x4_t b) {
25 + return vreinterpretq_f32_f64(
26 + vzip1q_f64(vreinterpretq_f64_f32(a), vreinterpretq_f64_f32(b)));
27 +}
28 +
29 +// a = [a0, a1, a2, a3], b = [b0, b1, b2, b3] -> [a2, a3, b2, b3]
30 +FORCE_INLINE float32x4_t zip2_f32x4(const float32x4_t a, const float32x4_t b) {
31 + return vreinterpretq_f32_f64(
32 + vzip2q_f64(vreinterpretq_f64_f32(a), vreinterpretq_f64_f32(b)));
33 +}
34 +
35 +FORCE_INLINE void init_acc_rowpair(float32x4_t& acc01, float32x4_t& acc23,
36 + float32x4_t& acc45, float32x4_t& acc67,
37 + const float* __restrict__ c_ptr,
38 + const int64_t ldc, const int32_t m_rows,
39 + const bool accum_c) {
40 + if (!accum_c || m_rows == 0) {
41 + acc01 = vdupq_n_f32(0.0f);
42 + acc23 = vdupq_n_f32(0.0f);
43 + acc45 = vdupq_n_f32(0.0f);
44 + acc67 = vdupq_n_f32(0.0f);
45 + return;
46 + }
47 +
48 + const float32x4_t row0_0123 = vld1q_f32(c_ptr);
49 + const float32x4_t row0_4567 = vld1q_f32(c_ptr + 4);
50 + const float32x4_t row1_0123 =
51 + (m_rows == 2) ? vld1q_f32(c_ptr + ldc) : vdupq_n_f32(0.0f);
52 + const float32x4_t row1_4567 =
53 + (m_rows == 2) ? vld1q_f32(c_ptr + ldc + 4) : vdupq_n_f32(0.0f);
54 +
55 + acc01 = zip1_f32x4(row0_0123, row1_0123);
56 + acc23 = zip2_f32x4(row0_0123, row1_0123);
57 + acc45 = zip1_f32x4(row0_4567, row1_4567);
58 + acc67 = zip2_f32x4(row0_4567, row1_4567);
59 +}
60 +
61 +FORCE_INLINE void store_acc_rowpair(const float32x4_t acc01,
62 + const float32x4_t acc23,
63 + const float32x4_t acc45,
64 + const float32x4_t acc67,
65 + float* __restrict__ c_ptr,
66 + const int64_t ldc, const int32_t m_rows) {
67 + if (m_rows == 0) {
68 + return;
69 + }
70 +
71 + vst1q_f32(c_ptr, zip1_f32x4(acc01, acc23));
72 + vst1q_f32(c_ptr + 4, zip1_f32x4(acc45, acc67));
73 +
74 + if (m_rows == 2) {
75 + vst1q_f32(c_ptr + ldc, zip2_f32x4(acc01, acc23));
76 + vst1q_f32(c_ptr + ldc + 4, zip2_f32x4(acc45, acc67));
77 + }
78 +}
79 +
80 +FORCE_INLINE void gemm_micro_bfmmla_8x8_packed_a(
81 + const bfloat16_t* __restrict__ a_packed,
82 + const bfloat16_t* __restrict__ b_packed, float* __restrict__ c_ptr,
83 + const int32_t m, const int32_t k_size, const int64_t ldc,
84 + const bool accum_c) {
85 + float32x4_t acc0101, acc0123, acc0145, acc0167;
86 + float32x4_t acc2301, acc2323, acc2345, acc2367;
87 + float32x4_t acc4501, acc4523, acc4545, acc4567;
88 + float32x4_t acc6701, acc6723, acc6745, acc6767;
89 +
90 + init_acc_rowpair(acc0101, acc0123, acc0145, acc0167, c_ptr, ldc,
91 + std::min(2, m), accum_c);
92 + init_acc_rowpair(acc2301, acc2323, acc2345, acc2367, c_ptr + 2 * ldc, ldc,
93 + std::min(2, std::max(0, m - 2)), accum_c);
94 + init_acc_rowpair(acc4501, acc4523, acc4545, acc4567, c_ptr + 4 * ldc, ldc,
95 + std::min(2, std::max(0, m - 4)), accum_c);
96 + init_acc_rowpair(acc6701, acc6723, acc6745, acc6767, c_ptr + 6 * ldc, ldc,
97 + std::min(2, std::max(0, m - 6)), accum_c);
98 +
99 + const bfloat16_t* __restrict__ a_tile = a_packed;
100 + const bfloat16_t* __restrict__ b_tile = b_packed;
101 +
102 +#pragma GCC unroll 8
103 + for (int32_t k_idx = 0; k_idx < k_size; k_idx += K) {
104 + const bfloat16x8_t a_tile01 = vld1q_bf16(a_tile);
105 + const bfloat16x8_t a_tile23 = vld1q_bf16(a_tile + TileSize);
106 + const bfloat16x8_t a_tile45 = vld1q_bf16(a_tile + 2 * TileSize);
107 + const bfloat16x8_t a_tile67 = vld1q_bf16(a_tile + 3 * TileSize);
108 +
109 + const bfloat16x8_t b_tile01 = vld1q_bf16(b_tile);
110 + const bfloat16x8_t b_tile23 = vld1q_bf16(b_tile + TileSize);
111 + const bfloat16x8_t b_tile45 = vld1q_bf16(b_tile + 2 * TileSize);
112 + const bfloat16x8_t b_tile67 = vld1q_bf16(b_tile + 3 * TileSize);
113 +
114 + acc0101 = vbfmmlaq_f32(acc0101, a_tile01, b_tile01);
115 + acc2301 = vbfmmlaq_f32(acc2301, a_tile23, b_tile01);
116 + acc4501 = vbfmmlaq_f32(acc4501, a_tile45, b_tile01);
117 + acc6701 = vbfmmlaq_f32(acc6701, a_tile67, b_tile01);
118 +
119 + acc0123 = vbfmmlaq_f32(acc0123, a_tile01, b_tile23);
120 + acc2323 = vbfmmlaq_f32(acc2323, a_tile23, b_tile23);
121 + acc4523 = vbfmmlaq_f32(acc4523, a_tile45, b_tile23);
122 + acc6723 = vbfmmlaq_f32(acc6723, a_tile67, b_tile23);
123 +
124 + acc0145 = vbfmmlaq_f32(acc0145, a_tile01, b_tile45);
125 + acc2345 = vbfmmlaq_f32(acc2345, a_tile23, b_tile45);
126 + acc4545 = vbfmmlaq_f32(acc4545, a_tile45, b_tile45);
127 + acc6745 = vbfmmlaq_f32(acc6745, a_tile67, b_tile45);
128 +
129 + acc0167 = vbfmmlaq_f32(acc0167, a_tile01, b_tile67);
130 + acc2367 = vbfmmlaq_f32(acc2367, a_tile23, b_tile67);
131 + acc4567 = vbfmmlaq_f32(acc4567, a_tile45, b_tile67);
132 + acc6767 = vbfmmlaq_f32(acc6767, a_tile67, b_tile67);
133 +
134 + a_tile += 4 * TileSize;
135 + b_tile += Nr * K;
136 + }
137 +
138 + store_acc_rowpair(acc0101, acc0123, acc0145, acc0167, c_ptr, ldc,
139 + std::min(2, m));
140 + store_acc_rowpair(acc2301, acc2323, acc2345, acc2367, c_ptr + 2 * ldc, ldc,
141 + std::min(2, std::max(0, m - 2)));
142 + store_acc_rowpair(acc4501, acc4523, acc4545, acc4567, c_ptr + 4 * ldc, ldc,
143 + std::min(2, std::max(0, m - 4)));
144 + store_acc_rowpair(acc6701, acc6723, acc6745, acc6767, c_ptr + 6 * ldc, ldc,
145 + std::min(2, std::max(0, m - 6)));
146 +}
147 +
148 +FORCE_INLINE void gemm_micro_bfmmla_4x16_packed_a(
149 + const bfloat16_t* __restrict__ a_packed,
150 + const bfloat16_t* __restrict__ b_packed, float* __restrict__ c_ptr,
151 + const int32_t m, const int32_t k_size, const int64_t b_n_group_stride,
152 + const int64_t ldc, const bool accum_c) {
153 + const int32_t m_rows_01 = std::min(2, m);
154 + const int32_t m_rows_23 = std::min(2, std::max(0, m - 2));
155 +
156 + float32x4_t acc0101, acc0123, acc0145, acc0167;
157 + float32x4_t acc2301, acc2323, acc2345, acc2367;
158 + float32x4_t acc0189, acc011011, acc011213, acc011415;
159 + float32x4_t acc2389, acc231011, acc231213, acc231415;
160 +
161 + init_acc_rowpair(acc0101, acc0123, acc0145, acc0167, c_ptr, ldc, m_rows_01,
162 + accum_c);
163 + init_acc_rowpair(acc2301, acc2323, acc2345, acc2367, c_ptr + 2 * ldc, ldc,
164 + m_rows_23, accum_c);
165 + init_acc_rowpair(acc0189, acc011011, acc011213, acc011415, c_ptr + 8, ldc,
166 + m_rows_01, accum_c);
167 + init_acc_rowpair(acc2389, acc231011, acc231213, acc231415,
168 + c_ptr + 2 * ldc + 8, ldc, m_rows_23, accum_c);
169 +
170 + const bfloat16_t* __restrict__ a_tile = a_packed;
171 + const bfloat16_t* __restrict__ b_tile0 = b_packed;
172 + const bfloat16_t* __restrict__ b_tile1 = b_packed + b_n_group_stride;
173 +
174 +#pragma GCC unroll 8
175 + for (int32_t k_idx = 0; k_idx < k_size; k_idx += K) {
176 + const bfloat16x8_t a_tile01 = vld1q_bf16(a_tile);
177 + const bfloat16x8_t a_tile23 = vld1q_bf16(a_tile + TileSize);
178 + const bfloat16x8_t b_tile01 = vld1q_bf16(b_tile0);
179 + const bfloat16x8_t b_tile23 = vld1q_bf16(b_tile0 + TileSize);
180 + const bfloat16x8_t b_tile45 = vld1q_bf16(b_tile0 + 2 * TileSize);
181 + const bfloat16x8_t b_tile67 = vld1q_bf16(b_tile0 + 3 * TileSize);
182 + const bfloat16x8_t b_tile89 = vld1q_bf16(b_tile1);
183 + const bfloat16x8_t b_tile1011 = vld1q_bf16(b_tile1 + TileSize);
184 + const bfloat16x8_t b_tile1213 = vld1q_bf16(b_tile1 + 2 * TileSize);
185 + const bfloat16x8_t b_tile1415 = vld1q_bf16(b_tile1 + 3 * TileSize);
186 +
187 + acc0101 = vbfmmlaq_f32(acc0101, a_tile01, b_tile01);
188 + acc2301 = vbfmmlaq_f32(acc2301, a_tile23, b_tile01);
189 + acc0123 = vbfmmlaq_f32(acc0123, a_tile01, b_tile23);
190 + acc2323 = vbfmmlaq_f32(acc2323, a_tile23, b_tile23);
191 +
192 + acc0145 = vbfmmlaq_f32(acc0145, a_tile01, b_tile45);
193 + acc2345 = vbfmmlaq_f32(acc2345, a_tile23, b_tile45);
194 + acc0167 = vbfmmlaq_f32(acc0167, a_tile01, b_tile67);
195 + acc2367 = vbfmmlaq_f32(acc2367, a_tile23, b_tile67);
196 +
197 + acc0189 = vbfmmlaq_f32(acc0189, a_tile01, b_tile89);
198 + acc2389 = vbfmmlaq_f32(acc2389, a_tile23, b_tile89);
199 + acc011011 = vbfmmlaq_f32(acc011011, a_tile01, b_tile1011);
200 + acc231011 = vbfmmlaq_f32(acc231011, a_tile23, b_tile1011);
201 +
202 + acc011213 = vbfmmlaq_f32(acc011213, a_tile01, b_tile1213);
203 + acc231213 = vbfmmlaq_f32(acc231213, a_tile23, b_tile1213);
204 + acc011415 = vbfmmlaq_f32(acc011415, a_tile01, b_tile1415);
205 + acc231415 = vbfmmlaq_f32(acc231415, a_tile23, b_tile1415);
206 +
207 + a_tile += 2 * TileSize;
208 + b_tile0 += Nr * K;
209 + b_tile1 += Nr * K;
210 + }
211 +
212 + store_acc_rowpair(acc0101, acc0123, acc0145, acc0167, c_ptr, ldc, m_rows_01);
213 + store_acc_rowpair(acc2301, acc2323, acc2345, acc2367, c_ptr + 2 * ldc, ldc,
214 + m_rows_23);
215 + store_acc_rowpair(acc0189, acc011011, acc011213, acc011415, c_ptr + 8, ldc,
216 + m_rows_01);
217 + store_acc_rowpair(acc2389, acc231011, acc231213, acc231415,
218 + c_ptr + 2 * ldc + 8, ldc, m_rows_23);
219 +}
220 +
221 +} // namespace
222 +
223 +template <typename scalar_t>
224 +class MicroGemm<cpu_utils::ISA::NEON, scalar_t> {
225 + public:
226 + static constexpr int32_t MaxMSize = 8;
227 + static constexpr int32_t NSize = 32;
228 + static constexpr int32_t WeightOCGroupSize = Nr;
229 + static constexpr bool PackA = false;
230 +
231 + public:
232 + void gemm(DEFINE_CPU_MICRO_GEMM_PARAMS) {
233 + TORCH_CHECK(false, "NEON BFMMLA MicroGemm only supports bfloat16.");
234 + }
235 +
236 + static void pack_weight(const scalar_t* __restrict__ /*weight*/,
237 + scalar_t* __restrict__ /*packed_weight*/,
238 + const int32_t /*output_size*/,
239 + const int32_t /*input_size*/) {
240 + TORCH_CHECK(false, "NEON BFMMLA MicroGemm only supports bfloat16.");
241 + }
242 +};
243 +
244 +template <>
245 +class MicroGemm<cpu_utils::ISA::NEON, c10::BFloat16> {
246 + public:
247 + using scalar_t = c10::BFloat16;
248 +
249 + static constexpr int32_t MaxMSize = 8;
250 + static constexpr int32_t NSize = 32;
251 + static constexpr int32_t WeightOCGroupSize = Nr;
252 + static constexpr bool PackA = true;
253 +
254 + public:
255 + // physical layout [
256 + // M / 8; Mr is 8
257 + // K / 4; K for bfmmla is 4
258 + // 4, ; 4 row-pairs for each 8 rows
259 + // 2, ; row-pair is 2 rows
260 + // 4 ; 4 elements per row
261 + // ]
262 +
263 + static void pack_input_from_rows(const scalar_t* const* __restrict__ rows,
264 + scalar_t* __restrict__ a_packed,
265 + const int32_t m, const int32_t k) {
266 + TORCH_CHECK(m > 0 && m <= MaxMSize);
267 + TORCH_CHECK_EQ(k % K, 0);
268 +
269 + auto* __restrict__ out = reinterpret_cast<bfloat16_t*>(a_packed);
270 + const bfloat16x8_t zero_q = vdupq_n_bf16(bfloat16_t{});
271 + const bfloat16x4_t zero = vget_low_bf16(zero_q);
272 +
273 + for (int32_t row_base = 0; row_base < m; row_base += Mr) {
274 + const int32_t actual_m = std::min(Mr, m - row_base);
275 + const bfloat16_t* __restrict__ row[Mr];
276 + for (int32_t i = 0; i < actual_m; ++i) {
277 + row[i] = reinterpret_cast<const bfloat16_t*>(rows[row_base + i]);
278 + }
279 +
280 + if (actual_m == 8) {
281 + int32_t k_idx = 0;
282 + for (; k_idx + 8 <= k; k_idx += 8) {
283 + bfloat16_t* __restrict__ block0 = out;
284 + bfloat16_t* __restrict__ block1 = out + 4 * TileSize;
285 +
286 + bfloat16x8_t a0 = vld1q_bf16(row[0] + k_idx);
287 + bfloat16x8_t a1 = vld1q_bf16(row[1] + k_idx);
288 + vst1q_bf16(block0,
289 + vcombine_bf16(vget_low_bf16(a0), vget_low_bf16(a1)));
290 + vst1q_bf16(block1,
291 + vcombine_bf16(vget_high_bf16(a0), vget_high_bf16(a1)));
292 +
293 + a0 = vld1q_bf16(row[2] + k_idx);
294 + a1 = vld1q_bf16(row[3] + k_idx);
295 + vst1q_bf16(block0 + TileSize,
296 + vcombine_bf16(vget_low_bf16(a0), vget_low_bf16(a1)));
297 + vst1q_bf16(block1 + TileSize,
298 + vcombine_bf16(vget_high_bf16(a0), vget_high_bf16(a1)));
299 +
300 + a0 = vld1q_bf16(row[4] + k_idx);
301 + a1 = vld1q_bf16(row[5] + k_idx);
302 + vst1q_bf16(block0 + 2 * TileSize,
303 + vcombine_bf16(vget_low_bf16(a0), vget_low_bf16(a1)));
304 + vst1q_bf16(block1 + 2 * TileSize,
305 + vcombine_bf16(vget_high_bf16(a0), vget_high_bf16(a1)));
306 +
307 + a0 = vld1q_bf16(row[6] + k_idx);
308 + a1 = vld1q_bf16(row[7] + k_idx);
309 + vst1q_bf16(block0 + 3 * TileSize,
310 + vcombine_bf16(vget_low_bf16(a0), vget_low_bf16(a1)));
311 + vst1q_bf16(block1 + 3 * TileSize,
312 + vcombine_bf16(vget_high_bf16(a0), vget_high_bf16(a1)));
313 +
314 + out += 8 * TileSize;
315 + }
316 +
317 + for (; k_idx < k; k_idx += K) {
318 + bfloat16x4_t a0 = vld1_bf16(row[0] + k_idx);
319 + bfloat16x4_t a1 = vld1_bf16(row[1] + k_idx);
320 + vst1q_bf16(out, vcombine_bf16(a0, a1));
321 +
322 + a0 = vld1_bf16(row[2] + k_idx);
323 + a1 = vld1_bf16(row[3] + k_idx);
324 + vst1q_bf16(out + TileSize, vcombine_bf16(a0, a1));
325 +
326 + a0 = vld1_bf16(row[4] + k_idx);
327 + a1 = vld1_bf16(row[5] + k_idx);
328 + vst1q_bf16(out + 2 * TileSize, vcombine_bf16(a0, a1));
329 +
330 + a0 = vld1_bf16(row[6] + k_idx);
331 + a1 = vld1_bf16(row[7] + k_idx);
332 + vst1q_bf16(out + 3 * TileSize, vcombine_bf16(a0, a1));
333 +
334 + out += 4 * TileSize;
335 + }
336 + continue;
337 + }
338 +
339 + if (actual_m == 4) {
340 + int32_t k_idx = 0;
341 + for (; k_idx + 8 <= k; k_idx += 8) {
342 + bfloat16_t* __restrict__ block0 = out;
343 + bfloat16_t* __restrict__ block1 = out + 2 * TileSize;
344 +
345 + bfloat16x8_t a0 = vld1q_bf16(row[0] + k_idx);
346 + bfloat16x8_t a1 = vld1q_bf16(row[1] + k_idx);
347 + vst1q_bf16(block0,
348 + vcombine_bf16(vget_low_bf16(a0), vget_low_bf16(a1)));
349 + vst1q_bf16(block1,
350 + vcombine_bf16(vget_high_bf16(a0), vget_high_bf16(a1)));
351 +
352 + a0 = vld1q_bf16(row[2] + k_idx);
353 + a1 = vld1q_bf16(row[3] + k_idx);
354 + vst1q_bf16(block0 + TileSize,
355 + vcombine_bf16(vget_low_bf16(a0), vget_low_bf16(a1)));
356 + vst1q_bf16(block1 + TileSize,
357 + vcombine_bf16(vget_high_bf16(a0), vget_high_bf16(a1)));
358 +
359 + out += 4 * TileSize;
360 + }
361 +
362 + for (; k_idx < k; k_idx += K) {
363 + bfloat16x4_t a0 = vld1_bf16(row[0] + k_idx);
364 + bfloat16x4_t a1 = vld1_bf16(row[1] + k_idx);
365 + vst1q_bf16(out, vcombine_bf16(a0, a1));
366 +
367 + a0 = vld1_bf16(row[2] + k_idx);
368 + a1 = vld1_bf16(row[3] + k_idx);
369 + vst1q_bf16(out + TileSize, vcombine_bf16(a0, a1));
370 +
371 + out += 2 * TileSize;
372 + }
373 + continue;
374 + }
375 +
376 + const int32_t row_pair_count = (actual_m <= 4) ? 2 : Mr / 2;
377 +
378 + int32_t k_idx = 0;
379 + for (; k_idx + 8 <= k; k_idx += 8) {
380 + bfloat16_t* __restrict__ block0 = out;
381 + bfloat16_t* __restrict__ block1 = out + row_pair_count * TileSize;
382 +
383 + bfloat16x8_t a0 = vld1q_bf16(row[0] + k_idx);
384 + bfloat16x8_t a1 = (actual_m > 1) ? vld1q_bf16(row[1] + k_idx) : zero_q;
385 + vst1q_bf16(block0, vcombine_bf16(vget_low_bf16(a0), vget_low_bf16(a1)));
386 + vst1q_bf16(block1,
387 + vcombine_bf16(vget_high_bf16(a0), vget_high_bf16(a1)));
388 +
389 + a0 = (actual_m > 2) ? vld1q_bf16(row[2] + k_idx) : zero_q;
390 + a1 = (actual_m > 3) ? vld1q_bf16(row[3] + k_idx) : zero_q;
391 + vst1q_bf16(block0 + TileSize,
392 + vcombine_bf16(vget_low_bf16(a0), vget_low_bf16(a1)));
393 + vst1q_bf16(block1 + TileSize,
394 + vcombine_bf16(vget_high_bf16(a0), vget_high_bf16(a1)));
395 +
396 + if (actual_m > 4) {
397 + a0 = vld1q_bf16(row[4] + k_idx);
398 + a1 = (actual_m > 5) ? vld1q_bf16(row[5] + k_idx) : zero_q;
399 + vst1q_bf16(block0 + 2 * TileSize,
400 + vcombine_bf16(vget_low_bf16(a0), vget_low_bf16(a1)));
401 + vst1q_bf16(block1 + 2 * TileSize,
402 + vcombine_bf16(vget_high_bf16(a0), vget_high_bf16(a1)));
403 +
404 + a0 = (actual_m > 6) ? vld1q_bf16(row[6] + k_idx) : zero_q;
405 + a1 = (actual_m > 7) ? vld1q_bf16(row[7] + k_idx) : zero_q;
406 + vst1q_bf16(block0 + 3 * TileSize,
407 + vcombine_bf16(vget_low_bf16(a0), vget_low_bf16(a1)));
408 + vst1q_bf16(block1 + 3 * TileSize,
409 + vcombine_bf16(vget_high_bf16(a0), vget_high_bf16(a1)));
410 + }
411 +
412 + out += 2 * row_pair_count * TileSize;
413 + }
414 +
415 + for (; k_idx < k; k_idx += K) {
416 + bfloat16x4_t a0 = vld1_bf16(row[0] + k_idx);
417 + bfloat16x4_t a1 = (actual_m > 1) ? vld1_bf16(row[1] + k_idx) : zero;
418 + vst1q_bf16(out, vcombine_bf16(a0, a1));
419 +
420 + a0 = (actual_m > 2) ? vld1_bf16(row[2] + k_idx) : zero;
421 + a1 = (actual_m > 3) ? vld1_bf16(row[3] + k_idx) : zero;
422 + vst1q_bf16(out + TileSize, vcombine_bf16(a0, a1));
423 +
424 + if (actual_m > 4) {
425 + a0 = vld1_bf16(row[4] + k_idx);
426 + a1 = (actual_m > 5) ? vld1_bf16(row[5] + k_idx) : zero;
427 + vst1q_bf16(out + 2 * TileSize, vcombine_bf16(a0, a1));
428 +
429 + a0 = (actual_m > 6) ? vld1_bf16(row[6] + k_idx) : zero;
430 + a1 = (actual_m > 7) ? vld1_bf16(row[7] + k_idx) : zero;
431 + vst1q_bf16(out + 3 * TileSize, vcombine_bf16(a0, a1));
432 + }
433 + out += row_pair_count * TileSize;
434 + }
435 + }
436 + }
437 +
438 + void gemm(DEFINE_CPU_MICRO_GEMM_PARAMS) {
439 + (void)lda; // A is packed, so lda is not needed
440 + TORCH_CHECK_EQ(k % K, 0);
441 +
442 + for (int32_t n_idx = 0; n_idx < NSize; n_idx += Nr_gemv) {
443 + const bfloat16_t* __restrict__ b_panel =
444 + reinterpret_cast<const bfloat16_t*>(b_ptr) + n_idx * k;
445 +
446 + for (int32_t row_base = 0; row_base < m; row_base += Mr) {
447 + const int32_t panel_m = std::min(Mr, m - row_base);
448 + const bfloat16_t* __restrict__ a_panel =
449 + reinterpret_cast<const bfloat16_t*>(a_ptr) + row_base * k;
450 + float* __restrict__ c_panel = c_ptr + row_base * ldc + n_idx;
451 +
452 + if (panel_m <= 4) {
453 + gemm_micro_bfmmla_4x16_packed_a(a_panel, b_panel, c_panel, panel_m, k,
454 + b_n_group_stride, ldc, accum_c);
455 + } else {
456 + gemm_micro_bfmmla_8x8_packed_a(a_panel, b_panel, c_panel, panel_m, k,
457 + ldc, accum_c);
458 + gemm_micro_bfmmla_8x8_packed_a(a_panel, b_panel + b_n_group_stride,
459 + c_panel + Nr, panel_m, k, ldc,
460 + accum_c);
461 + }
462 + }
463 + }
464 + }
465 +
466 + // physical layout [
467 + // N / 8; Nr is 8
468 + // K / 4; K for bfmmla is 4
469 + // 4, ; 4 col-pairs for each 8 cols
470 + // 2, ; col-pair is 2 cols
471 + // 4 ; 4 elements per col
472 + // ]
473 + static void pack_weight(const c10::BFloat16* __restrict__ weight,
474 + c10::BFloat16* __restrict__ packed_weight,
475 + const int32_t output_size, const int32_t input_size) {
476 + TORCH_CHECK_EQ(output_size % NSize, 0);
477 + TORCH_CHECK_EQ(input_size % K, 0);
478 +
479 + for (int32_t o_idx = 0; o_idx < output_size; o_idx += Nr) {
480 + c10::BFloat16* __restrict__ dst = packed_weight + o_idx * input_size;
481 + for (int32_t k_idx = 0; k_idx < input_size; k_idx += K) {
482 + for (int32_t pair_idx = 0; pair_idx < Nr; pair_idx += Cols) {
483 + const c10::BFloat16* __restrict__ row0 =
484 + weight + (o_idx + pair_idx) * input_size;
485 + const c10::BFloat16* __restrict__ row1 = row0 + input_size;
486 + dst[0] = row0[k_idx + 0];
487 + dst[1] = row0[k_idx + 1];
488 + dst[2] = row0[k_idx + 2];
489 + dst[3] = row0[k_idx + 3];
490 + dst[4] = row1[k_idx + 0];
491 + dst[5] = row1[k_idx + 1];
492 + dst[6] = row1[k_idx + 2];
493 + dst[7] = row1[k_idx + 3];
494 + dst += TileSize;
495 + }
496 + }
497 + }
498 + }
499 +};
500 +
501 +} // namespace cpu_micro_gemm
502 +
503 +#endif
csrc/cpu/micro_gemm/cpu_micro_gemm_vec.hpp
+2
@@ -104,6 +104,8 @@ class MicroGemm<cpu_utils::ISA::VEC, scalar_t> {
104 public:
105 static constexpr int32_t MaxMSize = 8;
106 static constexpr int32_t NSize = 32;
107 + static constexpr int32_t WeightOCGroupSize = 16;
108 + static constexpr bool PackA = false;
109
110 public:
111 void gemm(DEFINE_CPU_MICRO_GEMM_PARAMS) {
csrc/cpu/torch_bindings.cpp
+2 -2
@@ -538,7 +538,7 @@ TORCH_LIBRARY_EXPAND(TORCH_EXTENSION_NAME, ops) {
538 #endif
539
540 // fused moe
541 -#if defined(__AVX512F__)
541 +#if defined(__AVX512F__) || (defined(ARM_BF16_SUPPORT))
542 ops.def(
543 "prepack_moe_weight(Tensor weight, Tensor(a1!) packed_weight, str isa) "
544 "-> ()");
@@ -549,7 +549,7 @@ TORCH_LIBRARY_EXPAND(TORCH_EXTENSION_NAME, ops) {
549 "bool skip_weighted, "
550 "str act, str isa) -> ()");
551 ops.impl("cpu_fused_moe", torch::kCPU, &cpu_fused_moe);
552 -#endif
552 +#endif // #if defined(__AVX512F__) || (defined(ARM_BF16_SUPPORT))
553 ops.def(
554 "mla_decode_kvcache("
555 " Tensor! out, Tensor query, Tensor kv_cache,"
csrc/cpu/utils.hpp
+4 -1
@@ -2,13 +2,14 @@
2 #define UTILS_HPP
3
4 #include <atomic>
5 +#include <string>
6 #include <unistd.h>
7 #include <ATen/cpu/Utils.h>
8
9 #include "cpu/cpu_types.hpp"
10
11 namespace cpu_utils {
11 -enum class ISA { AMX, VEC, RVV };
12 +enum class ISA { AMX, VEC, RVV, NEON };
13
14 inline ISA get_isa(const std::string& isa) {
15 if (isa == "amx") {
@@ -17,6 +18,8 @@ inline ISA get_isa(const std::string& isa) {
18 return ISA::VEC;
19 } else if (isa == "rvv") {
20 return ISA::RVV;
21 + } else if (isa == "neon") {
22 + return ISA::NEON;
23 } else {
24 TORCH_CHECK(false, "Invalid isa type: " + isa);
25 }
tests/kernels/moe/test_cpu_fused_moe.py
+12 -4
@@ -5,10 +5,13 @@ import pytest
5 import torch
6
7 from tests.kernels.allclose_default import get_default_atol, get_default_rtol
8 -from vllm._custom_ops import cpu_fused_moe, cpu_prepack_moe_weight
8 +from vllm._custom_ops import (
9 + cpu_fused_moe,
10 + cpu_prepack_moe_weight,
11 +)
12 from vllm.model_executor.layers.fused_moe.activation import MoEActivation
13 from vllm.model_executor.layers.fused_moe.cpu_fused_moe import _CPU_MOE_ACT_FN
11 -from vllm.platforms import current_platform
14 +from vllm.platforms import CpuArchEnum, current_platform
15 from vllm.utils.torch_utils import set_random_seed
16
17 if not current_platform.is_cpu():
@@ -26,8 +29,13 @@ ACT = [
29 MoEActivation.GELU,
30 MoEActivation.GELU_TANH,
31 ]
29 -USE_BIAS = [True, False]
30 -ISA = ["amx", "vec"] if torch.cpu._is_amx_tile_supported() else ["vec"]
32 +USE_BIAS = [False, True]
33 +ISA = ["vec"]
34 +if current_platform.get_cpu_architecture() == CpuArchEnum.ARM:
35 + ISA.append("neon")
36 +if torch.cpu._is_amx_tile_supported():
37 + ISA.append("amx")
38 +
39 DTYPE = [torch.bfloat16]
40
41
vllm/model_executor/layers/fused_moe/cpu_fused_moe.py
+12
@@ -16,6 +16,7 @@ from vllm._custom_ops import (
16 from vllm.model_executor.layers.activation import SiluAndMul
17 from vllm.model_executor.layers.fused_moe.activation import MoEActivation
18 from vllm.model_executor.layers.quantization.utils.layer_utils import replace_parameter
19 +from vllm.platforms import CpuArchEnum, current_platform
20 from vllm.utils.torch_utils import direct_register_custom_op
21
22 _CPU_MOE_LAYER_CACHE = {}
@@ -312,6 +313,17 @@ class CPUFusedMOE:
313 if supports_amx:
314 return False, "none"
315
316 + supports_neon = current_platform.get_cpu_architecture() == CpuArchEnum.ARM
317 + if supports_neon:
318 + if (
319 + dtype == torch.bfloat16
320 + and w13_input_size % 4 == 0
321 + and w2_input_size % 4 == 0
322 + ):
323 + return True, "neon"
324 + else:
325 + return False, "none"
326 +
327 return True, "vec"
328
329 def init_moe_grouped_gemm(