// Copyright (c) 2025 PaddlePaddle Authors. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include "paddle/phi/kernels/gpu/moe_unpermute_kernel.h" #include #include "paddle/phi/backends/gpu/gpu_context.h" #include "paddle/phi/core/kernel_registry.h" #include "paddle/phi/kernels/full_kernel.h" #include "paddle/phi/kernels/gpu/moe_permute_utils.h" namespace phi { // Import MoE constants from shared header using moe::kMaxNumExperts; template __global__ __launch_bounds__(256) void tokens_zip_kernel( const bfloat16 *__restrict__ unzipped_tokens_in, const int *__restrict__ zipped_expertwise_rowmap, const int *__restrict__ expert_routemap_topk, const float *__restrict__ unzipped_token_probs, bfloat16 *__restrict__ zipped_tokens_out, float *__restrict__ zipped_probs_topk, const int total_zipped_tokens_num, const int token_length, const int num_experts, const int topk) { const int this_row = blockIdx.x; if (this_row >= total_zipped_tokens_num) return; const __nv_bfloat16 *unzipped_tokens = reinterpret_cast(unzipped_tokens_in); __nv_bfloat16 *zipped_tokens = reinterpret_cast<__nv_bfloat16 *>(zipped_tokens_out); __shared__ int local_row_fetchlist[NUM_EXPERTS]; __shared__ float local_row_weight[NUM_EXPERTS]; // Strided load: blockDim.x may be < num_experts, so each thread // handles multiple slots to cover the full [0, num_experts) range. for (int i = threadIdx.x; i < num_experts; i += blockDim.x) { const int fetch_row = zipped_expertwise_rowmap[static_cast(this_row) * num_experts + i]; local_row_fetchlist[i] = fetch_row; if constexpr (WEIGHTED_TOKEN) { local_row_weight[i] = ((fetch_row == -1) ? 0.0f : unzipped_token_probs[fetch_row]); } } __syncthreads(); #pragma unroll for (int k = 0; k < topk; ++k) { const int expert_idx = expert_routemap_topk[static_cast(this_row) * topk + k]; if (expert_idx < 0) [[likely]] continue; const int expert_fetch_row = local_row_fetchlist[expert_idx]; zipped_probs_topk[static_cast(this_row) * topk + k] = unzipped_token_probs[expert_fetch_row]; } // only support VecSize = 8 constexpr int VecSize = 8; // use bfloat162 to pack 2 bfloat16s constexpr int PACKED_VEC_SIZE = VecSize / 2; const int num_full_vec = token_length / VecSize; const int64_t thread_stride = static_cast(blockDim.x) * VecSize; #pragma unroll 1 for (int64_t x_offset = static_cast(threadIdx.x) * VecSize; x_offset < num_full_vec * VecSize; x_offset += thread_stride) { __nv_bfloat162 raw[PACKED_VEC_SIZE] = {{0.0f, 0.0f}}; float2 sum[PACKED_VEC_SIZE] = {{0.0f, 0.0f}}; int aggreg_cnt = 0; #pragma unroll for (int expert = 0; expert < num_experts; ++expert) { float weight; const int fetch_row = local_row_fetchlist[expert]; if (fetch_row < 0) continue; // Get weight of current copy of token. if constexpr (WEIGHTED_TOKEN) { weight = local_row_weight[expert]; } aggreg_cnt++; const __nv_bfloat162 *base_ptr = reinterpret_cast( &unzipped_tokens[(int64_t)fetch_row * (int64_t)token_length + x_offset]); // Cast the input pointer to uint4* to enforce a single 128-bit // vectorized load (LDG.E.128) for optimal memory bandwidth. uint4 packed_raw = *reinterpret_cast(base_ptr); const __nv_bfloat162 *raw_ptr = reinterpret_cast(&packed_raw); #pragma unroll for (int i = 0; i < PACKED_VEC_SIZE; ++i) { raw[i] = raw_ptr[i]; float2 token_vec = __bfloat1622float2(raw[i]); if constexpr (WEIGHTED_TOKEN) { sum[i].x = __fmaf_rn(token_vec.x, weight, sum[i].x); sum[i].y = __fmaf_rn(token_vec.y, weight, sum[i].y); } else { sum[i].x = __fadd_rn(token_vec.x, sum[i].x); sum[i].y = __fadd_rn(token_vec.y, sum[i].y); } } // Pack loop } // Expert loop __nv_bfloat162 results[PACKED_VEC_SIZE]; #pragma unroll for (int i = 0; i < PACKED_VEC_SIZE; ++i) { // Using raw if not aggregated, prevent submornal downcast. results[i] = (aggreg_cnt > 1) ? __float22bfloat162_rn(sum[i]) : raw[i]; } __nv_bfloat162 *out_ptr = reinterpret_cast<__nv_bfloat162 *>( &zipped_tokens[(int64_t)this_row * (int64_t)token_length + x_offset]); // Cast the output pointer to uint4* to enforce a single 128-bit // vectorized store (STG.E.128) for optimal memory bandwidth. *reinterpret_cast(out_ptr) = *reinterpret_cast(results); } // Vectorized token length loop #pragma unroll 1 for (int i = num_full_vec * VecSize + threadIdx.x; i < token_length; i += blockDim.x) { float sum = 0.0f; __nv_bfloat16 raw = 0.0f; int aggreg_cnt = 0; #pragma unroll for (int expert = 0; expert < num_experts; ++expert) { int fetch_row = local_row_fetchlist[expert]; float weight; if constexpr (WEIGHTED_TOKEN) { weight = local_row_weight[expert]; } if (fetch_row < 0) continue; aggreg_cnt++; raw = unzipped_tokens[(int64_t)fetch_row * (int64_t)token_length + i]; float token_val = static_cast(raw); if constexpr (WEIGHTED_TOKEN) { sum = __fmaf_rn(token_val, weight, sum); } else { sum = __fadd_rn(token_val, sum); } } zipped_tokens[(int64_t)this_row * (int64_t)token_length + i] = (aggreg_cnt > 1) ? static_cast<__nv_bfloat16>(sum) : raw; } // Trailing token length loop // Optimization: A dummy synchronization primitive is placed here to act as a // compiler barrier. This forces the compiler to shrink the live ranges of // variables and release registers earlier. This reduces peak register usage, // improving occupancy from 75% to 100% and yielding a significant performance // boost. __syncwarp(); } template void dispatch_tokens_zip(const Context &dev_ctx, const DenseTensor &unzipped_tokens, const DenseTensor &zipped_expertwise_rowmap, const DenseTensor &expert_routemap_topk, const DenseTensor &unzipped_token_probs, DenseTensor *zipped_tokens, DenseTensor *zipped_probs_topk, const int total_zipped_tokens_num, const int num_experts, const int token_length, const int topk, const bool MP, const bool using_weighted_combine) { PADDLE_ENFORCE_GE( total_zipped_tokens_num, 0, common::errors::InvalidArgument( "total_zipped_tokens_num should be non-negative, but got %d.", total_zipped_tokens_num)); if (total_zipped_tokens_num == 0) return; dim3 grid, block; grid.x = static_cast(total_zipped_tokens_num); block.x = 256; if (unzipped_token_probs.dtype() != DataType::FLOAT32) return; // Unified dispatch: MP x WEIGHTED x NUM_EXPERTS dispatch::Bools( [&](auto mp_tag, auto weighted_tag) { constexpr bool MP_CONST = decltype(mp_tag)::value; constexpr bool WEIGHTED_CONST = decltype(weighted_tag)::value; dispatch::NumExperts(num_experts, [&](auto ne_tag) { constexpr int NE = decltype(ne_tag)::value; tokens_zip_kernel <<>>( unzipped_tokens.data(), zipped_expertwise_rowmap.data(), expert_routemap_topk.data(), unzipped_token_probs.data(), zipped_tokens->data(), zipped_probs_topk->data(), total_zipped_tokens_num, token_length, num_experts, topk); }); }, MP, using_weighted_combine); } template void MoeUnpermuteKernel(const Context &dev_ctx, const DenseTensor &unzipped_tokens, const DenseTensor &zipped_expertwise_rowmap, const DenseTensor &expert_routemap_topk, const DenseTensor &unzipped_token_probs, const int total_zipped_tokens_num, const int num_experts, const bool MP, const bool using_weighted_combine, DenseTensor *zipped_tokens, DenseTensor *zipped_probs_topk) { PADDLE_ENFORCE_EQ(unzipped_tokens.dims().size(), 2, common::errors::InvalidArgument( "Input unzipped_tokens's dims should be 2, but got %u.", unzipped_tokens.dims().size())); PADDLE_ENFORCE_EQ( zipped_expertwise_rowmap.dims().size(), 2, common::errors::InvalidArgument( "Input zipped_expertwise_rowmap's dims should be 2, but got %u.", zipped_expertwise_rowmap.dims().size())); PADDLE_ENFORCE_EQ( expert_routemap_topk.dims().size(), 2, common::errors::InvalidArgument( "Input expert_routemap_topk's dims should be 2, but got %u.", expert_routemap_topk.dims().size())); PADDLE_ENFORCE_GE( total_zipped_tokens_num, 0, common::errors::InvalidArgument( "total_zipped_tokens_num should be non-negative, but got %d.", total_zipped_tokens_num)); PADDLE_ENFORCE_GE( num_experts, 1, common::errors::InvalidArgument( "num_experts should be > 0, received: (%d)", num_experts)); PADDLE_ENFORCE_LE( num_experts, kMaxNumExperts, common::errors::InvalidArgument( "Currently we support no more than (%ld), received num_expert: " "(%ld). Please check input value.", kMaxNumExperts, num_experts)); PADDLE_ENFORCE_EQ( zipped_expertwise_rowmap.dims()[0], total_zipped_tokens_num, common::errors::InvalidArgument( "Input zipped_expertwise_rowmap's first dimension should be equal to " "total_zipped_tokens_num, but got %ld and %d.", zipped_expertwise_rowmap.dims()[0], total_zipped_tokens_num)); PADDLE_ENFORCE_EQ( zipped_expertwise_rowmap.dims()[1], num_experts, common::errors::InvalidArgument("Input zipped_expertwise_rowmap's second " "dimension should be equal to " "num_experts, but got %ld and %d.", zipped_expertwise_rowmap.dims()[1], num_experts)); PADDLE_ENFORCE_EQ( expert_routemap_topk.dims()[0], total_zipped_tokens_num, common::errors::InvalidArgument( "Input expert_routemap_topk's first dimension should be equal to " "total_zipped_tokens_num, but got %ld and %d.", expert_routemap_topk.dims()[0], total_zipped_tokens_num)); PADDLE_ENFORCE_EQ( unzipped_token_probs.numel(), unzipped_tokens.dims()[0], common::errors::InvalidArgument( "Input unzipped_token_probs's number of elements should be equal to " "unzipped_tokens.dims()[0], but got %ld and %ld.", unzipped_token_probs.numel(), unzipped_tokens.dims()[0])); const int64_t cols = unzipped_tokens.dims()[1]; PADDLE_ENFORCE_LE(cols, std::numeric_limits::max(), common::errors::InvalidArgument( "unzipped_tokens.dims()[1] should be less than " "INT_MAX, received unzipped_tokens.dims()[1]: (%ld)", cols)); const int64_t topk = expert_routemap_topk.dims()[1]; PADDLE_ENFORCE_GE(topk, 1, common::errors::InvalidArgument( "topk should be > 0, received topk: (%ld)", topk)); PADDLE_ENFORCE_LE( topk, std::numeric_limits::max(), common::errors::InvalidArgument( "topk should be less than INT_MAX, received topk: (%ld)", topk)); PADDLE_ENFORCE_LE( static_cast(total_zipped_tokens_num), static_cast(std::numeric_limits::max()), common::errors::InvalidArgument( "total_zipped_tokens_num should be <= INT_MAX, but got %d.", total_zipped_tokens_num)); dev_ctx.template Alloc(zipped_tokens); dev_ctx.template Alloc(zipped_probs_topk); if (unzipped_tokens.numel() == 0 || total_zipped_tokens_num == 0) return; void *zipped_probs_topk_ptr = reinterpret_cast(zipped_probs_topk->data()); const int64_t probs_numel = static_cast(total_zipped_tokens_num) * topk; PADDLE_ENFORCE_LE( probs_numel, static_cast(std::numeric_limits::max() / sizeof(float)), common::errors::InvalidArgument( "The zipped_probs_topk memset size overflows, got %ld elements.", probs_numel)); PADDLE_ENFORCE_GPU_SUCCESS(cudaMemsetAsync( zipped_probs_topk_ptr, 0, sizeof(float) * probs_numel, dev_ctx.stream())); dispatch_tokens_zip(dev_ctx, unzipped_tokens, zipped_expertwise_rowmap, expert_routemap_topk, unzipped_token_probs, zipped_tokens, zipped_probs_topk, total_zipped_tokens_num, num_experts, static_cast(cols), static_cast(topk), MP, using_weighted_combine); } } // namespace phi PD_REGISTER_KERNEL( moe_unpermute, GPU, ALL_LAYOUT, phi::MoeUnpermuteKernel, phi::bfloat16) {}