[CPU][Perf] INT8 Fused MoE Kernel for Arm CPUs (#48637)
Signed-off-by: Fadi Arafeh <fadi.arafeh@arm.com> Co-authored-by: Li, Jiang <jiang1.li@intel.com>
Fadi Arafeh committed
Jul 27, 2026 at 06:53 UTC
c314af1abfddff7b6cce9af578be72c496c6a5e4
12 files changed
+1742
-216
cmake/cpu_extension.cmake
+19
-1
@@ -15,6 +15,7 @@ endif()
15
#
16
set(ENABLE_X86_ISA $ENV{VLLM_CPU_X86})
17
set(ENABLE_ARM_BF16 $ENV{VLLM_CPU_ARM_BF16})
18
+set(ENABLE_ARM_I8MM $ENV{VLLM_CPU_ARM_I8MM})
19
set(ENABLE_RVV_BF16 $ENV{VLLM_CPU_RVV_BF16})
20
21
include_directories("${CMAKE_SOURCE_DIR}/csrc")
@@ -96,12 +97,14 @@ if (MACOSX_FOUND AND CMAKE_SYSTEM_PROCESSOR STREQUAL "arm64")
97
set(ENABLE_NUMA OFF)
98
check_sysctl(hw.optional.neon ASIMD_FOUND)
99
check_sysctl(hw.optional.arm.FEAT_BF16 ARM_BF16_FOUND)
100
+ check_sysctl(hw.optional.arm.FEAT_I8MM ARM_I8MM_FOUND)
101
else()
102
find_isa(${CPUINFO} "Power11" POWER11_FOUND)
103
find_isa(${CPUINFO} "POWER10" POWER10_FOUND)
104
find_isa(${CPUINFO} "POWER9" POWER9_FOUND)
105
find_isa(${CPUINFO} "asimd" ASIMD_FOUND) # Check for ARM NEON support
106
find_isa(${CPUINFO} "bf16" ARM_BF16_FOUND) # Check for ARM BF16 support
107
+ find_isa(${CPUINFO} "i8mm" ARM_I8MM_FOUND) # Check for ARM I8MM support
108
find_isa(${CPUINFO} "S390" S390_FOUND)
109
find_isa(${CPUINFO} "zvfhmin" RVV_FP16_FOUND) # Check for RISC-V Vector FP16 support
110
find_isa(${CPUINFO} "zvfbfmin" RVV_BF16_FOUND) # Check for RISC-V Vector BF16 support
@@ -111,6 +114,11 @@ else()
114
set(ARM_BF16_FOUND ON)
115
message(STATUS "ARM BF16 support enabled via VLLM_CPU_ARM_BF16 environment variable")
116
endif()
117
+ if (ENABLE_ARM_I8MM)
118
+ set(ARM_I8MM_FOUND ON)
119
+ message(STATUS
120
+ "ARM I8MM support enabled via VLLM_CPU_ARM_I8MM environment variable")
121
+ endif()
122
# Some kernels (e.g. Bianbu on Spacemit X100) do not report zvfbfmin
123
# in /proc/cpuinfo despite hardware support. VLLM_CPU_RVV_BF16=1
124
# overrides the detection result.
@@ -166,6 +174,11 @@ elseif (ASIMD_FOUND)
174
message(WARNING "BF16 functionality is not available")
175
set(MARCH_FLAGS "-march=armv8.2-a+dotprod+fp16")
176
endif()
177
+ if(ARM_I8MM_FOUND)
178
+ message(STATUS "I8MM extension detected")
179
+ string(APPEND MARCH_FLAGS "+i8mm")
180
+ add_compile_definitions(ARM_I8MM_SUPPORT)
181
+ endif()
182
list(APPEND CXX_COMPILE_FLAGS ${MARCH_FLAGS})
183
elseif (S390_FOUND)
184
message(STATUS "S390 detected")
@@ -447,8 +460,13 @@ if (ASIMD_FOUND AND NOT APPLE_SILICON_FOUND)
460
"csrc/cpu/shm.cpp"
461
"csrc/cpu/activation_lut_bf16.cpp"
462
"csrc/cpu/cpu_tanhf_neon.hpp"
450
- "csrc/cpu/cpu_fused_moe.cpp"
463
${VLLM_EXT_SRC})
464
+ if (ARM_BF16_FOUND)
465
+ set(VLLM_EXT_SRC "csrc/cpu/cpu_fused_moe.cpp" ${VLLM_EXT_SRC})
466
+ if (ARM_I8MM_FOUND)
467
+ set(VLLM_EXT_SRC "csrc/cpu/cpu_fused_moe_int8.cpp" ${VLLM_EXT_SRC})
468
+ endif()
469
+ endif()
470
endif()
471
472
if (POWER9_FOUND OR POWER10_FOUND OR POWER11_FOUND)
csrc/cpu/cpu_fused_moe.cpp
+5
-187
@@ -1,5 +1,6 @@
1
#include "cpu/cpu_types.hpp"
2
#include "cpu/utils.hpp"
3
+#include "cpu/cpu_fused_moe_activations.hpp"
4
#include "cpu/micro_gemm/cpu_micro_gemm_vec.hpp"
5
#include "cpu/cpu_arch_macros.h"
6
@@ -43,193 +44,9 @@
44
}()
45
46
namespace {
46
-enum class FusedMOEAct {
47
- SiluAndMul,
48
- SwigluOAIAndMul,
49
- GeluAndMul,
50
- GeluTanhAndMul,
51
-};
52
-
53
-FusedMOEAct get_act_type(const std::string& act) {
54
- if (act == "silu") {
55
- return FusedMOEAct::SiluAndMul;
56
- } else if (act == "swigluoai") {
57
- return FusedMOEAct::SwigluOAIAndMul;
58
- } else if (act == "gelu") {
59
- return FusedMOEAct::GeluAndMul;
60
- } else if (act == "gelu_tanh") {
61
- return FusedMOEAct::GeluTanhAndMul;
62
- } else {
63
- TORCH_CHECK(false, "Invalid act type: " + act);
64
- }
65
-}
47
67
-template <typename scalar_t>
68
-void swigluoai_and_mul(float* __restrict__ input, scalar_t* __restrict__ output,
69
- const int32_t m_size, const int32_t n_size,
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);
82
- vec_op::FP32Vec16 one_vec(1.0);
83
-
84
- DEFINE_FAST_EXP
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));
100
- auto glu = gate_vec * sigmoid_vec;
101
- auto gated_output_fp32 = (one_vec + up_vec) * glu;
102
- scalar_vec_t gated_output = scalar_vec_t(gated_output_fp32);
103
- gated_output.save(output + n / 2);
104
- }
105
- input += input_stride;
106
- output += output_stride;
107
- }
108
-}
109
-
110
-template <typename scalar_t>
111
-void silu_and_mul(float* __restrict__ input, scalar_t* __restrict__ output,
112
- const int32_t m_size, const int32_t n_size,
113
- const int32_t input_stride, const int32_t output_stride) {
114
- using scalar_vec_t = typename cpu_utils::VecTypeTrait<scalar_t>::vec_t;
115
- const int32_t dim = n_size / 2;
116
- float* __restrict__ gate = input;
117
- float* __restrict__ up = input + dim;
118
- vec_op::FP32Vec16 one_vec(1.0);
119
-
120
- DEFINE_FAST_EXP
121
-
122
- for (int32_t m = 0; m < m_size; ++m) {
123
- for (int32_t n = 0; n < dim; n += 16) {
124
- vec_op::FP32Vec16 gate_vec(gate + n);
125
- vec_op::FP32Vec16 up_vec(up + n);
126
- auto sigmoid_vec = one_vec / (one_vec + fast_exp(-gate_vec));
127
- auto silu = gate_vec * sigmoid_vec;
128
- auto gated_output_fp32 = up_vec * silu;
129
- scalar_vec_t gated_output = scalar_vec_t(gated_output_fp32);
130
- gated_output.save(output + n);
131
- }
132
- gate += input_stride;
133
- up += input_stride;
134
- output += output_stride;
135
- }
136
-}
137
-
138
-template <typename scalar_t>
139
-void gelu_and_mul(float* __restrict__ input, scalar_t* __restrict__ output,
140
- const int32_t m_size, const int32_t n_size,
141
- const int32_t input_stride, const int32_t output_stride) {
142
- using scalar_vec_t = typename cpu_utils::VecTypeTrait<scalar_t>::vec_t;
143
- const int32_t dim = n_size / 2;
144
- float* __restrict__ gate = input;
145
- float* __restrict__ up = input + dim;
146
- vec_op::FP32Vec16 one_vec(1.0);
147
- vec_op::FP32Vec16 w1_vec(M_SQRT1_2);
148
- vec_op::FP32Vec16 w2_vec(0.5);
149
- alignas(64) float temp[16];
150
-
151
- DEFINE_FAST_EXP
152
-
153
- for (int32_t m = 0; m < m_size; ++m) {
154
- for (int32_t n = 0; n < dim; n += 16) {
155
- vec_op::FP32Vec16 gate_vec(gate + n);
156
- vec_op::FP32Vec16 up_vec(up + n);
157
- auto er_input_vec = gate_vec * w1_vec;
158
-
159
- er_input_vec.save(temp);
160
- for (int32_t i = 0; i < 16; ++i) {
161
- temp[i] = std::erf(temp[i]);
162
- }
163
- vec_op::FP32Vec16 er_vec(temp);
164
- auto gelu = gate_vec * w2_vec * (one_vec + er_vec);
165
- auto gated_output_fp32 = up_vec * gelu;
166
- scalar_vec_t gated_output = scalar_vec_t(gated_output_fp32);
167
- gated_output.save(output + n);
168
- }
169
- gate += input_stride;
170
- up += input_stride;
171
- output += output_stride;
172
- }
173
-}
174
-
175
-template <typename scalar_t>
176
-void gelu_tanh_and_mul(float* __restrict__ input, scalar_t* __restrict__ output,
177
- const int32_t m_size, const int32_t n_size,
178
- const int32_t input_stride,
179
- const int32_t output_stride) {
180
- using scalar_vec_t = typename cpu_utils::VecTypeTrait<scalar_t>::vec_t;
181
- const int32_t dim = n_size / 2;
182
- float* __restrict__ gate = input;
183
- float* __restrict__ up = input + dim;
184
- vec_op::FP32Vec16 one_vec(1.0);
185
- vec_op::FP32Vec16 w1_vec(0.7978845608028654);
186
- vec_op::FP32Vec16 w2_vec(0.5);
187
- vec_op::FP32Vec16 w3_vec(0.044715);
188
-
189
- for (int32_t m = 0; m < m_size; ++m) {
190
- for (int32_t n = 0; n < dim; n += 16) {
191
- vec_op::FP32Vec16 gate_vec(gate + n);
192
- vec_op::FP32Vec16 up_vec(up + n);
193
- auto gate_pow3_vec = gate_vec * gate_vec * gate_vec;
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
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);
201
- gated_output.save(output + n);
202
- }
203
- gate += input_stride;
204
- up += input_stride;
205
- output += output_stride;
206
- }
207
-}
208
-
209
-template <typename scalar_t>
210
-FORCE_INLINE void apply_gated_act(const FusedMOEAct act,
211
- float* __restrict__ input,
212
- scalar_t* __restrict__ output,
213
- const int32_t m, const int32_t n,
214
- const int32_t input_stride,
215
- const int32_t output_stride) {
216
- switch (act) {
217
- case FusedMOEAct::SwigluOAIAndMul:
218
- swigluoai_and_mul(input, output, m, n, input_stride, output_stride);
219
- return;
220
- case FusedMOEAct::SiluAndMul:
221
- silu_and_mul(input, output, m, n, input_stride, output_stride);
222
- return;
223
- case FusedMOEAct::GeluAndMul:
224
- gelu_and_mul(input, output, m, n, input_stride, output_stride);
225
- return;
226
- case FusedMOEAct::GeluTanhAndMul:
227
- gelu_tanh_and_mul(input, output, m, n, input_stride, output_stride);
228
- return;
229
- default:
230
- TORCH_CHECK(false, "Unsupported act type.");
231
- }
232
-}
48
+using cpu_fused_moe_utils::apply_gated_act;
49
+using cpu_fused_moe_utils::FusedMOEAct;
50
51
template <typename scalar_t, typename gemm_t>
52
void prepack_moe_weight_impl(scalar_t* __restrict__ weight_ptr,
@@ -817,6 +634,7 @@ void fused_moe_impl(scalar_t* __restrict__ output, scalar_t* __restrict__ input,
634
}
635
}
636
}
637
+
638
} // namespace
639
640
void prepack_moe_weight(
@@ -864,7 +682,7 @@ void cpu_fused_moe(
682
const int32_t input_size_2 = w2.size(2);
683
const int32_t output_size_2 = w2.size(1);
684
const int32_t topk_num = topk_id.size(1);
867
- const FusedMOEAct act_type = get_act_type(act);
685
+ const FusedMOEAct act_type = cpu_fused_moe_utils::get_act_type(act);
686
cpu_utils::ISA isa_type = cpu_utils::get_isa(isa);
687
TORCH_CHECK(!skip_weighted || topk_num == 1,
688
"skip_weighted is only supported for topk=1 on CPU");
csrc/cpu/cpu_fused_moe_activations.hpp
new
+204
@@ -0,0 +1,204 @@
1
+// SPDX-License-Identifier: Apache-2.0
2
+// SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
+
4
+#ifndef CPU_FUSED_MOE_ACTIVATIONS_HPP
5
+#define CPU_FUSED_MOE_ACTIVATIONS_HPP
6
+
7
+#include <cmath>
8
+#include <cstdint>
9
+#include <string>
10
+
11
+#include "cpu/cpu_arch_macros.h"
12
+#include "cpu/utils.hpp"
13
+
14
+namespace cpu_fused_moe_utils {
15
+enum class FusedMOEAct {
16
+ SiluAndMul,
17
+ SwigluOAIAndMul,
18
+ GeluAndMul,
19
+ GeluTanhAndMul,
20
+};
21
+
22
+inline FusedMOEAct get_act_type(const std::string& act) {
23
+ if (act == "silu") {
24
+ return FusedMOEAct::SiluAndMul;
25
+ } else if (act == "swigluoai") {
26
+ return FusedMOEAct::SwigluOAIAndMul;
27
+ } else if (act == "gelu") {
28
+ return FusedMOEAct::GeluAndMul;
29
+ } else if (act == "gelu_tanh") {
30
+ return FusedMOEAct::GeluTanhAndMul;
31
+ } else {
32
+ TORCH_CHECK(false, "Invalid act type: " + act);
33
+ }
34
+}
35
+
36
+template <typename scalar_t>
37
+void swigluoai_and_mul(float* __restrict__ input, scalar_t* __restrict__ output,
38
+ const int32_t m_size, const int32_t n_size,
39
+ const int32_t input_stride,
40
+ const int32_t output_stride) {
41
+ using scalar_vec_t = typename cpu_utils::VecTypeTrait<scalar_t>::vec_t;
42
+#if !defined(__aarch64__)
43
+ // For GPT-OSS interleaved gate-up weights
44
+ alignas(64) static int32_t index[16] = {0, 2, 4, 6, 8, 10, 12, 14,
45
+ 16, 18, 20, 22, 24, 26, 28, 30};
46
+ vec_op::INT32Vec16 index_vec(index);
47
+#endif
48
+ vec_op::FP32Vec16 gate_up_max_vec(7.0);
49
+ vec_op::FP32Vec16 up_min_vec(-7.0);
50
+ vec_op::FP32Vec16 alpha_vec(1.702);
51
+ vec_op::FP32Vec16 one_vec(1.0);
52
+
53
+ DEFINE_FAST_EXP
54
+
55
+ for (int32_t m = 0; m < m_size; ++m) {
56
+ for (int32_t n = 0; n < n_size; n += 32) {
57
+ // Note: AdvSIMD does not support gather loads
58
+#if defined(__aarch64__)
59
+ vec_op::FP32Vec16 gate_vec(vec_op::uninit);
60
+ vec_op::FP32Vec16 up_vec(vec_op::uninit);
61
+ vec_op::FP32Vec16::load_even_odd(input + n, gate_vec, up_vec);
62
+#else
63
+ vec_op::FP32Vec16 gate_vec(input + n, index_vec);
64
+ vec_op::FP32Vec16 up_vec(input + n + 1, index_vec);
65
+#endif
66
+ gate_vec = gate_vec.min(gate_up_max_vec);
67
+ up_vec = up_vec.clamp(up_min_vec, gate_up_max_vec);
68
+ auto sigmoid_vec = one_vec / (one_vec + fast_exp(-gate_vec * alpha_vec));
69
+ auto glu = gate_vec * sigmoid_vec;
70
+ auto gated_output_fp32 = (one_vec + up_vec) * glu;
71
+ scalar_vec_t gated_output = scalar_vec_t(gated_output_fp32);
72
+ gated_output.save(output + n / 2);
73
+ }
74
+ input += input_stride;
75
+ output += output_stride;
76
+ }
77
+}
78
+
79
+template <typename scalar_t>
80
+void silu_and_mul(float* __restrict__ input, scalar_t* __restrict__ output,
81
+ const int32_t m_size, const int32_t n_size,
82
+ const int32_t input_stride, const int32_t output_stride) {
83
+ using scalar_vec_t = typename cpu_utils::VecTypeTrait<scalar_t>::vec_t;
84
+ const int32_t dim = n_size / 2;
85
+ float* __restrict__ gate = input;
86
+ float* __restrict__ up = input + dim;
87
+ vec_op::FP32Vec16 one_vec(1.0);
88
+
89
+ DEFINE_FAST_EXP
90
+
91
+ for (int32_t m = 0; m < m_size; ++m) {
92
+ for (int32_t n = 0; n < dim; n += 16) {
93
+ vec_op::FP32Vec16 gate_vec(gate + n);
94
+ vec_op::FP32Vec16 up_vec(up + n);
95
+ auto sigmoid_vec = one_vec / (one_vec + fast_exp(-gate_vec));
96
+ auto silu = gate_vec * sigmoid_vec;
97
+ auto gated_output_fp32 = up_vec * silu;
98
+ scalar_vec_t gated_output = scalar_vec_t(gated_output_fp32);
99
+ gated_output.save(output + n);
100
+ }
101
+ gate += input_stride;
102
+ up += input_stride;
103
+ output += output_stride;
104
+ }
105
+}
106
+
107
+template <typename scalar_t>
108
+void gelu_and_mul(float* __restrict__ input, scalar_t* __restrict__ output,
109
+ const int32_t m_size, const int32_t n_size,
110
+ const int32_t input_stride, const int32_t output_stride) {
111
+ using scalar_vec_t = typename cpu_utils::VecTypeTrait<scalar_t>::vec_t;
112
+ const int32_t dim = n_size / 2;
113
+ float* __restrict__ gate = input;
114
+ float* __restrict__ up = input + dim;
115
+ vec_op::FP32Vec16 one_vec(1.0);
116
+ vec_op::FP32Vec16 w1_vec(M_SQRT1_2);
117
+ vec_op::FP32Vec16 w2_vec(0.5);
118
+ alignas(64) float temp[16];
119
+
120
+ DEFINE_FAST_EXP
121
+
122
+ for (int32_t m = 0; m < m_size; ++m) {
123
+ for (int32_t n = 0; n < dim; n += 16) {
124
+ vec_op::FP32Vec16 gate_vec(gate + n);
125
+ vec_op::FP32Vec16 up_vec(up + n);
126
+ auto er_input_vec = gate_vec * w1_vec;
127
+
128
+ er_input_vec.save(temp);
129
+ for (int32_t i = 0; i < 16; ++i) {
130
+ temp[i] = std::erf(temp[i]);
131
+ }
132
+ vec_op::FP32Vec16 er_vec(temp);
133
+ auto gelu = gate_vec * w2_vec * (one_vec + er_vec);
134
+ auto gated_output_fp32 = up_vec * gelu;
135
+ scalar_vec_t gated_output = scalar_vec_t(gated_output_fp32);
136
+ gated_output.save(output + n);
137
+ }
138
+ gate += input_stride;
139
+ up += input_stride;
140
+ output += output_stride;
141
+ }
142
+}
143
+
144
+template <typename scalar_t>
145
+void gelu_tanh_and_mul(float* __restrict__ input, scalar_t* __restrict__ output,
146
+ const int32_t m_size, const int32_t n_size,
147
+ const int32_t input_stride,
148
+ const int32_t output_stride) {
149
+ using scalar_vec_t = typename cpu_utils::VecTypeTrait<scalar_t>::vec_t;
150
+ const int32_t dim = n_size / 2;
151
+ float* __restrict__ gate = input;
152
+ float* __restrict__ up = input + dim;
153
+ vec_op::FP32Vec16 one_vec(1.0);
154
+ vec_op::FP32Vec16 w1_vec(0.7978845608028654);
155
+ vec_op::FP32Vec16 w2_vec(0.5);
156
+ vec_op::FP32Vec16 w3_vec(0.044715);
157
+
158
+ for (int32_t m = 0; m < m_size; ++m) {
159
+ for (int32_t n = 0; n < dim; n += 16) {
160
+ vec_op::FP32Vec16 gate_vec(gate + n);
161
+ vec_op::FP32Vec16 up_vec(up + n);
162
+ auto gate_pow3_vec = gate_vec * gate_vec * gate_vec;
163
+ auto inner_vec = w1_vec * (gate_vec + w3_vec * gate_pow3_vec);
164
+ // Note: can't use fast_exp form because diffusiongemma will generate
165
+ // wrong results
166
+ auto tanh_vec = inner_vec.tanh();
167
+ auto gelu_tanh = gate_vec * w2_vec * (one_vec + tanh_vec);
168
+ auto gated_output_fp32 = up_vec * gelu_tanh;
169
+ scalar_vec_t gated_output = scalar_vec_t(gated_output_fp32);
170
+ gated_output.save(output + n);
171
+ }
172
+ gate += input_stride;
173
+ up += input_stride;
174
+ output += output_stride;
175
+ }
176
+}
177
+
178
+template <typename scalar_t>
179
+FORCE_INLINE void apply_gated_act(const FusedMOEAct act,
180
+ float* __restrict__ input,
181
+ scalar_t* __restrict__ output,
182
+ const int32_t m, const int32_t n,
183
+ const int32_t input_stride,
184
+ const int32_t output_stride) {
185
+ switch (act) {
186
+ case FusedMOEAct::SwigluOAIAndMul:
187
+ swigluoai_and_mul(input, output, m, n, input_stride, output_stride);
188
+ return;
189
+ case FusedMOEAct::SiluAndMul:
190
+ silu_and_mul(input, output, m, n, input_stride, output_stride);
191
+ return;
192
+ case FusedMOEAct::GeluAndMul:
193
+ gelu_and_mul(input, output, m, n, input_stride, output_stride);
194
+ return;
195
+ case FusedMOEAct::GeluTanhAndMul:
196
+ gelu_tanh_and_mul(input, output, m, n, input_stride, output_stride);
197
+ return;
198
+ default:
199
+ TORCH_CHECK(false, "Unsupported act type.");
200
+ }
201
+}
202
+} // namespace cpu_fused_moe_utils
203
+
204
+#endif
csrc/cpu/cpu_fused_moe_int8.cpp
new
+647
@@ -0,0 +1,647 @@
1
+// SPDX-License-Identifier: Apache-2.0
2
+// SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
+
4
+#include "cpu/cpu_arch_macros.h"
5
+
6
+#include <algorithm>
7
+#include <cstdint>
8
+#include <cstring>
9
+#include <optional>
10
+#include <string>
11
+
12
+#include "cpu/cpu_fused_moe_activations.hpp"
13
+#include "cpu/cpu_types.hpp"
14
+#include "cpu/micro_gemm/cpu_micro_gemm_impl.hpp"
15
+#include "cpu/utils.hpp"
16
+
17
+#if defined(ARM_I8MM_SUPPORT) && defined(ARM_BF16_SUPPORT)
18
+ #include "cpu/micro_gemm/cpu_micro_gemm_int8_neon.hpp"
19
+ #define NEON_DISPATCH(SCALAR_TYPE, ...) \
20
+ case cpu_utils::ISA::NEON: { \
21
+ using gemm_t = \
22
+ cpu_micro_gemm::MicroGemmINT8<cpu_utils::ISA::NEON, SCALAR_TYPE>; \
23
+ return __VA_ARGS__(); \
24
+ }
25
+#else
26
+ #define NEON_DISPATCH(SCALAR_TYPE, ...) case cpu_utils::ISA::NEON:
27
+#endif
28
+
29
+#define CPU_INT8_ISA_DISPATCH_IMPL(ISA_TYPE, SCALAR_TYPE, ...) \
30
+ [&] { \
31
+ switch (ISA_TYPE) { \
32
+ NEON_DISPATCH(SCALAR_TYPE, __VA_ARGS__) \
33
+ default: { \
34
+ TORCH_CHECK(false, "Invalid CPU ISA type."); \
35
+ } \
36
+ } \
37
+ }()
38
+
39
+namespace {
40
+using cpu_fused_moe_utils::apply_gated_act;
41
+using cpu_fused_moe_utils::FusedMOEAct;
42
+
43
+template <typename gemm_t>
44
+void prepack_moe_weight_int8_impl(const int8_t* __restrict__ weight_ptr,
45
+ int8_t* __restrict__ packed_weight_ptr,
46
+ const int32_t expert_num,
47
+ const int32_t output_size,
48
+ const int32_t input_size,
49
+ const int64_t expert_stride) {
50
+#pragma omp parallel for
51
+ for (int32_t e_idx = 0; e_idx < expert_num; ++e_idx) {
52
+ gemm_t::pack_weight(weight_ptr + expert_stride * e_idx,
53
+ packed_weight_ptr + expert_stride * e_idx, output_size,
54
+ input_size);
55
+ }
56
+}
57
+
58
+// INT8 MoE kernel, based on the original BF16 kernel in cpu_fused_moe.cpp
59
+template <typename scalar_t, typename gemm_t>
60
+void fused_moe_int8_impl(
61
+ scalar_t* __restrict__ output, const scalar_t* __restrict__ input,
62
+ const int8_t* __restrict__ w13, const int8_t* __restrict__ w2,
63
+ const float* __restrict__ w13_scales, const float* __restrict__ w2_scales,
64
+ scalar_t* __restrict__ w13_bias, scalar_t* __restrict__ w2_bias,
65
+ const float* __restrict__ topk_weights, const int32_t* __restrict__ topk_id,
66
+ const FusedMOEAct act_type, const int32_t token_num,
67
+ const int32_t expert_num, const int32_t topk_num,
68
+ const int32_t input_size_13, const int32_t output_size_13,
69
+ const int32_t input_size_2, const int32_t output_size_2,
70
+ const bool skip_weighted) {
71
+ using scalar_vec_t = typename cpu_utils::VecTypeTrait<scalar_t>::vec_t;
72
+ constexpr int32_t gemm_n_tile_size = gemm_t::NSize;
73
+ constexpr int32_t gemm_m_tile_size = gemm_t::MaxMSize;
74
+ constexpr int32_t min_w13_n_tile_size = 2 * gemm_n_tile_size;
75
+
76
+ TORCH_CHECK_EQ(input_size_13 % gemm_t::K, 0);
77
+ TORCH_CHECK_EQ(input_size_2 % gemm_t::K, 0);
78
+ TORCH_CHECK_EQ(output_size_13 % min_w13_n_tile_size, 0);
79
+ TORCH_CHECK_EQ(output_size_2 % gemm_n_tile_size, 0);
80
+ TORCH_CHECK_EQ(output_size_13 / 2, input_size_2);
81
+
82
+ const int32_t thread_num = cpu_utils::get_max_threads();
83
+ const int32_t w13_input_buffer_size = cpu_utils::round_up<64>(
84
+ gemm_m_tile_size * input_size_13 * sizeof(int8_t));
85
+ const int32_t w2_input_buffer_size =
86
+ cpu_utils::round_up<64>(gemm_m_tile_size * input_size_2 * sizeof(int8_t));
87
+
88
+ const int32_t w13_n_tile_size = [&]() {
89
+ const int64_t cache_size = cpu_utils::get_available_l2_size();
90
+ const int32_t n_size_cache_limit =
91
+ (cache_size - w13_input_buffer_size) /
92
+ (gemm_m_tile_size * sizeof(float) + input_size_13 * sizeof(int8_t));
93
+ const int32_t n_size_thread_limit =
94
+ output_size_13 / std::max(1, thread_num / topk_num);
95
+ const int32_t n_size = cpu_utils::round_down<min_w13_n_tile_size>(
96
+ std::min(n_size_cache_limit, n_size_thread_limit));
97
+ return std::max(n_size, min_w13_n_tile_size);
98
+ }();
99
+
100
+ const int32_t w2_n_tile_size = [&]() {
101
+ const int64_t cache_size = cpu_utils::get_available_l2_size();
102
+ const int32_t n_size_cache_limit =
103
+ (cache_size - w2_input_buffer_size) / (input_size_2 * sizeof(int8_t));
104
+ const int32_t n_size_thread_limit =
105
+ output_size_2 / std::max(1, thread_num / topk_num);
106
+ const int32_t n_size = cpu_utils::round_down<gemm_n_tile_size>(
107
+ std::min(n_size_cache_limit, n_size_thread_limit));
108
+ return std::max(n_size, gemm_n_tile_size);
109
+ }();
110
+
111
+ int32_t common_buffer_offset = 0;
112
+ const int32_t token_num_per_group_buffer_offset = common_buffer_offset;
113
+ common_buffer_offset += cpu_utils::round_up<64>(expert_num * sizeof(int32_t));
114
+ const int32_t cu_token_num_per_group_buffer_offset = common_buffer_offset;
115
+ common_buffer_offset +=
116
+ cpu_utils::round_up<64>((expert_num + 1) * sizeof(int32_t));
117
+ const int32_t expanded_token_num = token_num * topk_num;
118
+ const int32_t expand_token_id_buffer_offset = common_buffer_offset;
119
+ common_buffer_offset +=
120
+ cpu_utils::round_up<64>(expanded_token_num * sizeof(int32_t));
121
+ const int32_t expand_token_id_index_buffer_offset = common_buffer_offset;
122
+ common_buffer_offset +=
123
+ cpu_utils::round_up<64>(expanded_token_num * sizeof(int32_t));
124
+ const int32_t input_quant_buffer_offset = common_buffer_offset;
125
+ common_buffer_offset +=
126
+ cpu_utils::round_up<64>(token_num * input_size_13 * sizeof(int8_t));
127
+ const int32_t input_scale_buffer_offset = common_buffer_offset;
128
+ common_buffer_offset += cpu_utils::round_up<64>(token_num * sizeof(float));
129
+ const int32_t w13_gemm_output_buffer_offset = common_buffer_offset;
130
+ common_buffer_offset += cpu_utils::round_up<64>(
131
+ expanded_token_num * input_size_2 * sizeof(scalar_t));
132
+ const int32_t w13_output_scale_buffer_offset = common_buffer_offset;
133
+ common_buffer_offset +=
134
+ cpu_utils::round_up<64>(expanded_token_num * sizeof(float));
135
+ const int32_t w2_gemm_output_buffer_offset = common_buffer_offset;
136
+ common_buffer_offset += cpu_utils::round_up<64>(
137
+ expanded_token_num * output_size_2 * sizeof(float));
138
+
139
+ int32_t gemm_thread_buffer_offset = 0;
140
+ const int32_t gemm_input_buffer_offset = gemm_thread_buffer_offset;
141
+ gemm_thread_buffer_offset +=
142
+ std::max(w13_input_buffer_size, w2_input_buffer_size);
143
+ const int32_t gemm_output_buffer_offset = gemm_thread_buffer_offset;
144
+ gemm_thread_buffer_offset += cpu_utils::round_up<64>(
145
+ gemm_m_tile_size * std::max(w13_n_tile_size, w2_n_tile_size) *
146
+ sizeof(int32_t));
147
+
148
+ const int32_t ws_output_buffer_offset = 0;
149
+ const int32_t ws_thread_buffer_size =
150
+ cpu_utils::round_up<64>(output_size_2 * sizeof(float));
151
+ const int32_t thread_buffer_size =
152
+ std::max(gemm_thread_buffer_offset, ws_thread_buffer_size);
153
+ const int32_t buffer_size =
154
+ common_buffer_offset + thread_buffer_size * thread_num;
155
+ cpu_utils::ScratchPadManager::get_scratchpad_manager()->realloc(buffer_size);
156
+ uint8_t* common_buffer_start =
157
+ cpu_utils::ScratchPadManager::get_scratchpad_manager()
158
+ ->get_data<uint8_t>();
159
+ uint8_t* thread_buffer_start = common_buffer_start + common_buffer_offset;
160
+
161
+ int32_t* __restrict__ token_num_per_group_buffer = reinterpret_cast<int32_t*>(
162
+ common_buffer_start + token_num_per_group_buffer_offset);
163
+ int32_t* __restrict__ cu_token_num_per_group_buffer =
164
+ reinterpret_cast<int32_t*>(common_buffer_start +
165
+ cu_token_num_per_group_buffer_offset);
166
+ int32_t* __restrict__ expand_token_id_buffer = reinterpret_cast<int32_t*>(
167
+ common_buffer_start + expand_token_id_buffer_offset);
168
+ int32_t* __restrict__ expand_token_id_index_buffer =
169
+ reinterpret_cast<int32_t*>(common_buffer_start +
170
+ expand_token_id_index_buffer_offset);
171
+ int8_t* __restrict__ input_quant_buffer = reinterpret_cast<int8_t*>(
172
+ common_buffer_start + input_quant_buffer_offset);
173
+ float* __restrict__ input_scale_buffer =
174
+ reinterpret_cast<float*>(common_buffer_start + input_scale_buffer_offset);
175
+
176
+ std::memset(token_num_per_group_buffer, 0, expert_num * sizeof(int32_t));
177
+ for (int32_t i = 0; i < expanded_token_num; ++i) {
178
+ ++token_num_per_group_buffer[topk_id[i]];
179
+ }
180
+
181
+ int32_t token_num_sum = 0;
182
+ cu_token_num_per_group_buffer[0] = 0;
183
+ int32_t* token_index_buffer = cu_token_num_per_group_buffer + 1;
184
+ for (int32_t i = 0; i < expert_num; ++i) {
185
+ token_index_buffer[i] = token_num_sum;
186
+ token_num_sum += token_num_per_group_buffer[i];
187
+ }
188
+
189
+ for (int32_t i = 0; i < token_num; ++i) {
190
+ const int32_t* curr_topk_id = topk_id + i * topk_num;
191
+ int32_t* curr_index_buffer = expand_token_id_index_buffer + i * topk_num;
192
+ for (int32_t j = 0; j < topk_num; ++j) {
193
+ const int32_t curr_expert_id = curr_topk_id[j];
194
+ const int32_t curr_index = token_index_buffer[curr_expert_id]++;
195
+ expand_token_id_buffer[curr_index] = i;
196
+ curr_index_buffer[j] = curr_index;
197
+ }
198
+ }
199
+
200
+// quantize inputs
201
+#pragma omp parallel for
202
+ for (int32_t token_idx = 0; token_idx < token_num; ++token_idx) {
203
+ gemm_t::quantize_row(input + token_idx * input_size_13,
204
+ input_quant_buffer + token_idx * input_size_13,
205
+ input_scale_buffer[token_idx], input_size_13);
206
+ }
207
+
208
+ {
209
+ alignas(64) cpu_utils::Counter counter;
210
+ cpu_utils::Counter* counter_ptr = &counter;
211
+
212
+// w13 GEMM + act
213
+#pragma omp parallel for schedule(static, 1)
214
+ for (int32_t thread_id = 0; thread_id < thread_num; ++thread_id) {
215
+ const int32_t task_num_per_expert =
216
+ (output_size_13 + w13_n_tile_size - 1) / w13_n_tile_size;
217
+ const int32_t task_num = task_num_per_expert * expert_num;
218
+ uint8_t* __restrict__ thread_buffer =
219
+ thread_buffer_start + thread_id * thread_buffer_size;
220
+ int8_t* __restrict__ gemm_input_buffer =
221
+ reinterpret_cast<int8_t*>(thread_buffer + gemm_input_buffer_offset);
222
+ float* __restrict__ gemm_output_buffer =
223
+ reinterpret_cast<float*>(thread_buffer + gemm_output_buffer_offset);
224
+ auto* __restrict__ w13_gemm_output_buffer = reinterpret_cast<scalar_t*>(
225
+ common_buffer_start + w13_gemm_output_buffer_offset);
226
+ gemm_t gemm;
227
+
228
+ const int32_t w13_n_group_stride =
229
+ gemm_t::WeightOCGroupSize * input_size_13;
230
+ const int32_t w13_n_tile_stride = gemm_n_tile_size * input_size_13;
231
+
232
+ for (;;) {
233
+ const int32_t task_id = counter_ptr->acquire_counter();
234
+ if (task_id >= task_num) {
235
+ break;
236
+ }
237
+ const int32_t curr_expert_id = task_id / task_num_per_expert;
238
+ const int32_t curr_output_group_id = task_id % task_num_per_expert;
239
+ const int32_t curr_token_num =
240
+ token_num_per_group_buffer[curr_expert_id];
241
+ if (curr_token_num == 0) {
242
+ continue;
243
+ }
244
+
245
+ const int32_t actual_n_tile_size =
246
+ std::min(w13_n_tile_size,
247
+ output_size_13 - curr_output_group_id * w13_n_tile_size);
248
+ const int32_t* __restrict__ curr_expand_token_id_buffer =
249
+ expand_token_id_buffer +
250
+ cu_token_num_per_group_buffer[curr_expert_id];
251
+ scalar_t* __restrict__ curr_w13_gemm_output_buffer =
252
+ w13_gemm_output_buffer +
253
+ cu_token_num_per_group_buffer[curr_expert_id] * input_size_2 +
254
+ curr_output_group_id * w13_n_tile_size / 2;
255
+
256
+ const int8_t* w13_weight_ptr_0 = nullptr;
257
+ const int8_t* w13_weight_ptr_1 = nullptr;
258
+ const float* w13_scale_ptr_0 = nullptr;
259
+ const float* w13_scale_ptr_1 = nullptr;
260
+ scalar_t* w13_bias_ptr_0 = nullptr;
261
+ scalar_t* w13_bias_ptr_1 = nullptr;
262
+ if (act_type == FusedMOEAct::SwigluOAIAndMul) {
263
+ const int32_t output_offset = curr_output_group_id * w13_n_tile_size;
264
+ w13_weight_ptr_0 = w13 +
265
+ curr_expert_id * input_size_13 * output_size_13 +
266
+ output_offset * input_size_13;
267
+ w13_weight_ptr_1 =
268
+ w13_weight_ptr_0 + actual_n_tile_size / 2 * input_size_13;
269
+ w13_scale_ptr_0 =
270
+ w13_scales + curr_expert_id * output_size_13 + output_offset;
271
+ w13_scale_ptr_1 = w13_scale_ptr_0 + actual_n_tile_size / 2;
272
+ if (w13_bias != nullptr) {
273
+ w13_bias_ptr_0 =
274
+ w13_bias + curr_expert_id * output_size_13 + output_offset;
275
+ w13_bias_ptr_1 = w13_bias_ptr_0 + actual_n_tile_size / 2;
276
+ }
277
+ } else {
278
+ const int32_t output_offset =
279
+ curr_output_group_id * (w13_n_tile_size / 2);
280
+ w13_weight_ptr_0 = w13 +
281
+ curr_expert_id * input_size_13 * output_size_13 +
282
+ output_offset * input_size_13;
283
+ w13_weight_ptr_1 =
284
+ w13_weight_ptr_0 + output_size_13 / 2 * input_size_13;
285
+ w13_scale_ptr_0 =
286
+ w13_scales + curr_expert_id * output_size_13 + output_offset;
287
+ w13_scale_ptr_1 = w13_scale_ptr_0 + output_size_13 / 2;
288
+ if (w13_bias != nullptr) {
289
+ w13_bias_ptr_0 =
290
+ w13_bias + curr_expert_id * output_size_13 + output_offset;
291
+ w13_bias_ptr_1 = w13_bias_ptr_0 + output_size_13 / 2;
292
+ }
293
+ }
294
+
295
+ for (int32_t token_idx = 0; token_idx < curr_token_num;
296
+ token_idx += gemm_m_tile_size) {
297
+ const int32_t actual_token_num =
298
+ std::min(gemm_m_tile_size, curr_token_num - token_idx);
299
+ const int8_t* input_rows[gemm_m_tile_size];
300
+ alignas(64) float input_scales[gemm_m_tile_size];
301
+ // gather and pack
302
+ for (int32_t i = 0; i < actual_token_num; ++i) {
303
+ const int32_t curr_token_id = curr_expand_token_id_buffer[i];
304
+ input_rows[i] = input_quant_buffer + curr_token_id * input_size_13;
305
+ input_scales[i] = input_scale_buffer[curr_token_id];
306
+ }
307
+ gemm_t::pack_input_from_rows(input_rows, gemm_input_buffer,
308
+ actual_token_num, input_size_13);
309
+ curr_expand_token_id_buffer += actual_token_num;
310
+
311
+ const int8_t* w13_weight_ptr_0_iter = w13_weight_ptr_0;
312
+ const int8_t* w13_weight_ptr_1_iter = w13_weight_ptr_1;
313
+ const float* w13_scale_ptr_0_iter = w13_scale_ptr_0;
314
+ const float* w13_scale_ptr_1_iter = w13_scale_ptr_1;
315
+ scalar_t* w13_bias_ptr_0_iter = w13_bias_ptr_0;
316
+ scalar_t* w13_bias_ptr_1_iter = w13_bias_ptr_1;
317
+ float* w13_output_buffer_0_iter = gemm_output_buffer;
318
+ float* w13_output_buffer_1_iter =
319
+ gemm_output_buffer + actual_n_tile_size / 2;
320
+
321
+ for (int32_t i = 0; i < actual_n_tile_size;
322
+ i += min_w13_n_tile_size) {
323
+ auto* output_0_int32 =
324
+ reinterpret_cast<int32_t*>(w13_output_buffer_0_iter);
325
+ gemm.gemm(gemm_input_buffer, w13_weight_ptr_0_iter, output_0_int32,
326
+ actual_token_num, input_size_13, w13_n_group_stride,
327
+ actual_n_tile_size);
328
+ gemm_t::dequantize_tile(output_0_int32, w13_output_buffer_0_iter,
329
+ input_scales, w13_scale_ptr_0_iter,
330
+ actual_token_num, gemm_n_tile_size,
331
+ actual_n_tile_size);
332
+ if (w13_bias != nullptr) {
333
+ cpu_micro_gemm::add_bias_epilogue<gemm_n_tile_size>(
334
+ w13_output_buffer_0_iter, w13_output_buffer_0_iter,
335
+ w13_bias_ptr_0_iter, actual_token_num, actual_n_tile_size,
336
+ actual_n_tile_size);
337
+ w13_bias_ptr_0_iter += gemm_n_tile_size;
338
+ }
339
+
340
+ auto* output_1_int32 =
341
+ reinterpret_cast<int32_t*>(w13_output_buffer_1_iter);
342
+ gemm.gemm(gemm_input_buffer, w13_weight_ptr_1_iter, output_1_int32,
343
+ actual_token_num, input_size_13, w13_n_group_stride,
344
+ actual_n_tile_size);
345
+ gemm_t::dequantize_tile(output_1_int32, w13_output_buffer_1_iter,
346
+ input_scales, w13_scale_ptr_1_iter,
347
+ actual_token_num, gemm_n_tile_size,
348
+ actual_n_tile_size);
349
+ if (w13_bias != nullptr) {
350
+ cpu_micro_gemm::add_bias_epilogue<gemm_n_tile_size>(
351
+ w13_output_buffer_1_iter, w13_output_buffer_1_iter,
352
+ w13_bias_ptr_1_iter, actual_token_num, actual_n_tile_size,
353
+ actual_n_tile_size);
354
+ w13_bias_ptr_1_iter += gemm_n_tile_size;
355
+ }
356
+
357
+ w13_weight_ptr_0_iter += w13_n_tile_stride;
358
+ w13_weight_ptr_1_iter += w13_n_tile_stride;
359
+ w13_scale_ptr_0_iter += gemm_n_tile_size;
360
+ w13_scale_ptr_1_iter += gemm_n_tile_size;
361
+ w13_output_buffer_0_iter += gemm_n_tile_size;
362
+ w13_output_buffer_1_iter += gemm_n_tile_size;
363
+ }
364
+
365
+ apply_gated_act(act_type, gemm_output_buffer,
366
+ curr_w13_gemm_output_buffer, actual_token_num,
367
+ actual_n_tile_size, actual_n_tile_size, input_size_2);
368
+ curr_w13_gemm_output_buffer += gemm_m_tile_size * input_size_2;
369
+ }
370
+ }
371
+ }
372
+ }
373
+
374
+ auto* __restrict__ w13_gemm_output_buffer = reinterpret_cast<scalar_t*>(
375
+ common_buffer_start + w13_gemm_output_buffer_offset);
376
+ float* __restrict__ w13_output_scale_buffer = reinterpret_cast<float*>(
377
+ common_buffer_start + w13_output_scale_buffer_offset);
378
+
379
+// quantize w2 inputs - in place
380
+#pragma omp parallel for
381
+ for (int32_t token_idx = 0; token_idx < expanded_token_num; ++token_idx) {
382
+ scalar_t* input_row = w13_gemm_output_buffer + token_idx * input_size_2;
383
+ int8_t* output_row = reinterpret_cast<int8_t*>(input_row);
384
+ gemm_t::quantize_row(input_row, output_row,
385
+ w13_output_scale_buffer[token_idx], input_size_2);
386
+ }
387
+
388
+ {
389
+ alignas(64) cpu_utils::Counter counter;
390
+ cpu_utils::Counter* counter_ptr = &counter;
391
+
392
+// w2 gemm
393
+#pragma omp parallel for schedule(static, 1)
394
+ for (int32_t thread_id = 0; thread_id < thread_num; ++thread_id) {
395
+ const int32_t task_num_per_expert =
396
+ (output_size_2 + w2_n_tile_size - 1) / w2_n_tile_size;
397
+ const int32_t task_num = task_num_per_expert * expert_num;
398
+ uint8_t* __restrict__ thread_buffer =
399
+ thread_buffer_start + thread_id * thread_buffer_size;
400
+ int8_t* __restrict__ gemm_input_buffer =
401
+ reinterpret_cast<int8_t*>(thread_buffer + gemm_input_buffer_offset);
402
+ float* __restrict__ gemm_output_buffer =
403
+ reinterpret_cast<float*>(thread_buffer + gemm_output_buffer_offset);
404
+ float* __restrict__ w2_gemm_output_buffer = reinterpret_cast<float*>(
405
+ common_buffer_start + w2_gemm_output_buffer_offset);
406
+ gemm_t gemm;
407
+
408
+ const int32_t w2_n_group_stride =
409
+ gemm_t::WeightOCGroupSize * input_size_2;
410
+ const int32_t w2_n_tile_stride = gemm_n_tile_size * input_size_2;
411
+
412
+ for (;;) {
413
+ const int32_t task_id = counter_ptr->acquire_counter();
414
+ if (task_id >= task_num) {
415
+ break;
416
+ }
417
+ const int32_t curr_expert_id = task_id / task_num_per_expert;
418
+ const int32_t curr_output_group_id = task_id % task_num_per_expert;
419
+ const int32_t curr_token_num =
420
+ token_num_per_group_buffer[curr_expert_id];
421
+ if (curr_token_num == 0) {
422
+ continue;
423
+ }
424
+
425
+ const int32_t actual_n_tile_size =
426
+ std::min(w2_n_tile_size,
427
+ output_size_2 - curr_output_group_id * w2_n_tile_size);
428
+ scalar_t* __restrict__ curr_w13_gemm_output_buffer =
429
+ w13_gemm_output_buffer +
430
+ cu_token_num_per_group_buffer[curr_expert_id] * input_size_2;
431
+ float* __restrict__ curr_w13_output_scale_buffer =
432
+ w13_output_scale_buffer +
433
+ cu_token_num_per_group_buffer[curr_expert_id];
434
+ float* __restrict__ curr_w2_gemm_output_buffer =
435
+ w2_gemm_output_buffer +
436
+ cu_token_num_per_group_buffer[curr_expert_id] * output_size_2 +
437
+ curr_output_group_id * w2_n_tile_size;
438
+ const int8_t* __restrict__ w2_weight_ptr =
439
+ w2 + curr_expert_id * output_size_2 * input_size_2 +
440
+ curr_output_group_id * w2_n_tile_size * input_size_2;
441
+ const float* __restrict__ w2_scale_ptr =
442
+ w2_scales + curr_expert_id * output_size_2 +
443
+ curr_output_group_id * w2_n_tile_size;
444
+ scalar_t* w2_bias_ptr = nullptr;
445
+ if (w2_bias != nullptr) {
446
+ w2_bias_ptr = w2_bias + curr_expert_id * output_size_2 +
447
+ curr_output_group_id * w2_n_tile_size;
448
+ }
449
+
450
+ for (int32_t token_idx = 0; token_idx < curr_token_num;
451
+ token_idx += gemm_m_tile_size) {
452
+ const int32_t actual_token_num =
453
+ std::min(gemm_m_tile_size, curr_token_num - token_idx);
454
+ const int8_t* input_rows[gemm_m_tile_size];
455
+ alignas(64) float input_scales[gemm_m_tile_size];
456
+ for (int32_t i = 0; i < actual_token_num; ++i) {
457
+ input_rows[i] = reinterpret_cast<const int8_t*>(
458
+ curr_w13_gemm_output_buffer + i * input_size_2);
459
+ input_scales[i] = curr_w13_output_scale_buffer[i];
460
+ }
461
+ gemm_t::pack_input_from_rows(input_rows, gemm_input_buffer,
462
+ actual_token_num, input_size_2);
463
+
464
+ const int8_t* w2_weight_ptr_iter = w2_weight_ptr;
465
+ const float* w2_scale_ptr_iter = w2_scale_ptr;
466
+ scalar_t* w2_bias_ptr_iter = w2_bias_ptr;
467
+ float* curr_w2_gemm_output_buffer_iter = curr_w2_gemm_output_buffer;
468
+ for (int32_t i = 0; i < actual_n_tile_size; i += gemm_n_tile_size) {
469
+ auto* output_int32 = reinterpret_cast<int32_t*>(gemm_output_buffer);
470
+ gemm.gemm(gemm_input_buffer, w2_weight_ptr_iter, output_int32,
471
+ actual_token_num, input_size_2, w2_n_group_stride,
472
+ gemm_n_tile_size);
473
+ gemm_t::dequantize_tile(output_int32, gemm_output_buffer,
474
+ input_scales, w2_scale_ptr_iter,
475
+ actual_token_num, gemm_n_tile_size,
476
+ gemm_n_tile_size);
477
+ if (w2_bias != nullptr) {
478
+ cpu_micro_gemm::add_bias_epilogue<gemm_n_tile_size>(
479
+ gemm_output_buffer, gemm_output_buffer, w2_bias_ptr_iter,
480
+ actual_token_num, gemm_n_tile_size, gemm_n_tile_size);
481
+ w2_bias_ptr_iter += gemm_n_tile_size;
482
+ }
483
+ for (int32_t m_idx = 0; m_idx < actual_token_num; ++m_idx) {
484
+ std::memcpy(
485
+ curr_w2_gemm_output_buffer_iter + m_idx * output_size_2,
486
+ gemm_output_buffer + m_idx * gemm_n_tile_size,
487
+ gemm_n_tile_size * sizeof(float));
488
+ }
489
+
490
+ w2_weight_ptr_iter += w2_n_tile_stride;
491
+ w2_scale_ptr_iter += gemm_n_tile_size;
492
+ curr_w2_gemm_output_buffer_iter += gemm_n_tile_size;
493
+ }
494
+
495
+ curr_w13_gemm_output_buffer += gemm_m_tile_size * input_size_2;
496
+ curr_w13_output_scale_buffer += gemm_m_tile_size;
497
+ curr_w2_gemm_output_buffer += gemm_m_tile_size * output_size_2;
498
+ }
499
+ }
500
+ }
501
+ }
502
+
503
+ {
504
+ alignas(64) cpu_utils::Counter counter;
505
+ cpu_utils::Counter* counter_ptr = &counter;
506
+
507
+#pragma omp parallel for schedule(static, 1)
508
+ for (int32_t thread_id = 0; thread_id < thread_num; ++thread_id) {
509
+ uint8_t* __restrict__ thread_buffer =
510
+ thread_buffer_start + thread_id * thread_buffer_size;
511
+ float* __restrict__ ws_output_buffer =
512
+ reinterpret_cast<float*>(thread_buffer + ws_output_buffer_offset);
513
+ float* __restrict__ w2_gemm_output_buffer = reinterpret_cast<float*>(
514
+ common_buffer_start + w2_gemm_output_buffer_offset);
515
+
516
+ for (;;) {
517
+ const int32_t token_id = counter_ptr->acquire_counter();
518
+ if (token_id >= token_num) {
519
+ break;
520
+ }
521
+ int32_t* __restrict__ curr_expand_token_id_index_buffer =
522
+ expand_token_id_index_buffer + token_id * topk_num;
523
+ const float* __restrict__ curr_weight =
524
+ topk_weights + token_id * topk_num;
525
+ const float first_weight = skip_weighted ? 1.0f : curr_weight[0];
526
+ scalar_t* __restrict__ curr_output_buffer =
527
+ output + token_id * output_size_2;
528
+
529
+ if (topk_num > 1) {
530
+ int32_t w2_output_idx = curr_expand_token_id_index_buffer[0];
531
+ float* w2_output_iter =
532
+ w2_gemm_output_buffer + w2_output_idx * output_size_2;
533
+ float* ws_output_buffer_iter = ws_output_buffer;
534
+ vec_op::FP32Vec16 weight_vec(first_weight);
535
+ for (int32_t i = 0; i < output_size_2; i += 16) {
536
+ vec_op::FP32Vec16 vec(w2_output_iter);
537
+ (vec * weight_vec).save(ws_output_buffer_iter);
538
+ w2_output_iter += 16;
539
+ ws_output_buffer_iter += 16;
540
+ }
541
+
542
+ for (int32_t idx = 1; idx < topk_num - 1; ++idx) {
543
+ w2_output_idx = curr_expand_token_id_index_buffer[idx];
544
+ w2_output_iter =
545
+ w2_gemm_output_buffer + w2_output_idx * output_size_2;
546
+ ws_output_buffer_iter = ws_output_buffer;
547
+ weight_vec = vec_op::FP32Vec16(curr_weight[idx]);
548
+ for (int32_t i = 0; i < output_size_2; i += 16) {
549
+ vec_op::FP32Vec16 vec(w2_output_iter);
550
+ vec_op::FP32Vec16 sum(ws_output_buffer_iter);
551
+ (sum + vec * weight_vec).save(ws_output_buffer_iter);
552
+ w2_output_iter += 16;
553
+ ws_output_buffer_iter += 16;
554
+ }
555
+ }
556
+
557
+ const int32_t last_idx = topk_num - 1;
558
+ w2_output_idx = curr_expand_token_id_index_buffer[last_idx];
559
+ w2_output_iter =
560
+ w2_gemm_output_buffer + w2_output_idx * output_size_2;
561
+ ws_output_buffer_iter = ws_output_buffer;
562
+ scalar_t* curr_output_buffer_iter = curr_output_buffer;
563
+ weight_vec = vec_op::FP32Vec16(curr_weight[last_idx]);
564
+ for (int32_t i = 0; i < output_size_2; i += 16) {
565
+ vec_op::FP32Vec16 vec(w2_output_iter);
566
+ vec_op::FP32Vec16 sum(ws_output_buffer_iter);
567
+ scalar_vec_t(sum + vec * weight_vec).save(curr_output_buffer_iter);
568
+ w2_output_iter += 16;
569
+ ws_output_buffer_iter += 16;
570
+ curr_output_buffer_iter += 16;
571
+ }
572
+ } else {
573
+ const int32_t w2_output_idx = curr_expand_token_id_index_buffer[0];
574
+ float* w2_output_iter =
575
+ w2_gemm_output_buffer + w2_output_idx * output_size_2;
576
+ scalar_t* curr_output_buffer_iter = curr_output_buffer;
577
+ vec_op::FP32Vec16 weight_vec(first_weight);
578
+ for (int32_t i = 0; i < output_size_2; i += 16) {
579
+ vec_op::FP32Vec16 vec(w2_output_iter);
580
+ scalar_vec_t(vec * weight_vec).save(curr_output_buffer_iter);
581
+ w2_output_iter += 16;
582
+ curr_output_buffer_iter += 16;
583
+ }
584
+ }
585
+ }
586
+ }
587
+ }
588
+}
589
+} // namespace
590
+
591
+void prepack_moe_weight_int8(
592
+ const torch::Tensor& weight, // [expert_num, output_size, input_size]
593
+ torch::Tensor& packed_weight, const std::string& isa) {
594
+ TORCH_CHECK(weight.is_contiguous());
595
+ const int32_t expert_num = weight.size(0);
596
+ const int32_t output_size = weight.size(1);
597
+ const int32_t input_size = weight.size(2);
598
+ const int64_t expert_stride = weight.stride(0);
599
+ const cpu_utils::ISA isa_type = cpu_utils::get_isa(isa);
600
+ TORCH_CHECK_EQ(output_size % 32, 0);
601
+
602
+ CPU_INT8_ISA_DISPATCH_IMPL(isa_type, c10::BFloat16, [&]() {
603
+ TORCH_CHECK_EQ(input_size % gemm_t::K, 0);
604
+ prepack_moe_weight_int8_impl<gemm_t>(
605
+ weight.data_ptr<int8_t>(), packed_weight.data_ptr<int8_t>(), expert_num,
606
+ output_size, input_size, expert_stride);
607
+ });
608
+}
609
+
610
+void cpu_fused_moe_int8(torch::Tensor& output, const torch::Tensor& input,
611
+ const torch::Tensor& w13, const torch::Tensor& w2,
612
+ const torch::Tensor& w13_scale,
613
+ const torch::Tensor& w2_scale,
614
+ const std::optional<torch::Tensor>& w13_bias,
615
+ const std::optional<torch::Tensor>& w2_bias,
616
+ const torch::Tensor& topk_weights,
617
+ const torch::Tensor& topk_id, const bool skip_weighted,
618
+ const std::string& act, const std::string& isa) {
619
+ const int32_t token_num = input.size(0);
620
+ const int32_t input_size_13 = input.size(1);
621
+ const int64_t input_stride = input.stride(0);
622
+ TORCH_CHECK_EQ(input_stride, input_size_13);
623
+ const int32_t expert_num = w13.size(0);
624
+ const int32_t output_size_13 = w13.size(1);
625
+ const int32_t input_size_2 = w2.size(2);
626
+ const int32_t output_size_2 = w2.size(1);
627
+ const int32_t topk_num = topk_id.size(1);
628
+ const FusedMOEAct act_type = cpu_fused_moe_utils::get_act_type(act);
629
+ const cpu_utils::ISA isa_type = cpu_utils::get_isa(isa);
630
+ TORCH_CHECK(!skip_weighted || topk_num == 1,
631
+ "skip_weighted is only supported for topk=1 on CPU");
632
+
633
+ VLLM_DISPATCH_FLOATING_TYPES(
634
+ input.scalar_type(), "cpu_fused_moe_int8", [&]() {
635
+ CPU_INT8_ISA_DISPATCH_IMPL(isa_type, scalar_t, [&]() {
636
+ fused_moe_int8_impl<scalar_t, gemm_t>(
637
+ output.data_ptr<scalar_t>(), input.data_ptr<scalar_t>(),
638
+ w13.data_ptr<int8_t>(), w2.data_ptr<int8_t>(),
639
+ w13_scale.data_ptr<float>(), w2_scale.data_ptr<float>(),
640
+ w13_bias.has_value() ? w13_bias->data_ptr<scalar_t>() : nullptr,
641
+ w2_bias.has_value() ? w2_bias->data_ptr<scalar_t>() : nullptr,
642
+ topk_weights.data_ptr<float>(), topk_id.data_ptr<int32_t>(),
643
+ act_type, token_num, expert_num, topk_num, input_size_13,
644
+ output_size_13, input_size_2, output_size_2, skip_weighted);
645
+ });
646
+ });
647
+}
csrc/cpu/micro_gemm/cpu_micro_gemm_impl.hpp
+3
@@ -31,6 +31,9 @@ class MicroGemm {
31
}
32
};
33
34
+template <cpu_utils::ISA isa, typename scalar_t>
35
+class MicroGemmINT8;
36
+
37
template <int32_t n_size, typename scalar_t>
38
FORCE_INLINE void default_epilogue(float* __restrict__ c_ptr,
39
scalar_t* __restrict__ d_ptr,
csrc/cpu/micro_gemm/cpu_micro_gemm_int8_neon.hpp
new
+424
@@ -0,0 +1,424 @@
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
csrc/cpu/micro_gemm/cpu_micro_gemm_neon.hpp
+14
-8
@@ -1,3 +1,6 @@
1
+// SPDX-License-Identifier: Apache-2.0
2
+// SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
+
4
#ifndef CPU_MICRO_GEMM_NEON_HPP
5
#define CPU_MICRO_GEMM_NEON_HPP
6
@@ -16,9 +19,6 @@ namespace {
19
constexpr int32_t K = 4;
20
constexpr int32_t Cols = 2;
21
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) {
@@ -132,7 +132,7 @@ FORCE_INLINE void gemm_micro_bfmmla_8x8_packed_a(
132
acc6767 = vbfmmlaq_f32(acc6767, a_tile67, b_tile67);
133
134
a_tile += 4 * TileSize;
135
- b_tile += Nr * K;
135
+ b_tile += 4 * TileSize;
136
}
137
138
store_acc_rowpair(acc0101, acc0123, acc0145, acc0167, c_ptr, ldc,
@@ -205,8 +205,8 @@ FORCE_INLINE void gemm_micro_bfmmla_4x16_packed_a(
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;
208
+ b_tile0 += 4 * TileSize;
209
+ b_tile1 += 4 * TileSize;
210
}
211
212
store_acc_rowpair(acc0101, acc0123, acc0145, acc0167, c_ptr, ldc, m_rows_01);
@@ -223,6 +223,9 @@ FORCE_INLINE void gemm_micro_bfmmla_4x16_packed_a(
223
template <typename scalar_t>
224
class MicroGemm<cpu_utils::ISA::NEON, scalar_t> {
225
public:
226
+ static constexpr int32_t Mr = 8;
227
+ static constexpr int32_t Nr = 8;
228
+ static constexpr int32_t NrGemv = 16;
229
static constexpr int32_t MaxMSize = 8;
230
static constexpr int32_t NSize = 32;
231
static constexpr int32_t WeightOCGroupSize = Nr;
@@ -246,6 +249,9 @@ class MicroGemm<cpu_utils::ISA::NEON, c10::BFloat16> {
249
public:
250
using scalar_t = c10::BFloat16;
251
252
+ static constexpr int32_t Mr = 8;
253
+ static constexpr int32_t Nr = 8;
254
+ static constexpr int32_t NrGemv = 16;
255
static constexpr int32_t MaxMSize = 8;
256
static constexpr int32_t NSize = 32;
257
static constexpr int32_t WeightOCGroupSize = Nr;
@@ -253,7 +259,7 @@ class MicroGemm<cpu_utils::ISA::NEON, c10::BFloat16> {
259
260
public:
261
// physical layout [
256
- // M / 8; Mr is 8
262
+ // M / (8 or 4); Mr is 8 or 4
263
// K / 4; K for bfmmla is 4
264
// 4, ; 4 row-pairs for each 8 rows
265
// 2, ; row-pair is 2 rows
@@ -439,7 +445,7 @@ class MicroGemm<cpu_utils::ISA::NEON, c10::BFloat16> {
445
(void)lda; // A is packed, so lda is not needed
446
TORCH_CHECK_EQ(k % K, 0);
447
442
- for (int32_t n_idx = 0; n_idx < NSize; n_idx += Nr_gemv) {
448
+ for (int32_t n_idx = 0; n_idx < NSize; n_idx += NrGemv) {
449
const bfloat16_t* __restrict__ b_panel =
450
reinterpret_cast<const bfloat16_t*>(b_ptr) + n_idx * k;
451
csrc/cpu/torch_bindings.cpp
+31
-3
@@ -207,6 +207,20 @@ void cpu_fused_moe(torch::Tensor& output, const torch::Tensor& input,
207
const torch::Tensor& topk_id, const bool skip_weighted,
208
const std::string& act, const std::string& isa);
209
210
+void prepack_moe_weight_int8(const torch::Tensor& weight,
211
+ torch::Tensor& packed_weight,
212
+ const std::string& isa);
213
+
214
+void cpu_fused_moe_int8(torch::Tensor& output, const torch::Tensor& input,
215
+ const torch::Tensor& w13, const torch::Tensor& w2,
216
+ const torch::Tensor& w13_scale,
217
+ const torch::Tensor& w2_scale,
218
+ const std::optional<torch::Tensor>& w13_bias,
219
+ const std::optional<torch::Tensor>& w2_bias,
220
+ const torch::Tensor& topk_weights,
221
+ const torch::Tensor& topk_id, const bool skip_weighted,
222
+ const std::string& act, const std::string& isa);
223
+
224
void compute_slot_mapping_kernel_impl(const torch::Tensor query_start_loc,
225
const torch::Tensor positions,
226
const torch::Tensor block_table,
@@ -596,8 +610,7 @@ TORCH_LIBRARY_EXPAND(TORCH_EXTENSION_NAME, ops) {
610
#endif
611
612
// fused moe
599
-#if defined(__AVX512F__) || \
600
- (defined(__aarch64__) && !defined(__APPLE__) && defined(ARM_BF16_SUPPORT))
613
+#if defined(__AVX512F__) || (defined(ARM_BF16_SUPPORT) && !defined(__APPLE__))
614
ops.def(
615
"prepack_moe_weight(Tensor weight, Tensor(a1!) packed_weight, str isa) "
616
"-> ()");
@@ -608,7 +621,22 @@ TORCH_LIBRARY_EXPAND(TORCH_EXTENSION_NAME, ops) {
621
"bool skip_weighted, "
622
"str act, str isa) -> ()");
623
ops.impl("cpu_fused_moe", torch::kCPU, &cpu_fused_moe);
611
-#endif
624
+#endif // #if defined(__AVX512F__) || (defined(ARM_BF16_SUPPORT) &&
625
+ // !defined(__APPLE__))
626
+#if defined(ARM_I8MM_SUPPORT) && defined(ARM_BF16_SUPPORT) && \
627
+ !defined(__APPLE__)
628
+ ops.def(
629
+ "prepack_moe_weight_int8(Tensor weight, Tensor(a1!) packed_weight, "
630
+ "str isa) -> ()");
631
+ ops.impl("prepack_moe_weight_int8", torch::kCPU, &prepack_moe_weight_int8);
632
+ ops.def(
633
+ "cpu_fused_moe_int8(Tensor(a0!) output, Tensor input, Tensor w13, "
634
+ "Tensor w2, Tensor w13_scale, Tensor w2_scale, Tensor? w13_bias, "
635
+ "Tensor? w2_bias, Tensor topk_weights, Tensor topk_id, bool "
636
+ "skip_weighted, str act, str isa) -> ()");
637
+ ops.impl("cpu_fused_moe_int8", torch::kCPU, &cpu_fused_moe_int8);
638
+#endif // #if defined(ARM_I8MM_SUPPORT) && defined(ARM_BF16_SUPPORT) &&
639
+ // !defined(__APPLE__)
640
ops.def(
641
"mla_decode_kvcache("
642
" Tensor! out, Tensor query, Tensor kv_cache,"
tests/kernels/moe/test_cpu_fused_moe.py
+171
-1
@@ -7,10 +7,14 @@ import torch
7
from tests.kernels.allclose_default import get_default_atol, get_default_rtol
8
from vllm._custom_ops import (
9
cpu_fused_moe,
10
+ cpu_fused_moe_int8,
11
cpu_prepack_moe_weight,
12
+ cpu_prepack_moe_weight_int8,
13
)
14
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
15
+from vllm.model_executor.layers.fused_moe.cpu_fused_moe import (
16
+ _CPU_MOE_ACT_FN,
17
+)
18
from vllm.platforms import CpuArchEnum, current_platform
19
from vllm.utils.torch_utils import set_random_seed
20
@@ -101,6 +105,90 @@ def ref_fused_moe(
105
return final_out
106
107
108
+def quantize_per_channel(
109
+ weight: torch.Tensor,
110
+) -> tuple[torch.Tensor, torch.Tensor]:
111
+ """Symmetrically quantize each weight output channel."""
112
+ weight_fp32 = weight.float()
113
+ scale = weight_fp32.abs().amax(dim=-1).clamp_min(1e-12) / 127.0
114
+ quantized = (
115
+ (weight_fp32 / scale.unsqueeze(-1)).round().clamp(-127, 127).to(torch.int8)
116
+ )
117
+ return quantized, scale
118
+
119
+
120
+def quantize_per_token(
121
+ input: torch.Tensor,
122
+) -> tuple[torch.Tensor, torch.Tensor]:
123
+ """Symmetrically quantize each input token."""
124
+ input_fp32 = input.float()
125
+ scale = input_fp32.abs().amax(dim=-1, keepdim=True).clamp_min(1e-7) / 127.0
126
+ quantized = (input_fp32 / scale).round().clamp(-127, 127).to(torch.int8)
127
+ return quantized, scale
128
+
129
+
130
+def ref_fused_moe_int8(
131
+ input: torch.Tensor,
132
+ w13: torch.Tensor,
133
+ w2: torch.Tensor,
134
+ w13_scale: torch.Tensor,
135
+ w2_scale: torch.Tensor,
136
+ w13_bias: torch.Tensor | None,
137
+ w2_bias: torch.Tensor | None,
138
+ topk_weights: torch.Tensor,
139
+ topk_ids: torch.Tensor,
140
+ activation: MoEActivation,
141
+) -> torch.Tensor:
142
+ """Reference the two dynamically quantized INT8 GEMMs."""
143
+ input_int8, input_scale = quantize_per_token(input)
144
+ expert_num = w13.size(0)
145
+
146
+ counts = topk_ids.new_zeros((topk_ids.size(0), expert_num))
147
+ counts.scatter_(1, topk_ids.to(torch.int64), 1)
148
+ tokens_per_expert = counts.sum(dim=0).tolist()
149
+ sorted_route_ids = topk_ids.view(-1).argsort()
150
+ sorted_token_ids = sorted_route_ids // topk_ids.size(1)
151
+
152
+ outputs = []
153
+ start_idx = 0
154
+ for expert_idx, token_count in enumerate(tokens_per_expert):
155
+ end_idx = start_idx + token_count
156
+ if token_count == 0:
157
+ continue
158
+
159
+ token_ids = sorted_token_ids[start_idx:end_idx]
160
+ gate_up = torch.matmul(
161
+ input_int8[token_ids].float(),
162
+ w13[expert_idx].float().T,
163
+ )
164
+ gate_up *= input_scale[token_ids] * w13_scale[expert_idx]
165
+ if w13_bias is not None:
166
+ gate_up += w13_bias[expert_idx].float()
167
+
168
+ intermediate = _CPU_MOE_ACT_FN[activation](gate_up).to(input.dtype)
169
+ intermediate_int8, intermediate_scale = quantize_per_token(intermediate)
170
+ output = torch.matmul(
171
+ intermediate_int8.float(),
172
+ w2[expert_idx].float().T,
173
+ )
174
+ output *= intermediate_scale * w2_scale[expert_idx]
175
+ if w2_bias is not None:
176
+ output += w2_bias[expert_idx].float()
177
+
178
+ outputs.append(output)
179
+ start_idx = end_idx
180
+
181
+ sorted_output = torch.cat(outputs, dim=0)
182
+ routed_output = torch.empty_like(sorted_output)
183
+ routed_output[sorted_route_ids] = sorted_output
184
+ return (
185
+ routed_output.view(*topk_ids.shape, -1)
186
+ .mul_(topk_weights.unsqueeze(-1))
187
+ .sum(dim=1)
188
+ .to(input.dtype)
189
+ )
190
+
191
+
192
@pytest.mark.parametrize("batch_size", BATCH_SIZE)
193
@pytest.mark.parametrize("expert_num", EXPERT_NUM)
194
@pytest.mark.parametrize("hidden_size", HIDDEN_DIM)
@@ -176,3 +264,85 @@ def test_cpu_fused_moe(
264
torch.testing.assert_close(output, ref_output, atol=atol, rtol=rtol),
265
f"{torch.max(torch.abs(output - ref_output))}",
266
)
267
+
268
+
269
+@pytest.mark.skipif(
270
+ current_platform.get_cpu_architecture() != CpuArchEnum.ARM,
271
+ reason="Requires Arm CPU",
272
+)
273
+@pytest.mark.parametrize("batch_size", BATCH_SIZE)
274
+@pytest.mark.parametrize("expert_num", EXPERT_NUM)
275
+@pytest.mark.parametrize("hidden_size", HIDDEN_DIM)
276
+@pytest.mark.parametrize("intermediate_size", INTERMEDIATE_DIM)
277
+@pytest.mark.parametrize("use_bias", USE_BIAS)
278
+@pytest.mark.parametrize("dtype", [torch.float32, torch.float16, torch.bfloat16])
279
+@pytest.mark.parametrize("act", ACT)
280
+@pytest.mark.parametrize("isa", ["neon"])
281
+def test_cpu_fused_moe_int8(
282
+ batch_size: int,
283
+ expert_num: int,
284
+ hidden_size: int,
285
+ intermediate_size: int,
286
+ use_bias: bool,
287
+ dtype: torch.dtype,
288
+ act: MoEActivation,
289
+ isa: str,
290
+):
291
+ set_random_seed(0)
292
+ topk_num = max(expert_num // 2, 1)
293
+ up_dim = 2 * intermediate_size
294
+
295
+ input = torch.randn((batch_size, hidden_size), dtype=dtype) / (
296
+ 0.5 * hidden_size**0.5
297
+ )
298
+ w13_fp = torch.randn((expert_num, up_dim, hidden_size), dtype=dtype) / (
299
+ 0.5 * hidden_size**0.5
300
+ )
301
+ w2_fp = torch.randn((expert_num, hidden_size, intermediate_size), dtype=dtype) / (
302
+ 0.5 * intermediate_size**0.5
303
+ )
304
+ w13, w13_scale = quantize_per_channel(w13_fp)
305
+ w2, w2_scale = quantize_per_channel(w2_fp)
306
+
307
+ w13_bias = None
308
+ w2_bias = None
309
+ if use_bias:
310
+ w13_bias = torch.randn((expert_num, up_dim), dtype=dtype) / (0.5 * up_dim**0.5)
311
+ w2_bias = torch.randn((expert_num, hidden_size), dtype=dtype) / (
312
+ 0.5 * hidden_size**0.5
313
+ )
314
+
315
+ router_logits = torch.randn((batch_size, expert_num), dtype=dtype)
316
+ score = torch.softmax(router_logits, dim=-1, dtype=torch.float32)
317
+ topk_weights, topk_ids = torch.topk(score, topk_num)
318
+ topk_ids = topk_ids.to(torch.int32)
319
+
320
+ ref_output = ref_fused_moe_int8(
321
+ input,
322
+ w13,
323
+ w2,
324
+ w13_scale,
325
+ w2_scale,
326
+ w13_bias,
327
+ w2_bias,
328
+ topk_weights,
329
+ topk_ids,
330
+ act,
331
+ )
332
+ packed_w13 = cpu_prepack_moe_weight_int8(w13, isa)
333
+ packed_w2 = cpu_prepack_moe_weight_int8(w2, isa)
334
+ output = cpu_fused_moe_int8(
335
+ input,
336
+ packed_w13,
337
+ packed_w2,
338
+ w13_scale,
339
+ w2_scale,
340
+ w13_bias,
341
+ w2_bias,
342
+ topk_weights,
343
+ topk_ids,
344
+ act.value,
345
+ isa,
346
+ )
347
+
348
+ torch.testing.assert_close(output, ref_output, atol=2e-2, rtol=2e-2)
vllm/_custom_ops.py
+42
@@ -3787,6 +3787,15 @@ def cpu_prepack_moe_weight(
3787
return output
3788
3789
3790
+def cpu_prepack_moe_weight_int8(
3791
+ weight: torch.Tensor,
3792
+ isa: str,
3793
+) -> torch.Tensor:
3794
+ output = torch.empty_like(weight)
3795
+ torch.ops._C.prepack_moe_weight_int8(weight, output, isa)
3796
+ return output
3797
+
3798
+
3799
def cpu_fused_moe(
3800
input: torch.Tensor,
3801
w13: torch.Tensor,
@@ -3816,6 +3825,39 @@ def cpu_fused_moe(
3825
return output
3826
3827
3828
+def cpu_fused_moe_int8(
3829
+ input: torch.Tensor,
3830
+ w13: torch.Tensor,
3831
+ w2: torch.Tensor,
3832
+ w13_scale: torch.Tensor,
3833
+ w2_scale: torch.Tensor,
3834
+ w13_bias: torch.Tensor | None,
3835
+ w2_bias: torch.Tensor | None,
3836
+ topk_weights: torch.Tensor,
3837
+ topk_ids: torch.Tensor,
3838
+ act: str,
3839
+ isa: str,
3840
+ skip_weighted: bool = False,
3841
+) -> torch.Tensor:
3842
+ output = torch.empty_like(input)
3843
+ torch.ops._C.cpu_fused_moe_int8(
3844
+ output,
3845
+ input,
3846
+ w13,
3847
+ w2,
3848
+ w13_scale,
3849
+ w2_scale,
3850
+ w13_bias,
3851
+ w2_bias,
3852
+ topk_weights,
3853
+ topk_ids,
3854
+ skip_weighted,
3855
+ act,
3856
+ isa,
3857
+ )
3858
+ return output
3859
+
3860
+
3861
if hasattr(torch.ops._qutlass_C, "matmul_mxf4_bf16_tn"):
3862
3863
@register_fake("_qutlass_C::matmul_mxf4_bf16_tn")
vllm/model_executor/layers/fused_moe/experts/cpu_moe.py
+179
-3
@@ -9,6 +9,8 @@ from vllm._custom_ops import (
9
CPUQuantAlgo,
10
CPUQuantMethod,
11
convert_weight_packed_scale_zp,
12
+ cpu_fused_moe_int8,
13
+ cpu_prepack_moe_weight_int8,
14
fused_experts_cpu,
15
)
16
from vllm.model_executor.layers.fused_moe.activation import MoEActivation
@@ -27,7 +29,7 @@ from vllm.model_executor.layers.quantization.utils.quant_utils import (
29
kInt8StaticChannelSym,
30
kMxfp4Static,
31
)
30
-from vllm.platforms import current_platform
32
+from vllm.platforms import CpuArchEnum, current_platform
33
34
# ===========================================================================
35
# FP8 W8A16 MoE
@@ -556,7 +558,14 @@ def prepare_int8_moe_layer_for_cpu(
558
w13: torch.Tensor,
559
w2: torch.Tensor,
560
) -> tuple[torch.Tensor, torch.Tensor]:
559
- """VNNI-prepack INT8 MoE weights for CPU kernel."""
561
+ """Prepack INT8 MoE weights for the current CPU architecture."""
562
+ # SMMLA packing for AArch64
563
+ if current_platform.get_cpu_architecture() == CpuArchEnum.ARM:
564
+ return (
565
+ cpu_prepack_moe_weight_int8(w13, "neon"),
566
+ cpu_prepack_moe_weight_int8(w2, "neon"),
567
+ )
568
+ # VNNI packing for x86
569
packed_w13 = torch.ops._C.convert_weight_packed(w13)
570
packed_w2 = torch.ops._C.convert_weight_packed(w2)
571
return packed_w13, packed_w2
@@ -586,7 +595,10 @@ class CPUExpertsInt8(mk.FusedMoEExpertsMonolithic):
595
596
@staticmethod
597
def _supports_current_device() -> bool:
589
- return current_platform.is_cpu()
598
+ return (
599
+ current_platform.is_cpu()
600
+ and current_platform.get_cpu_architecture() == CpuArchEnum.X86
601
+ )
602
603
@staticmethod
604
def _supports_no_act_and_mul() -> bool:
@@ -701,3 +713,167 @@ class CPUExpertsInt8(mk.FusedMoEExpertsMonolithic):
713
None, # limit
714
True, # is_vnni
715
)
716
+
717
+
718
+class ArmCPUExpertsInt8(mk.FusedMoEExpertsMonolithic):
719
+ """Arm INT8 MoE with per-token activation and channelwise weight quantization."""
720
+
721
+ @property
722
+ def expects_unquantized_inputs(self) -> bool:
723
+ return True
724
+
725
+ @staticmethod
726
+ def activation_format() -> mk.FusedMoEActivationFormat:
727
+ return mk.FusedMoEActivationFormat.Standard
728
+
729
+ @staticmethod
730
+ def is_supported_config(
731
+ cls: type[mk.FusedMoEExperts],
732
+ moe_config: FusedMoEConfig,
733
+ weight_key: QuantKey | None,
734
+ activation_key: QuantKey | None,
735
+ activation_format: mk.FusedMoEActivationFormat,
736
+ ) -> tuple[bool, str | None]:
737
+ supported, reason = mk.FusedMoEExperts.is_supported_config(
738
+ cls,
739
+ moe_config,
740
+ weight_key,
741
+ activation_key,
742
+ activation_format,
743
+ )
744
+ if not supported:
745
+ return supported, reason
746
+ if moe_config.in_dtype not in (
747
+ torch.float32,
748
+ torch.float16,
749
+ torch.bfloat16,
750
+ ):
751
+ return False, "kernel requires float32, float16, or bfloat16 activations"
752
+ if moe_config.hidden_dim % 32 != 0:
753
+ return False, "kernel requires hidden dim divisible by 32"
754
+ if moe_config.intermediate_size_per_partition % 32 != 0:
755
+ return False, "kernel requires intermediate dim divisible by 32"
756
+ return True, None
757
+
758
+ @staticmethod
759
+ def _supports_current_device() -> bool:
760
+ return (
761
+ current_platform.is_cpu()
762
+ and current_platform.get_cpu_architecture() == CpuArchEnum.ARM
763
+ and hasattr(torch.ops._C, "cpu_fused_moe_int8")
764
+ )
765
+
766
+ @staticmethod
767
+ def _supports_no_act_and_mul() -> bool:
768
+ return False
769
+
770
+ @staticmethod
771
+ def _supports_activation(activation: MoEActivation) -> bool:
772
+ return activation in (
773
+ MoEActivation.SILU,
774
+ MoEActivation.SWIGLUOAI,
775
+ MoEActivation.GELU,
776
+ MoEActivation.GELU_TANH,
777
+ )
778
+
779
+ @staticmethod
780
+ def _supports_parallel_config(
781
+ moe_parallel_config: FusedMoEParallelConfig,
782
+ ) -> bool:
783
+ return not moe_parallel_config.use_ep
784
+
785
+ @staticmethod
786
+ def _supports_quant_scheme(
787
+ weight_key: QuantKey | None,
788
+ activation_key: QuantKey | None,
789
+ ) -> bool:
790
+ return (weight_key, activation_key) == (
791
+ kInt8StaticChannelSym,
792
+ kInt8DynamicTokenSym,
793
+ )
794
+
795
+ @staticmethod
796
+ def _supports_routing_method(
797
+ routing_method: RoutingMethodType,
798
+ weight_key: QuantKey | None,
799
+ activation_key: QuantKey | None,
800
+ ) -> bool:
801
+ return routing_method in [
802
+ RoutingMethodType.Default,
803
+ RoutingMethodType.Renormalize,
804
+ RoutingMethodType.RenormalizeNaive,
805
+ ]
806
+
807
+ @staticmethod
808
+ def _supports_router_logits_dtype(
809
+ router_logits_dtype: torch.dtype | None,
810
+ routing_method: RoutingMethodType,
811
+ ) -> bool:
812
+ return True
813
+
814
+ def process_weights_after_loading(self, layer: torch.nn.Module) -> None:
815
+ from vllm.model_executor.utils import replace_parameter
816
+
817
+ w13, w2 = prepare_int8_moe_layer_for_cpu(layer.w13_weight, layer.w2_weight)
818
+ replace_parameter(layer, "w13_weight", w13)
819
+ replace_parameter(layer, "w2_weight", w2)
820
+
821
+ def apply(
822
+ self,
823
+ hidden_states: torch.Tensor,
824
+ w1: torch.Tensor,
825
+ w2: torch.Tensor,
826
+ router_logits: torch.Tensor,
827
+ activation: MoEActivation,
828
+ global_num_experts: int,
829
+ expert_map: torch.Tensor | None,
830
+ a1q_scale: torch.Tensor | None,
831
+ apply_router_weight_on_input: bool,
832
+ num_expert_group: int | None = None,
833
+ e_score_correction_bias: torch.Tensor | None = None,
834
+ routed_scaling_factor: float | None = None,
835
+ topk_group: int | None = None,
836
+ ) -> torch.Tensor:
837
+ from vllm.model_executor.layers.fused_moe.cpu_fused_moe import (
838
+ select_experts,
839
+ )
840
+
841
+ topk_weights, topk_ids = select_experts(
842
+ hidden_states=hidden_states,
843
+ router_logits=router_logits,
844
+ use_grouped_topk=num_expert_group is not None,
845
+ top_k=self.moe_config.experts_per_token,
846
+ renormalize=self.moe_config.routing_method
847
+ in (
848
+ RoutingMethodType.Renormalize,
849
+ RoutingMethodType.RenormalizeNaive,
850
+ ),
851
+ topk_group=topk_group,
852
+ num_expert_group=num_expert_group,
853
+ scoring_func="softmax",
854
+ routed_scaling_factor=(
855
+ routed_scaling_factor if routed_scaling_factor is not None else 1.0
856
+ ),
857
+ e_score_correction_bias=e_score_correction_bias,
858
+ )
859
+
860
+ if apply_router_weight_on_input:
861
+ assert topk_ids.size(1) == 1
862
+ hidden_states.mul_(topk_weights.to(hidden_states.dtype))
863
+
864
+ assert self.w1_scale is not None
865
+ assert self.w2_scale is not None
866
+ return cpu_fused_moe_int8(
867
+ hidden_states,
868
+ w1,
869
+ w2,
870
+ self.w1_scale,
871
+ self.w2_scale,
872
+ self.w1_bias,
873
+ self.w2_bias,
874
+ topk_weights,
875
+ topk_ids,
876
+ activation.value,
877
+ "neon",
878
+ skip_weighted=apply_router_weight_on_input,
879
+ )
vllm/model_executor/layers/fused_moe/oracle/int8.py
+3
-13
@@ -24,7 +24,6 @@ from vllm.model_executor.layers.quantization.utils.quant_utils import (
24
kInt8StaticChannelSym,
25
)
26
from vllm.model_executor.utils import replace_parameter
27
-from vllm.platforms import current_platform
27
28
logger = init_logger(__name__)
29
@@ -41,20 +40,12 @@ def _get_priority_backends(
40
"""
41
Get available backends in priority order based on platform and config.
42
"""
44
- _AVAILABLE_BACKENDS = [
43
+ return [
44
Int8MoeBackend.TRITON,
45
Int8MoeBackend.HUMMING,
46
Int8MoeBackend.CPU,
47
]
48
50
- def _move_to_front(backends: list[Int8MoeBackend], backend: Int8MoeBackend) -> None:
51
- backends.insert(0, backends.pop(backends.index(backend)))
52
-
53
- if current_platform.is_cpu():
54
- _move_to_front(_AVAILABLE_BACKENDS, Int8MoeBackend.CPU)
55
-
56
- return _AVAILABLE_BACKENDS
57
-
49
50
def backend_to_kernel_cls(
51
backend: Int8MoeBackend,
@@ -78,14 +69,13 @@ def backend_to_kernel_cls(
69
HummingGroupedExperts,
70
HummingIndexedExperts,
71
]
81
-
72
elif backend == Int8MoeBackend.CPU:
73
from vllm.model_executor.layers.fused_moe.experts.cpu_moe import (
74
+ ArmCPUExpertsInt8,
75
CPUExpertsInt8,
76
)
77
87
- return [CPUExpertsInt8]
88
-
78
+ return [ArmCPUExpertsInt8, CPUExpertsInt8]
79
else:
80
raise ValueError(f"Unknown Int8 MoE backend: {backend.value}")
81