Files
2026-07-13 12:40:42 +08:00

1022 lines
42 KiB
Plaintext

// 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 <limits>
#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 <cooperative_groups.h>
#include <cooperative_groups/memcpy_async.h>
#include <cuda/barrier>
#include <cuda/pipeline>
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 <typename TokenT,
typename IndexT,
typename ProbT,
typename ScaleT,
bool has_scale,
bool do_gather,
bool return_expert_indices,
int TOPK,
bool USE_TMA,
int ROWS_PER_BLOCK = kPermuteBlockSize,
int BLOCK_DIM_X = kPermuteBlockDimX>
__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<uint32_t *>(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<int>(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<scope, stages> pstate;
#pragma nv_diag_default 20054
[[maybe_unused]] auto pipe = cuda::make_pipeline(cg_block, &pstate);
if constexpr (USE_TMA) {
ping_buffer = reinterpret_cast<TokenT *>(tma_base);
pong_buffer =
reinterpret_cast<TokenT *>(tma_base + token_length * sizeof(TokenT));
shared_routemap = reinterpret_cast<IndexT *>(tma_base + 2 * token_length *
sizeof(TokenT));
shared_probs =
reinterpret_cast<ProbT *>(reinterpret_cast<char *>(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<int64_t>(block_row_base) * topk,
local_elems * sizeof(IndexT),
pipe);
cuda::memcpy_async(cg_block,
shared_probs,
probs_topk + static_cast<int64_t>(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<int64_t>(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<int64_t>(global_row) * topk + col];
prob = probs_topk[static_cast<int64_t>(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<int64_t>(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<int *>(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<int64_t>(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 <typename TokenT,
typename ProbT,
typename IntT,
typename ScaleT,
bool HasScale,
bool DoGather,
bool ReturnIndices,
int TOPK>
void launch_permute_kernel(const GPUContext &dev_ctx,
const DenseTensor &X,
const DenseTensor &expert_routemap_topk,
const DenseTensor &expert_prob_topk,
const paddle::optional<DenseTensor> &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<int64_t>(total_zipped_tokens_num) + ROWS_PER_BLOCK - 1) /
ROWS_PER_BLOCK;
PADDLE_ENFORCE_LE(
grid_x,
static_cast<int64_t>(std::numeric_limits<unsigned int>::max()),
common::errors::InvalidArgument(
"The grid size of moe_permute should be <= UINT_MAX, but got %ld.",
grid_x));
dim3 grid(static_cast<unsigned int>(grid_x));
dim3 block(BLOCK_DIM_X);
const TokenT *x_ptr = X.data<TokenT>();
const IntT *routemap_ptr = expert_routemap_topk.data<IntT>();
const ProbT *prob_ptr = expert_prob_topk.data<ProbT>();
const ScaleT *scale_ptr = XScale ? XScale.get_ptr()->data<ScaleT>() : nullptr;
const int *offset_ptr = expert_offsets.data<int>();
const int *offset_end_ptr = expert_offset_end.data<int>();
TokenT *x_out_ptr = X_unzipped->data<TokenT>();
IntT *rowmap_out_ptr = zipped_expertwise_rowmap->data<IntT>();
ProbT *prob_out_ptr = token_prob_unzipped->data<ProbT>();
ScaleT *scale_out_ptr = XScale_unzipped->data<ScaleT>();
int *cumsum_ptr = global_expertwise_block_cumsum->data<int>();
int *expert_indices_ptr =
(ReturnIndices && expert_indices) ? expert_indices->data<int>() : 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<int>(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<TokenT,
IntT,
ProbT,
ScaleT,
HasScale,
DoGather,
ReturnIndices,
TOPK,
UseTMA,
ROWS_PER_BLOCK,
BLOCK_DIM_X>;
if (smem > 48 * 1024) {
PADDLE_ENFORCE_GPU_SUCCESS(cudaFuncSetAttribute(
kernel_ptr, cudaFuncAttributeMaxDynamicSharedMemorySize, smem));
}
kernel_ptr<<<grid, block, smem, dev_ctx.stream()>>>(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 <typename T, typename Context>
void dispatch_permute_kernel(const Context &dev_ctx,
const DenseTensor &X,
const DenseTensor &expert_routemap_topk,
const DenseTensor &expert_prob_topk,
const paddle::optional<DenseTensor> &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<TokenT,
ProbT,
int,
ScaleT,
HasScale,
DoGather,
ReturnIndices,
TK>(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 <typename TokenT, typename ScaleT, typename Context>
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<int> &padding_rows) {
if (padding_rows.empty()) return;
DenseTensor padding_tokens_tensor;
padding_tokens_tensor.Resize({static_cast<int64_t>(padding_rows.size())});
dev_ctx.template Alloc<int>(&padding_tokens_tensor);
auto *stable_padding_rows = backends::gpu::RestoreHostMemIfCapturingCUDAGraph(
const_cast<int *>(padding_rows.data()), padding_rows.size());
PADDLE_ENFORCE_GPU_SUCCESS(cudaMemcpyAsync(padding_tokens_tensor.data<int>(),
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<uint32_t>(padding_rows.size())};
dim3 block{512};
const int *padding_ptr = padding_tokens_tensor.data<int>();
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<TokenT,
ScaleT,
FillX,
FillScale,
FillIndices>
<<<grid, block, 0, dev_ctx.stream()>>>(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 <typename Context>
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<int64_t>(std::numeric_limits<int32_t>::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<int>(sizeof(int32_t)) * num_experts * 2;
routemap_digest_kernel<FillExpertIndices, BLOCK_SIZE>
<<<1, BLOCK_SIZE, smem_bytes, dev_ctx.stream()>>>(
expert_routemap_topk.data<int32_t>(),
expert_offset->data<int32_t>(),
expert_offset_end->data<int32_t>(),
expert_indices->data<int32_t>(),
expert_routemap_topk.numel(),
num_experts,
padding_alignment);
},
return_expert_indices);
}
// ============================================================================
// CPP Interface
// ============================================================================
template <typename T, typename Context>
void MoePermuteKernel(const Context &dev_ctx,
const DenseTensor &X,
const paddle::optional<DenseTensor> &XScale,
const DenseTensor &expert_routemap_topk,
const DenseTensor &expert_prob_topk,
const int num_experts,
const std::vector<int> &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<int64_t>(std::numeric_limits<int32_t>::max()) -
static_cast<int64_t>(kPermuteBlockSize),
common::errors::InvalidArgument(
"X.dims()[0] should be <= INT_MAX - %d, received: (%ld)",
kPermuteBlockSize,
rows));
PADDLE_ENFORCE_LE(
cols,
std::numeric_limits<int32_t>::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<int64_t>(std::numeric_limits<int32_t>::max()) /
static_cast<int64_t>(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<int64_t>(std::numeric_limits<int32_t>::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<int32_t>::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<T>(X_unzipped);
dev_ctx.template Alloc<int>(zipped_expertwise_rowmap);
dev_ctx.template Alloc<float>(token_prob_unzipped);
dev_ctx.template Alloc<int>(expert_indices);
auto X_unzipped_ptr = reinterpret_cast<void *>(X_unzipped->data<T>());
auto token_prob_unzipped_ptr =
reinterpret_cast<void *>(token_prob_unzipped->data<float>());
if (using_ue8m0_scale) {
dev_ctx.template Alloc<int32_t>(XScale_unzipped);
XScale_unzipped_ptr =
reinterpret_cast<void *>(XScale_unzipped->data<int32_t>());
} else {
dev_ctx.template Alloc<float>(XScale_unzipped);
XScale_unzipped_ptr =
reinterpret_cast<void *>(XScale_unzipped->data<float>());
}
// 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<int32_t>(),
0xFF,
static_cast<size_t>(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<int64_t>(std::numeric_limits<int32_t>::max()) /
static_cast<int64_t>(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<int>(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<int64_t>(cumsum_blocknum + 2),
static_cast<int64_t>(num_experts)});
dev_ctx.template Alloc<int>(&expert_offset_tensor);
dev_ctx.template Alloc<int>(&expert_offset_end_tensor);
dev_ctx.template Alloc<int>(&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<int>(),
-1,
zipped_expertwise_rowmap->numel() * sizeof(int),
dev_ctx.stream()));
if (cumsum_blocknum > 1) {
PADDLE_ENFORCE_GPU_SUCCESS(
cudaMemsetAsync(global_expertwise_block_cumsum.data<int>(),
-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<int> 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<int>(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<int>(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<int>(),
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<int>(),
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<int>(cur_expert_end + j));
}
}
if (using_ue8m0_scale) {
dispatch_preprocess(dev_ctx,
do_gather ? X_unzipped->data<T>() : nullptr,
XScale ? XScale_unzipped->data<int32_t>() : nullptr,
token_prob_unzipped->data<float>(),
expert_indices->data<int>(),
static_cast<int>(cols),
static_cast<int>(quanted_cols),
padding_rows);
} else {
dispatch_preprocess(dev_ctx,
do_gather ? X_unzipped->data<T>() : nullptr,
XScale ? XScale_unzipped->data<float>() : nullptr,
token_prob_unzipped->data<float>(),
expert_indices->data<int>(),
static_cast<int>(cols),
static_cast<int>(quanted_cols),
padding_rows);
}
}
// Kernel dispatch
dispatch_permute_kernel<T, Context>(
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<int>(rows),
static_cast<int>(cols),
static_cast<int>(topk),
num_experts,
static_cast<int>(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) {}