Skip to content

Commit edc885d

Browse files
[Feature] Support MegaMoE (#7943)
* [Feature] Support MegaMoE * update code * fix code style * fix code style * fix test * fix test * fix test * fix test * fix typo * fix code style * fix test * fix xpu test * fix test * fix test * fix code style * fix typo * fix typo
1 parent 9431c4f commit edc885d

14 files changed

Lines changed: 1129 additions & 4 deletions

File tree

Lines changed: 336 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,336 @@
1+
// Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
#include "paddle/extension.h"
16+
#include "helper.h"
17+
18+
#include <cuda_bf16.h>
19+
#include <cuda_fp8.h>
20+
#include <cuda_runtime.h>
21+
22+
#include <algorithm>
23+
#include <cstdint>
24+
#include <vector>
25+
26+
#ifndef PD_BUILD_STATIC_OP
27+
#define PD_BUILD_STATIC_OP(name) PD_BUILD_OP(static_op_##name)
28+
#endif
29+
30+
namespace {
31+
32+
constexpr float kFP8E4M3Max = 448.0f;
33+
constexpr uint32_t kVecElems = 8;
34+
35+
template <uint32_t kNumThreads>
36+
__device__ __forceinline__ float WarpReduceMax(float value) {
37+
static_assert(kNumThreads >= 1 && kNumThreads <= WARP_SIZE,
38+
"kNumThreads must be in [1, 32]");
39+
static_assert((kNumThreads & (kNumThreads - 1)) == 0,
40+
"kNumThreads must be a power of 2");
41+
#pragma unroll
42+
for (int mask = kNumThreads / 2; mask > 0; mask >>= 1) {
43+
value = fmaxf(value, __shfl_xor_sync(0xffffffffu, value, mask, WARP_SIZE));
44+
}
45+
return value;
46+
}
47+
48+
__device__ __forceinline__ uint32_t CastToUE8M0(float value) {
49+
value = fabsf(value);
50+
uint32_t bits = __float_as_uint(value);
51+
uint32_t exp = (bits >> 23) & 0xffu;
52+
const uint32_t mantissa = bits & 0x7fffffu;
53+
exp += mantissa != 0;
54+
exp = min(max(exp, 1u), 254u);
55+
return exp;
56+
}
57+
58+
struct MegaMoEPreDispatchParams {
59+
const __nv_bfloat16* __restrict__ x;
60+
const int64_t* __restrict__ topk_idx;
61+
const float* __restrict__ topk_weights;
62+
63+
phi::dtype::float8_e4m3fn* __restrict__ buf_x;
64+
int32_t* __restrict__ buf_x_sf;
65+
int64_t* __restrict__ buf_topk_idx;
66+
float* __restrict__ buf_topk_weights;
67+
68+
uint32_t num_tokens;
69+
uint32_t padded_max;
70+
uint32_t hidden;
71+
uint32_t num_groups;
72+
uint32_t top_k;
73+
};
74+
75+
template <uint32_t kGroupSize>
76+
__global__ __launch_bounds__(1024, 2) void MegaMoEPreDispatchKernel(
77+
const MegaMoEPreDispatchParams params) {
78+
static_assert(kGroupSize == 32 || kGroupSize == 64 || kGroupSize == 128,
79+
"unsupported group_size");
80+
static_assert(kGroupSize % kVecElems == 0,
81+
"group_size must be a multiple of 8");
82+
constexpr uint32_t kThreadsPerGroup = kGroupSize / kVecElems;
83+
84+
const uint32_t bid = blockIdx.x;
85+
const uint32_t tid = threadIdx.x;
86+
87+
if (bid < params.num_tokens) {
88+
const uint32_t token_id = bid;
89+
const __nv_bfloat16* token_in =
90+
params.x + static_cast<uint64_t>(token_id) * params.hidden;
91+
phi::dtype::float8_e4m3fn* token_out =
92+
params.buf_x + static_cast<uint64_t>(token_id) * params.hidden;
93+
94+
const uint32_t base = tid * kVecElems;
95+
float vals[kVecElems];
96+
float local_max = 0.0f;
97+
98+
#pragma unroll
99+
for (uint32_t i = 0; i < kVecElems; ++i) {
100+
const float v = __bfloat162float(token_in[base + i]);
101+
vals[i] = v;
102+
local_max = fmaxf(local_max, fabsf(v));
103+
}
104+
105+
local_max = WarpReduceMax<kThreadsPerGroup>(local_max);
106+
107+
const float absmax = fmaxf(local_max, 1e-10f);
108+
const float raw_scale = absmax / kFP8E4M3Max;
109+
const uint32_t ue8m0_exp = CastToUE8M0(raw_scale);
110+
const float inv_scale = __uint_as_float((127u + 127u - ue8m0_exp) << 23);
111+
112+
#pragma unroll
113+
for (uint32_t i = 0; i < kVecElems; ++i) {
114+
token_out[base + i] = phi::dtype::float8_e4m3fn(vals[i] * inv_scale);
115+
}
116+
117+
const uint32_t group_id = tid / kThreadsPerGroup;
118+
const uint32_t within_group_id = tid % kThreadsPerGroup;
119+
if (within_group_id == 0 && group_id < params.num_groups) {
120+
const uint32_t byte_off = token_id * params.num_groups + group_id;
121+
reinterpret_cast<uint8_t*>(params.buf_x_sf)[byte_off] =
122+
static_cast<uint8_t>(ue8m0_exp);
123+
}
124+
125+
if (tid < params.top_k) {
126+
const uint32_t off = token_id * params.top_k + tid;
127+
params.buf_topk_idx[off] = static_cast<int64_t>(params.topk_idx[off]);
128+
params.buf_topk_weights[off] = params.topk_weights[off];
129+
}
130+
}
131+
}
132+
133+
void CheckShape2D(const paddle::Tensor& tensor, const char* name) {
134+
PD_CHECK(tensor.shape().size() == 2, name, " must be a 2D tensor");
135+
}
136+
137+
void CheckSameShape(const paddle::Tensor& lhs,
138+
const paddle::Tensor& rhs,
139+
const char* lhs_name,
140+
const char* rhs_name) {
141+
PD_CHECK(lhs.shape() == rhs.shape(),
142+
lhs_name,
143+
" shape must equal ",
144+
rhs_name,
145+
" shape");
146+
}
147+
148+
template <uint32_t kGroupSize>
149+
void LaunchMegaMoEPreDispatch(const MegaMoEPreDispatchParams& params,
150+
uint32_t num_total_blocks,
151+
uint32_t num_threads,
152+
cudaStream_t stream) {
153+
MegaMoEPreDispatchKernel<kGroupSize>
154+
<<<num_total_blocks, num_threads, 0, stream>>>(params);
155+
}
156+
157+
} // namespace
158+
159+
void MegaMoePreDispatch(const paddle::Tensor& x,
160+
const paddle::Tensor& topk_idx,
161+
const paddle::Tensor& topk_weights,
162+
const paddle::Tensor& buf_x,
163+
const paddle::Tensor& buf_x_sf,
164+
const paddle::Tensor& buf_topk_idx,
165+
const paddle::Tensor& buf_topk_weights,
166+
int64_t num_max_tokens_per_rank,
167+
int64_t group_size) {
168+
CheckShape2D(x, "x");
169+
CheckShape2D(topk_idx, "topk_idx");
170+
CheckShape2D(topk_weights, "topk_weights");
171+
CheckShape2D(buf_x, "buf_x");
172+
CheckShape2D(buf_x_sf, "buf_x_sf");
173+
CheckShape2D(buf_topk_idx, "buf_topk_idx");
174+
CheckShape2D(buf_topk_weights, "buf_topk_weights");
175+
CheckSameShape(topk_idx, topk_weights, "topk_idx", "topk_weights");
176+
CheckSameShape(
177+
buf_topk_idx, buf_topk_weights, "buf_topk_idx", "buf_topk_weights");
178+
179+
PD_CHECK(x.dtype() == paddle::DataType::BFLOAT16,
180+
"x must be bfloat16, but got ",
181+
x.dtype());
182+
PD_CHECK(topk_idx.dtype() == paddle::DataType::INT64,
183+
"topk_idx must be int64, but got ",
184+
topk_idx.dtype());
185+
PD_CHECK(topk_weights.dtype() == paddle::DataType::FLOAT32,
186+
"topk_weights must be float32, but got ",
187+
topk_weights.dtype());
188+
PD_CHECK(buf_x.dtype() == paddle::DataType::FLOAT8_E4M3FN,
189+
"buf_x must be float8_e4m3fn, but got ",
190+
buf_x.dtype());
191+
PD_CHECK(buf_x_sf.dtype() == paddle::DataType::INT32,
192+
"buf_x_sf must be int32, but got ",
193+
buf_x_sf.dtype());
194+
PD_CHECK(buf_topk_idx.dtype() == paddle::DataType::INT64,
195+
"buf_topk_idx must be int64, but got ",
196+
buf_topk_idx.dtype());
197+
PD_CHECK(buf_topk_weights.dtype() == paddle::DataType::FLOAT32,
198+
"buf_topk_weights must be float32, but got ",
199+
buf_topk_weights.dtype());
200+
201+
const int64_t num_tokens_i64 = x.shape()[0];
202+
const int64_t hidden_i64 = x.shape()[1];
203+
const int64_t top_k_i64 = topk_idx.shape()[1];
204+
const int64_t padded_max_i64 = buf_x.shape()[0];
205+
206+
PD_CHECK(num_max_tokens_per_rank <= padded_max_i64,
207+
"num_max_tokens_per_rank must not exceed buf_x.shape[0], but got ",
208+
num_max_tokens_per_rank,
209+
" vs ",
210+
padded_max_i64);
211+
PD_CHECK(num_tokens_i64 == topk_idx.shape()[0],
212+
"x.shape[0] must equal topk_idx.shape[0]");
213+
PD_CHECK(buf_x.shape()[1] == hidden_i64,
214+
"buf_x.shape[1] must equal hidden, but got ",
215+
buf_x.shape()[1],
216+
" vs ",
217+
hidden_i64);
218+
PD_CHECK(buf_topk_idx.shape()[0] == padded_max_i64,
219+
"buf_topk_idx.shape[0] must equal padded_max");
220+
PD_CHECK(buf_topk_idx.shape()[1] == top_k_i64,
221+
"buf_topk_idx.shape[1] must equal top_k");
222+
223+
PD_CHECK(group_size == 32 || group_size == 64 || group_size == 128,
224+
"unsupported group_size: ",
225+
group_size);
226+
PD_CHECK(num_tokens_i64 <= num_max_tokens_per_rank,
227+
"num_tokens must not exceed padded_max");
228+
PD_CHECK(hidden_i64 % group_size == 0,
229+
"hidden must be a multiple of group_size");
230+
const int64_t num_groups_i64 = hidden_i64 / group_size;
231+
PD_CHECK(num_groups_i64 % 4 == 0, "num_groups must be a multiple of 4");
232+
PD_CHECK(buf_x_sf.shape()[0] == padded_max_i64,
233+
"buf_x_sf.shape[0] must equal padded_max");
234+
PD_CHECK(buf_x_sf.shape()[1] == num_groups_i64 / 4,
235+
"buf_x_sf.shape[1] must equal hidden/group_size/4, but got ",
236+
buf_x_sf.shape()[1],
237+
" vs ",
238+
num_groups_i64 / 4);
239+
PD_CHECK(hidden_i64 % static_cast<int64_t>(kVecElems) == 0,
240+
"hidden must be a multiple of 8 (16B bf16 loads)");
241+
const int64_t num_threads_i64 = hidden_i64 / static_cast<int64_t>(kVecElems);
242+
PD_CHECK(num_threads_i64 <= 1024,
243+
"hidden too large for single-block-per-row quant");
244+
PD_CHECK(num_threads_i64 >= top_k_i64, "top_k must fit into one quant CTA");
245+
246+
const uint32_t num_tokens = static_cast<uint32_t>(num_tokens_i64);
247+
const uint32_t padded_max = static_cast<uint32_t>(padded_max_i64);
248+
const uint32_t hidden = static_cast<uint32_t>(hidden_i64);
249+
const uint32_t num_groups = static_cast<uint32_t>(num_groups_i64);
250+
const uint32_t top_k = static_cast<uint32_t>(top_k_i64);
251+
const uint32_t num_threads = static_cast<uint32_t>(num_threads_i64);
252+
const uint32_t num_total_blocks = num_tokens;
253+
254+
const MegaMoEPreDispatchParams params{
255+
reinterpret_cast<const __nv_bfloat16*>(x.data<paddle::bfloat16>()),
256+
topk_idx.data<int64_t>(),
257+
topk_weights.data<float>(),
258+
const_cast<phi::dtype::float8_e4m3fn*>(
259+
buf_x.data<phi::dtype::float8_e4m3fn>()),
260+
const_cast<int32_t*>(buf_x_sf.data<int32_t>()),
261+
const_cast<int64_t*>(buf_topk_idx.data<int64_t>()),
262+
const_cast<float*>(buf_topk_weights.data<float>()),
263+
num_tokens,
264+
padded_max,
265+
hidden,
266+
num_groups,
267+
top_k,
268+
};
269+
270+
if (num_total_blocks > 0) {
271+
auto stream = x.stream();
272+
switch (group_size) {
273+
case 32:
274+
LaunchMegaMoEPreDispatch<32>(
275+
params, num_total_blocks, num_threads, stream);
276+
break;
277+
case 64:
278+
LaunchMegaMoEPreDispatch<64>(
279+
params, num_total_blocks, num_threads, stream);
280+
break;
281+
case 128:
282+
LaunchMegaMoEPreDispatch<128>(
283+
params, num_total_blocks, num_threads, stream);
284+
break;
285+
default:
286+
PD_THROW("unsupported group_size: ", group_size);
287+
}
288+
}
289+
290+
// return {buf_x, buf_x_sf, buf_topk_idx, buf_topk_weights};
291+
}
292+
293+
std::vector<paddle::DataType> MegaMoePreDispatchInferDtype(
294+
const paddle::DataType& x_dtype,
295+
const paddle::DataType& topk_idx_dtype,
296+
const paddle::DataType& topk_weights_dtype,
297+
const paddle::DataType& buf_x_dtype,
298+
const paddle::DataType& buf_x_sf_dtype,
299+
const paddle::DataType& buf_topk_idx_dtype,
300+
const paddle::DataType& buf_topk_weights_dtype) {
301+
return {
302+
buf_x_dtype, buf_x_sf_dtype, buf_topk_idx_dtype, buf_topk_weights_dtype};
303+
}
304+
305+
std::vector<std::vector<int64_t>> MegaMoePreDispatchInferShape(
306+
const std::vector<int64_t>& x_shape,
307+
const std::vector<int64_t>& topk_idx_shape,
308+
const std::vector<int64_t>& topk_weights_shape,
309+
const std::vector<int64_t>& buf_x_shape,
310+
const std::vector<int64_t>& buf_x_sf_shape,
311+
const std::vector<int64_t>& buf_topk_idx_shape,
312+
const std::vector<int64_t>& buf_topk_weights_shape) {
313+
return {
314+
buf_x_shape, buf_x_sf_shape, buf_topk_idx_shape, buf_topk_weights_shape};
315+
}
316+
317+
PD_BUILD_STATIC_OP(mega_moe_pre_dispatch)
318+
.Inputs({"x",
319+
"topk_idx",
320+
"topk_weights",
321+
"buf_x",
322+
"buf_x_sf",
323+
"buf_topk_idx",
324+
"buf_topk_weights"})
325+
.Outputs({"buf_x_out",
326+
"buf_x_sf_out",
327+
"buf_topk_idx_out",
328+
"buf_topk_weights_out"})
329+
.Attrs({"num_max_tokens_per_rank: int64_t", "group_size: int64_t"})
330+
.SetInplaceMap({{"buf_x", "buf_x_out"},
331+
{"buf_x_sf", "buf_x_sf_out"},
332+
{"buf_topk_idx", "buf_topk_idx_out"},
333+
{"buf_topk_weights", "buf_topk_weights_out"}})
334+
.SetKernelFn(PD_KERNEL(MegaMoePreDispatch))
335+
.SetInferShapeFn(PD_INFER_SHAPE(MegaMoePreDispatchInferShape))
336+
.SetInferDtypeFn(PD_INFER_DTYPE(MegaMoePreDispatchInferDtype));

custom_ops/setup_ops.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -348,6 +348,7 @@ def find_end_files(directory, end_str):
348348
"gpu_ops/gelu_tanh.cu",
349349
"gpu_ops/reasoning_phase_token_constraint.cu",
350350
"gpu_ops/get_attn_mask_q.cu",
351+
"gpu_ops/mega_moe_pre_dispatch.cu",
351352
]
352353
sm_versions = get_sm_version(archs)
353354
# Some kernels in this file require SM75+ instructions. Exclude them when building SM70 (V100).

fastdeploy/config.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -651,6 +651,7 @@ def __init__(
651651
self.enable_expert_parallel = False
652652
self.enable_chunked_moe = False
653653
self.chunked_moe_size = 256
654+
self.enable_mega_moe = False
654655

655656
self.local_data_parallel_id = 0
656657
# Engine worker queue port

fastdeploy/engine/args_utils.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -369,6 +369,11 @@ class EngineArgs:
369369
Whether use chunked moe.
370370
"""
371371

372+
enable_mega_moe: bool = False
373+
"""
374+
Whether use MegaMoE wfp4afp8 for MoE and block_wise_fp8 for dense Linear.
375+
"""
376+
372377
chunked_moe_size: int = 256
373378
"""
374379
Chunk size of moe input.
@@ -1176,6 +1181,12 @@ def add_cli_args(parser: FlexibleArgumentParser) -> FlexibleArgumentParser:
11761181
default=EngineArgs.enable_chunked_moe,
11771182
help="Use chunked moe.",
11781183
)
1184+
parallel_group.add_argument(
1185+
"--enable-mega-moe",
1186+
action="store_true",
1187+
default=EngineArgs.enable_mega_moe,
1188+
help="Use MegaMoE wfp4afp8 for MoE and block_wise_fp8 for dense Linear.",
1189+
)
11791190
parallel_group.add_argument(
11801191
"--chunked-moe-size",
11811192
type=int,

fastdeploy/engine/engine.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -685,6 +685,7 @@ def _start_worker_service(self):
685685
worker_store_true_flag = {
686686
"enable_expert_parallel": self.cfg.parallel_config.enable_expert_parallel,
687687
"enable_chunked_moe": self.cfg.parallel_config.enable_chunked_moe,
688+
"enable_mega_moe": self.cfg.parallel_config.enable_mega_moe,
688689
"enable_prefix_caching": self.cfg.cache_config.enable_prefix_caching,
689690
"enable_chunked_prefill": self.cfg.cache_config.enable_chunked_prefill,
690691
"do_profile": self.do_profile,

0 commit comments

Comments
 (0)