|
| 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)); |
0 commit comments