// 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 #include "paddle/common/enforce.h" #include "paddle/phi/backends/gpu/cuda/cuda_graph_with_memory_pool.h" #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" #include "paddle/utils/optional.h" #if CUDA_VERSION >= 12080 #include #include #include #include namespace cg = cooperative_groups; #endif namespace phi { using moe::kCumsumInvalidTag; using moe::kMaxNumExperts; using moe::kPermuteBlockDimX; using moe::kPermuteBlockSize; // ============================================================================ // Unified Permute Kernel // ============================================================================ // Register-centric scheduling: metadata lives in registers, not shared memory. // Shared memory layout (phases are non-overlapping): // Phase 1: uint32_t[num_experts] expert bitmask // Phase 2: int[ROWS_PER_BLOCK * TOPK] output_rows (reuses bitmask region) // TMA only: ping/pong buffers + routemap/probs (after max(bitmask, outrows)) // // Rowmap is pre-filled with -1 by host cudaMemsetAsync before kernel launch. // Phase 1a: Scatter routemap->bitmask, cache expert/prob in registers // Phase 1b: Progressive cumsum + direct global writes (routemap, probs, // indices) Phase 2: Flush output_rows to smem once, then zero-sync data // movement // template __global__ __launch_bounds__(BLOCK_DIM_X) void permute_kernel( const TokenT *__restrict__ X, const IndexT *__restrict__ routemap_topk, const ProbT *__restrict__ probs_topk, const ScaleT *__restrict__ XScale, const int *__restrict__ expert_base_offset, const int *__restrict__ expert_base_offset_end, TokenT *__restrict__ X_unzipped, int *__restrict__ zipped_expertwise_rowmap, ProbT *__restrict__ probs_unzipped, ScaleT *__restrict__ XScale_unzipped, int *global_expertwise_block_cumsum, int *__restrict__ expert_indices, const int total_zipped_tokens_num, const int token_length, const int scale_length, const int num_experts, const int topk) { #if defined(__CUDA_ARCH__) && (__CUDA_ARCH__ >= 900) static_assert(ROWS_PER_BLOCK == 32, "ROWS_PER_BLOCK must equal warp size"); const int lane_id = threadIdx.x & 31; const int warp_id = threadIdx.x >> 5; constexpr int warp_num = BLOCK_DIM_X >> 5; // ===================== Block-to-data mapping (prefix/suffix) ============= const bool use_prefix = blockIdx.x % 2 == 0; const int block_index_in_X = use_prefix ? blockIdx.x / 2 : gridDim.x - 1 - blockIdx.x / 2; const int block_row_base = block_index_in_X * ROWS_PER_BLOCK; const int block_row_end = min(block_row_base + ROWS_PER_BLOCK, total_zipped_tokens_num); // ===================== Shared memory layout ============================= // Section 1 (Phase 1a-1b): uint32_t[num_experts] expert bitmask // Section 1 (Phase 2): int[ROWS_PER_BLOCK * TOPK] output_rows (reuses) // Section 2 (USE_TMA): ping/pong + routemap + probs extern __shared__ char smem_raw[]; uint32_t *expert_bitmask = reinterpret_cast(smem_raw); // TMA region starts after max(bitmask, output_rows), 32-byte aligned constexpr int output_rows_bytes = ROWS_PER_BLOCK * TOPK * sizeof(int); [[maybe_unused]] char *tma_base = smem_raw + (((max(static_cast(kMaxNumExperts * sizeof(uint32_t)), output_rows_bytes)) + 31) & ~31); // Initialize expert bitmask for (int i = threadIdx.x; i < num_experts; i += BLOCK_DIM_X) { expert_bitmask[i] = 0u; } // ===================== TMA setup ======================================== [[maybe_unused]] TokenT *ping_buffer = nullptr; [[maybe_unused]] TokenT *pong_buffer = nullptr; [[maybe_unused]] IndexT *shared_routemap = nullptr; [[maybe_unused]] ProbT *shared_probs = nullptr; #if CUDA_VERSION >= 12080 [[maybe_unused]] cg::thread_block cg_block = cg::this_thread_block(); constexpr auto scope = cuda::thread_scope_block; constexpr int stages = 2; #pragma nv_diag_suppress 20054 __shared__ cuda::pipeline_shared_state pstate; #pragma nv_diag_default 20054 [[maybe_unused]] auto pipe = cuda::make_pipeline(cg_block, &pstate); if constexpr (USE_TMA) { ping_buffer = reinterpret_cast(tma_base); pong_buffer = reinterpret_cast(tma_base + token_length * sizeof(TokenT)); shared_routemap = reinterpret_cast(tma_base + 2 * token_length * sizeof(TokenT)); shared_probs = reinterpret_cast(reinterpret_cast(shared_routemap) + ROWS_PER_BLOCK * topk * sizeof(IndexT)); const int local_elems = (block_row_end - block_row_base) * topk; pipe.producer_acquire(); cuda::memcpy_async( cg_block, shared_routemap, routemap_topk + static_cast(block_row_base) * topk, local_elems * sizeof(IndexT), pipe); cuda::memcpy_async(cg_block, shared_probs, probs_topk + static_cast(block_row_base) * topk, local_elems * sizeof(ProbT), pipe); pipe.producer_commit(); if constexpr (do_gather) { pipe.producer_acquire(); cuda::memcpy_async( cg_block, ping_buffer, X + static_cast(block_row_base) * token_length, token_length * sizeof(TokenT), pipe); pipe.producer_commit(); } } #endif if constexpr (USE_TMA) { #if CUDA_VERSION >= 12080 pipe.consumer_wait(); __syncthreads(); pipe.consumer_release(); #endif } else { __syncthreads(); } // ===================== Phase 1a: Scatter into bitmasks =================== // Every lane loads ALL its topk columns into registers so that Phase 1b // can always match reg_expert[k] == expert_id regardless of warp id. int reg_expert[TOPK]; ProbT reg_prob[TOPK]; #pragma unroll for (int k = 0; k < TOPK; k++) { reg_expert[k] = -1; reg_prob[k] = ProbT(0); } const int global_row = block_row_base + lane_id; const bool row_valid = global_row < total_zipped_tokens_num; // Each lane reads all its topk entries; warps collaborate on atomicOr. for (int col = 0; col < topk; col++) { int expert = -1; ProbT prob = ProbT(0); if (row_valid) { if constexpr (USE_TMA) { expert = shared_routemap[lane_id * topk + col]; prob = shared_probs[lane_id * topk + col]; } else { expert = routemap_topk[static_cast(global_row) * topk + col]; prob = probs_topk[static_cast(global_row) * topk + col]; } } if (expert >= 0 && expert < num_experts) { // Only one warp per column does atomicOr (idempotent, but reduces // traffic) if (col % warp_num == warp_id) { atomicOr(&expert_bitmask[expert], 1u << lane_id); } reg_expert[col] = expert; reg_prob[col] = prob; } } __syncthreads(); // ===================== Phase 1b: Progressive cumsum + global writes ======= // Chain layout (even blockIdx = prefix, odd = suffix): // prefix: block 0 → block 2 → block 4 → ... (recv from blockIdx-2) // suffix: block 1 → block 3 → block 5 → ... (recv from blockIdx-2) // Root blocks (prefix: blockIdx==0, suffix: blockIdx==1) have no predecessor. // When mask==0u for an expert, lane 0 still receives & forwards the offset // (with local_count==0) so the chain never breaks. int reg_output_row[TOPK]; #pragma unroll for (int k = 0; k < TOPK; k++) reg_output_row[k] = -1; const bool is_chain_root = (use_prefix ? blockIdx.x == 0 : blockIdx.x == 1); for (int expert_id = warp_id; expert_id < num_experts; expert_id += warp_num) { const uint32_t mask = expert_bitmask[expert_id]; const int local_count = __popc(mask); // --- Inter-block cumsum: lane 0 receives from predecessor, sends to // successor. Always executes regardless of mask to keep chain alive. int chain_offset = 0; if (lane_id == 0) { if (!is_chain_root) { const int recv_idx = blockIdx.x * num_experts + expert_id; while ((chain_offset = atomicAdd(&global_expertwise_block_cumsum[recv_idx], 0)) == kCumsumInvalidTag) { } } const int send_idx = (blockIdx.x + 2) * num_experts + expert_id; atomicExch(&global_expertwise_block_cumsum[send_idx], chain_offset + local_count); } // --- Intra-block position assignment (only when this expert has tokens) // --- int final_pos = -1; if (mask != 0u) { chain_offset = __shfl_sync(0xFFFFFFFF, chain_offset, 0); const bool lane_active = (mask & (1u << lane_id)) != 0; if (lane_active && row_valid) { if (use_prefix) { final_pos = expert_base_offset[expert_id] + chain_offset + __popc(mask & ((1u << lane_id) - 1)); } else { final_pos = expert_base_offset_end[expert_id] - chain_offset - __popc((lane_id < 31) ? (mask >> (lane_id + 1)) : 0u); } zipped_expertwise_rowmap[static_cast(global_row) * num_experts + expert_id] = final_pos; #pragma unroll for (int k = 0; k < TOPK; k++) { if (reg_expert[k] == expert_id) { reg_output_row[k] = final_pos; probs_unzipped[final_pos] = reg_prob[k]; if constexpr (return_expert_indices) { expert_indices[final_pos] = expert_id; } break; } } } } } // ===================== Phase 2: Token data movement ====================== // All warps must finish Phase 1b before shared memory is repurposed. __syncthreads(); if constexpr (do_gather) { // Flush output_rows from registers to shared memory (reuse bitmask region). // reg_output_row[k] was set by whichever warp processed the matching // expert; other warps still hold -1 for that k. Pre-fill with -1 then // only write non-(-1) values to avoid race conditions. int *shared_output_rows = reinterpret_cast(smem_raw); for (int i = threadIdx.x; i < ROWS_PER_BLOCK * TOPK; i += BLOCK_DIM_X) { shared_output_rows[i] = -1; } __syncthreads(); #pragma unroll for (int k = 0; k < TOPK; k++) { if (reg_output_row[k] >= 0) { shared_output_rows[lane_id * TOPK + k] = reg_output_row[k]; } } __syncthreads(); // Data movement loop — no per-row __syncthreads needed (output_rows // are already fully materialized in smem). Only TMA pipeline needs sync. for (int row = block_row_base; row < block_row_end; row++) { const int internal_row = row - block_row_base; if constexpr (USE_TMA) { #if CUDA_VERSION >= 12080 pipe.consumer_wait(); cg_block.sync(); if (row + 1 < block_row_end) { TokenT *prefetch_buffer = (internal_row % 2 == 0) ? pong_buffer : ping_buffer; pipe.producer_acquire(); cuda::memcpy_async(cg_block, prefetch_buffer, X + static_cast(row + 1) * token_length, token_length * sizeof(TokenT), pipe); pipe.producer_commit(); } #endif } [[maybe_unused]] TokenT *current_buffer = nullptr; if constexpr (USE_TMA) { current_buffer = (internal_row % 2 == 0) ? ping_buffer : pong_buffer; } // Read output rows from shared memory (no sync needed) #pragma unroll for (int k = 0; k < TOPK; k++) { const int out_row = shared_output_rows[internal_row * TOPK + k]; if (out_row < 0) continue; if constexpr (USE_TMA) { #if CUDA_VERSION >= 12080 if (threadIdx.x == 0) { cuda::device::experimental::cp_async_bulk_shared_to_global( &X_unzipped[(int64_t)out_row * (int64_t)token_length], current_buffer, token_length * sizeof(TokenT)); cuda::device::experimental::cp_async_bulk_commit_group(); } #endif } else { vectorized_memcpy( &X[(int64_t)row * (int64_t)token_length], &X_unzipped[(int64_t)out_row * (int64_t)token_length], token_length); } if constexpr (has_scale) { try_vectorized_memcpy( &XScale[(int64_t)row * (int64_t)scale_length], &XScale_unzipped[(int64_t)out_row * (int64_t)scale_length], scale_length); } } if constexpr (USE_TMA) { #if CUDA_VERSION >= 12080 if (threadIdx.x == 0) { cuda::device::experimental::cp_async_bulk_wait_group_read<0>(); } pipe.consumer_release(); #endif } } } #endif // __CUDA_ARCH__ >= 900 } // ============================================================================ // Kernel launcher // ============================================================================ template void launch_permute_kernel(const GPUContext &dev_ctx, const DenseTensor &X, const DenseTensor &expert_routemap_topk, const DenseTensor &expert_prob_topk, const paddle::optional &XScale, const DenseTensor &expert_offsets, const DenseTensor &expert_offset_end, DenseTensor *X_unzipped, DenseTensor *zipped_expertwise_rowmap, DenseTensor *token_prob_unzipped, DenseTensor *XScale_unzipped, DenseTensor *global_expertwise_block_cumsum, DenseTensor *expert_indices, int total_zipped_tokens_num, int token_length, int scale_length, int num_experts, int topk, int capability) { constexpr int ROWS_PER_BLOCK = kPermuteBlockSize; constexpr int BLOCK_DIM_X = kPermuteBlockDimX; 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; const int64_t grid_x = (static_cast(total_zipped_tokens_num) + ROWS_PER_BLOCK - 1) / ROWS_PER_BLOCK; PADDLE_ENFORCE_LE( grid_x, static_cast(std::numeric_limits::max()), common::errors::InvalidArgument( "The grid size of moe_permute should be <= UINT_MAX, but got %ld.", grid_x)); dim3 grid(static_cast(grid_x)); dim3 block(BLOCK_DIM_X); const TokenT *x_ptr = X.data(); const IntT *routemap_ptr = expert_routemap_topk.data(); const ProbT *prob_ptr = expert_prob_topk.data(); const ScaleT *scale_ptr = XScale ? XScale.get_ptr()->data() : nullptr; const int *offset_ptr = expert_offsets.data(); const int *offset_end_ptr = expert_offset_end.data(); TokenT *x_out_ptr = X_unzipped->data(); IntT *rowmap_out_ptr = zipped_expertwise_rowmap->data(); ProbT *prob_out_ptr = token_prob_unzipped->data(); ScaleT *scale_out_ptr = XScale_unzipped->data(); int *cumsum_ptr = global_expertwise_block_cumsum->data(); int *expert_indices_ptr = (ReturnIndices && expert_indices) ? expert_indices->data() : nullptr; [[maybe_unused]] bool use_tma = false; #if CUDA_VERSION >= 12080 use_tma = capability >= 90 && is_aligned_in_bytes(token_length * sizeof(TokenT)) && is_aligned_in_bytes(sizeof(IntT) * topk * ROWS_PER_BLOCK); #endif // Shared memory: max(bitmask, output_rows) + optional TMA buffers constexpr int output_rows_bytes = ROWS_PER_BLOCK * TOPK * sizeof(int); const int base_smem = max(static_cast(kMaxNumExperts * sizeof(uint32_t)), output_rows_bytes); dispatch::Bool(use_tma, [&](auto tma_tag) { constexpr bool UseTMA = decltype(tma_tag)::value; int smem = base_smem; if constexpr (UseTMA) { smem = ((smem + 31) & ~31); smem += 2 * token_length * sizeof(TokenT) + sizeof(IntT) * TOPK * ROWS_PER_BLOCK + sizeof(ProbT) * TOPK * ROWS_PER_BLOCK; } auto kernel_ptr = permute_kernel; if (smem > 48 * 1024) { PADDLE_ENFORCE_GPU_SUCCESS(cudaFuncSetAttribute( kernel_ptr, cudaFuncAttributeMaxDynamicSharedMemorySize, smem)); } kernel_ptr<<>>(x_ptr, routemap_ptr, prob_ptr, scale_ptr, offset_ptr, offset_end_ptr, x_out_ptr, rowmap_out_ptr, prob_out_ptr, scale_out_ptr, cumsum_ptr, expert_indices_ptr, total_zipped_tokens_num, token_length, scale_length, num_experts, topk); }); } // ============================================================================ // Dispatchers // ============================================================================ template void dispatch_permute_kernel(const Context &dev_ctx, const DenseTensor &X, const DenseTensor &expert_routemap_topk, const DenseTensor &expert_prob_topk, const paddle::optional &XScale, const DenseTensor &expert_offsets, const DenseTensor &expert_offset_end, DenseTensor *X_unzipped, DenseTensor *zipped_expertwise_rowmap, DenseTensor *token_prob_unzipped, DenseTensor *XScale_unzipped, DenseTensor *global_expertwise_block_cumsum, DenseTensor *expert_indices, int total_zipped_tokens_num, int token_length, int topk, int num_experts, int scale_length, bool do_gather, bool using_ue8m0_scale, bool return_expert_indices) { static int capability = dev_ctx.GetComputeCapability(); dispatch::TokenType(X.dtype(), [&](auto token_tag, auto has_scale_tag) { using TokenT = typename decltype(token_tag)::type; constexpr bool HasScale = decltype(has_scale_tag)::value; dispatch::ProbType(expert_prob_topk.dtype(), [&](auto prob_tag) { using ProbT = typename decltype(prob_tag)::type; dispatch::ScaleType(using_ue8m0_scale, [&](auto scale_tag) { using ScaleT = typename decltype(scale_tag)::type; dispatch::Bools( [&](auto do_gather_tag, auto return_indices_tag) { constexpr bool DoGather = decltype(do_gather_tag)::value; constexpr bool ReturnIndices = decltype(return_indices_tag)::value; dispatch::TopK(topk, [&](auto topk_tag) { constexpr int TK = decltype(topk_tag)::value; launch_permute_kernel(dev_ctx, X, expert_routemap_topk, expert_prob_topk, XScale, expert_offsets, expert_offset_end, X_unzipped, zipped_expertwise_rowmap, token_prob_unzipped, XScale_unzipped, global_expertwise_block_cumsum, expert_indices, total_zipped_tokens_num, token_length, scale_length, num_experts, topk, capability); }); }, do_gather, return_expert_indices); }); }); }); } // ============================================================================ // Preprocessing helpers // ============================================================================ template void dispatch_preprocess(const Context &dev_ctx, TokenT *X_unzipped_ptr, ScaleT *XScale_unzipped_ptr, float *token_prob_unzipped_ptr, int *expert_indices_ptr, int cols, int quanted_cols, const std::vector &padding_rows) { if (padding_rows.empty()) return; DenseTensor padding_tokens_tensor; padding_tokens_tensor.Resize({static_cast(padding_rows.size())}); dev_ctx.template Alloc(&padding_tokens_tensor); auto *stable_padding_rows = backends::gpu::RestoreHostMemIfCapturingCUDAGraph( const_cast(padding_rows.data()), padding_rows.size()); PADDLE_ENFORCE_GPU_SUCCESS(cudaMemcpyAsync(padding_tokens_tensor.data(), stable_padding_rows, sizeof(int) * padding_rows.size(), cudaMemcpyHostToDevice, dev_ctx.stream())); PADDLE_ENFORCE_LE_UINT32_MAX(padding_rows.size(), "padding_rows size"); dim3 grid{static_cast(padding_rows.size())}; dim3 block{512}; const int *padding_ptr = padding_tokens_tensor.data(); dispatch::Bools( [&](auto fill_x_tag, auto fill_scale_tag, auto fill_indices_tag) { constexpr bool FillX = decltype(fill_x_tag)::value; constexpr bool FillScale = decltype(fill_scale_tag)::value; constexpr bool FillIndices = decltype(fill_indices_tag)::value; filling_padding_rows_kernel <<>>(X_unzipped_ptr, XScale_unzipped_ptr, token_prob_unzipped_ptr, expert_indices_ptr, cols, quanted_cols, padding_ptr); }, X_unzipped_ptr != nullptr, XScale_unzipped_ptr != nullptr, expert_indices_ptr != nullptr); } template void dispatch_preprocess_w_override(const Context &dev_ctx, const DenseTensor &expert_routemap_topk, const int num_experts, const int padding_alignment, const int override_buffer_size, const bool return_expert_indices, DenseTensor *expert_offset, DenseTensor *expert_offset_end, DenseTensor *expert_indices) { constexpr int BLOCK_SIZE = 1024; PADDLE_ENFORCE_LE( expert_routemap_topk.numel(), static_cast(std::numeric_limits::max()), common::errors::InvalidArgument( "expert_routemap_topk.numel() should be <= INT_MAX, but got %ld.", expert_routemap_topk.numel())); dispatch::Bools( [&](auto fill_expert_indices_tag) { constexpr bool FillExpertIndices = decltype(fill_expert_indices_tag)::value; const int smem_bytes = static_cast(sizeof(int32_t)) * num_experts * 2; routemap_digest_kernel <<<1, BLOCK_SIZE, smem_bytes, dev_ctx.stream()>>>( expert_routemap_topk.data(), expert_offset->data(), expert_offset_end->data(), expert_indices->data(), expert_routemap_topk.numel(), num_experts, padding_alignment); }, return_expert_indices); } // ============================================================================ // CPP Interface // ============================================================================ template void MoePermuteKernel(const Context &dev_ctx, const DenseTensor &X, const paddle::optional &XScale, const DenseTensor &expert_routemap_topk, const DenseTensor &expert_prob_topk, const int num_experts, const std::vector &tokens_per_expert, const int padding_alignment, const bool do_gather, const bool using_ue8m0_scale, const bool return_expert_indices, const int override_buffer_size, DenseTensor *X_unzipped, DenseTensor *zipped_expertwise_rowmap, DenseTensor *token_prob_unzipped, DenseTensor *XScale_unzipped, DenseTensor *expert_indices) { PADDLE_ENFORCE_EQ( X.dims().size(), 2, common::errors::InvalidArgument("Input X's dims should be 2, but got %u.", X.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_EQ( expert_prob_topk.dims().size(), 2, common::errors::InvalidArgument( "Input expert_prob_topk's dims should be 2, but got %u.", expert_prob_topk.dims().size())); PADDLE_ENFORCE_EQ(expert_prob_topk.dims(), expert_routemap_topk.dims(), common::errors::InvalidArgument( "Input expert_prob_topk's dims should be equal to " "expert_routemap_topk's dims, but got %s and %s.", expert_prob_topk.dims(), expert_routemap_topk.dims())); const int64_t rows = X.dims()[0]; const int64_t cols = X.dims()[1]; const int64_t topk = expert_routemap_topk.dims()[1]; PADDLE_ENFORCE_EQ( expert_routemap_topk.dims()[0], rows, common::errors::InvalidArgument( "Input expert_routemap_topk's first dimension should be equal to " "X.dims()[0], but got %ld and %ld.", expert_routemap_topk.dims()[0], rows)); PADDLE_ENFORCE_LE( rows, static_cast(std::numeric_limits::max()) - static_cast(kPermuteBlockSize), common::errors::InvalidArgument( "X.dims()[0] should be <= INT_MAX - %d, received: (%ld)", kPermuteBlockSize, rows)); PADDLE_ENFORCE_LE( cols, std::numeric_limits::max(), common::errors::InvalidArgument( "X.dims()[1] should be less than INT_MAX, received: (%ld)", cols)); PADDLE_ENFORCE_GE(topk, 1, common::errors::InvalidArgument( "topk should be > 0, received: (%ld)", topk)); PADDLE_ENFORCE_LE(topk, 16, common::errors::InvalidArgument( "topk should be <= 16, received: (%ld)", topk)); 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( "num_experts should be <= %d, received: (%d)", kMaxNumExperts, num_experts)); PADDLE_ENFORCE_GE(padding_alignment, 1, common::errors::InvalidArgument( "padding_alignment should be > 0, received: (%d)", padding_alignment)); PADDLE_ENFORCE_GE( override_buffer_size, -1, common::errors::InvalidArgument( "override_buffer_size should be -1 or non-negative, received: (%d)", override_buffer_size)); PADDLE_ENFORCE_LE( rows, static_cast(std::numeric_limits::max()) / static_cast(num_experts), common::errors::InvalidArgument( "X.dims()[0] * num_experts should be <= INT_MAX, received: %ld * %d.", rows, num_experts)); PADDLE_ENFORCE_LE( expert_routemap_topk.numel(), static_cast(std::numeric_limits::max()), common::errors::InvalidArgument( "expert_routemap_topk.numel() should be <= INT_MAX, but got %ld.", expert_routemap_topk.numel())); if (X.dtype() == DataType::FLOAT8_E4M3FN && do_gather) { PADDLE_ENFORCE_EQ(XScale.get_ptr() != nullptr, true, common::errors::InvalidArgument( "Input XScale should not be None when X's dtype is " "FLOAT8_E4M3FN and do_gather is True.")); } if (XScale.get_ptr() != nullptr) { PADDLE_ENFORCE_EQ(XScale.get_ptr()->dims().size(), 2, common::errors::InvalidArgument( "Input XScale's dims should be 2, but got %u.", XScale.get_ptr()->dims().size())); if (do_gather) { PADDLE_ENFORCE_EQ( XScale.get_ptr()->dims()[0], rows, common::errors::InvalidArgument( "Input XScale's first dimension should be equal to X.dims()[0], " "but got %ld and %ld.", XScale.get_ptr()->dims()[0], rows)); } } const int64_t quanted_cols = (XScale.get_ptr() != nullptr) ? XScale.get_ptr()->dims()[1] : 0; PADDLE_ENFORCE_LE( quanted_cols, std::numeric_limits::max(), common::errors::InvalidArgument( "quanted_cols should be less than INT_MAX, received: (%ld)", quanted_cols)); const bool is_buffer_overridden = (override_buffer_size >= 0); // Output allocation void *XScale_unzipped_ptr = nullptr; dev_ctx.template Alloc(X_unzipped); dev_ctx.template Alloc(zipped_expertwise_rowmap); dev_ctx.template Alloc(token_prob_unzipped); dev_ctx.template Alloc(expert_indices); auto X_unzipped_ptr = reinterpret_cast(X_unzipped->data()); auto token_prob_unzipped_ptr = reinterpret_cast(token_prob_unzipped->data()); if (using_ue8m0_scale) { dev_ctx.template Alloc(XScale_unzipped); XScale_unzipped_ptr = reinterpret_cast(XScale_unzipped->data()); } else { dev_ctx.template Alloc(XScale_unzipped); XScale_unzipped_ptr = reinterpret_cast(XScale_unzipped->data()); } // Pre-fill expert_indices with -1 via hardware DMA engine (cudaMemsetAsync). // (Even if input is 0-size) // 0xFF byte-pattern on int32 = 0xFFFFFFFF = -1 in two's complement. // This offloads the bulk -1 fill (~10K-500K int32s) from SM compute to the // DMA copy engine, running in parallel with subsequent kernel execution. if (is_buffer_overridden && return_expert_indices) { PADDLE_ENFORCE_GPU_SUCCESS(cudaMemsetAsync( expert_indices->data(), 0xFF, static_cast(override_buffer_size) * sizeof(int32_t), dev_ctx.stream())); } // Handle empty input: initialize all outputs properly if (X.numel() == 0) return; // Preprocess constexpr int kEffectiveBlockSize = kPermuteBlockSize; const int64_t cumsum_blocknum_i64 = (rows + kEffectiveBlockSize - 1) / kEffectiveBlockSize; PADDLE_ENFORCE_LE( cumsum_blocknum_i64 + 2, static_cast(std::numeric_limits::max()) / static_cast(num_experts), common::errors::InvalidArgument( "The cumsum buffer size of moe_permute should be <= INT_MAX, but got " "(%ld + 2) * %d.", cumsum_blocknum_i64, num_experts)); const int cumsum_blocknum = static_cast(cumsum_blocknum_i64); DenseTensor expert_offset_tensor; DenseTensor expert_offset_end_tensor; DenseTensor global_expertwise_block_cumsum; expert_offset_tensor.Resize({kMaxNumExperts}); expert_offset_end_tensor.Resize({kMaxNumExperts}); global_expertwise_block_cumsum.Resize( {static_cast(cumsum_blocknum + 2), static_cast(num_experts)}); dev_ctx.template Alloc(&expert_offset_tensor); dev_ctx.template Alloc(&expert_offset_end_tensor); dev_ctx.template Alloc(&global_expertwise_block_cumsum); // Pre-fill rowmap with -1 via bulk DMA (replaces scattered per-block writes) PADDLE_ENFORCE_GPU_SUCCESS( cudaMemsetAsync(zipped_expertwise_rowmap->data(), -1, zipped_expertwise_rowmap->numel() * sizeof(int), dev_ctx.stream())); if (cumsum_blocknum > 1) { PADDLE_ENFORCE_GPU_SUCCESS( cudaMemsetAsync(global_expertwise_block_cumsum.data(), -1, global_expertwise_block_cumsum.numel() * sizeof(int), dev_ctx.stream())); } if (is_buffer_overridden) { dispatch_preprocess_w_override(dev_ctx, expert_routemap_topk, num_experts, padding_alignment, override_buffer_size, return_expert_indices, &expert_offset_tensor, &expert_offset_end_tensor, expert_indices); } else { int64_t tokens_cumulated = 0; std::vector padding_rows; int expert_offset[kMaxNumExperts]; int expert_offset_end[kMaxNumExperts]; for (int i = 0; i < kMaxNumExperts; i++) { if (i < num_experts) { PADDLE_ENFORCE_LE_INT_MAX(tokens_cumulated, "expert offset"); expert_offset[i] = static_cast(tokens_cumulated); const int64_t tokens = tokens_per_expert[i]; const int64_t padded_tokens = ((tokens + padding_alignment - 1) / padding_alignment) * padding_alignment; const int64_t expert_offset_end_64 = tokens_cumulated + tokens - 1; PADDLE_ENFORCE_LE_INT_MAX(expert_offset_end_64, "expert offset end"); expert_offset_end[i] = static_cast(expert_offset_end_64); tokens_cumulated += padded_tokens; } else { expert_offset[i] = 0; } } auto *stable_expert_offset = backends::gpu::RestoreHostMemIfCapturingCUDAGraph(expert_offset, kMaxNumExperts); PADDLE_ENFORCE_GPU_SUCCESS(cudaMemcpyAsync(expert_offset_tensor.data(), stable_expert_offset, sizeof(int) * kMaxNumExperts, cudaMemcpyHostToDevice, dev_ctx.stream())); auto *stable_expert_offset_end = backends::gpu::RestoreHostMemIfCapturingCUDAGraph(expert_offset_end, kMaxNumExperts); PADDLE_ENFORCE_GPU_SUCCESS( cudaMemcpyAsync(expert_offset_end_tensor.data(), stable_expert_offset_end, sizeof(int) * kMaxNumExperts, cudaMemcpyHostToDevice, dev_ctx.stream())); for (int i = 0; i < num_experts; i++) { int64_t next_expert_offset = i < num_experts - 1 ? expert_offset[i + 1] : tokens_cumulated; int64_t invalid_rows = next_expert_offset - expert_offset[i] - tokens_per_expert[i]; int64_t cur_expert_end = expert_offset[i] + tokens_per_expert[i]; if (invalid_rows > 0) { PADDLE_ENFORCE_LE_INT_MAX(cur_expert_end + invalid_rows - 1, "padding row"); } for (int64_t j = 0; j < invalid_rows; ++j) { padding_rows.push_back(static_cast(cur_expert_end + j)); } } if (using_ue8m0_scale) { dispatch_preprocess(dev_ctx, do_gather ? X_unzipped->data() : nullptr, XScale ? XScale_unzipped->data() : nullptr, token_prob_unzipped->data(), expert_indices->data(), static_cast(cols), static_cast(quanted_cols), padding_rows); } else { dispatch_preprocess(dev_ctx, do_gather ? X_unzipped->data() : nullptr, XScale ? XScale_unzipped->data() : nullptr, token_prob_unzipped->data(), expert_indices->data(), static_cast(cols), static_cast(quanted_cols), padding_rows); } } // Kernel dispatch dispatch_permute_kernel( dev_ctx, X, expert_routemap_topk, expert_prob_topk, XScale, expert_offset_tensor, expert_offset_end_tensor, X_unzipped, zipped_expertwise_rowmap, token_prob_unzipped, XScale_unzipped, &global_expertwise_block_cumsum, expert_indices, static_cast(rows), static_cast(cols), static_cast(topk), num_experts, static_cast(quanted_cols), do_gather, using_ue8m0_scale, !is_buffer_overridden && return_expert_indices); } } // namespace phi PD_REGISTER_KERNEL(moe_permute, GPU, ALL_LAYOUT, phi::MoePermuteKernel, phi::float8_e4m3fn, phi::bfloat16) {}