/************************************************************************* * Copyright (c) 2022-2024, NVIDIA CORPORATION & AFFILIATES. All rights reserved. * * See LICENSE for license information. ************************************************************************/ #include "permute.h" #include #include #include #include "cuda_runtime.h" #include "device_launch_parameters.h" #include "ATen/cuda/CUDAContext.h" #include "cutlass/arch/memory.h" #include "cutlass/arch/cache_operation.h" #include "cutlass/array.h" #include "cutlass/numeric_conversion.h" using torch::Tensor; template inline T *get_ptr(torch::Tensor &t) { return reinterpret_cast(t.data_ptr()); } ///////////////////////////////////////////////////////////////////////////////////////////////// // // Top K // ///////////////////////////////////////////////////////////////////////////////////////////////// static __global__ void moe_permute_topK_row_map( const int *sorted_row_id, int *row_id_map, const int num_rows, const int num_topK, const int num_out_tokens) { // Each block corresponds to one source token // row_id_map[num_topK][num_rows] const int bid = blockIdx.x; const int tid = threadIdx.x; const int idx = bid * blockDim.x + tid; if (idx >= num_rows * num_topK) return; int source_row = sorted_row_id[idx]; int source_token_id = source_row / num_topK; int source_topK_id = source_row % num_topK; if (idx >= num_out_tokens) { row_id_map[source_topK_id * num_rows + source_token_id] = -1; } else { row_id_map[source_topK_id * num_rows + source_token_id] = idx; } } template __global__ void moe_recover_topK_kernel(const T *input, T *unpermuted_output, const int *row_id_map, const float *prob, const int num_rows, const int num_topK, const int num_cols) { extern __shared__ int8_t s_mem[]; TCompute *s_prob = reinterpret_cast(s_mem); using FragmentLoadStore = cutlass::Array; using FragmentCompute = cutlass::Array; cutlass::NumericArrayConverter src_converter; cutlass::NumericArrayConverter dst_converter; // each block corresponds to one source token const int source_token = blockIdx.x; const int tid = threadIdx.x; if (hasProb) { for (int i = tid; i < num_topK; i += blockDim.x * blockDim.y) { s_prob[i] = TCompute(prob[source_token * num_topK + i]); } __syncthreads(); } for (int i = tid * kElementsPerAccess; i < num_cols; i += blockDim.x * kElementsPerAccess) { FragmentLoadStore frag_load_store; FragmentCompute frag_elem; FragmentCompute frag_sum; int source_row = row_id_map[source_token]; if (source_row != -1) { const T *source_row_ptr = input + source_row * num_cols; cutlass::arch::global_load( frag_load_store, (source_row_ptr + i), true); frag_sum = src_converter(frag_load_store); if (hasProb) { frag_sum = frag_sum * s_prob[0]; } } else { frag_sum.clear(); } for (int k = 1; k < num_topK; k++) { source_row = row_id_map[k * num_rows + source_token]; if (source_row == -1) continue; const T *source_row_ptr = input + source_row * num_cols; cutlass::arch::global_load( frag_load_store, (source_row_ptr + i), true); frag_elem = src_converter(frag_load_store); if (hasProb) { frag_elem = frag_elem * s_prob[k]; } for (int e = 0; e < kElementsPerAccess; e++) { frag_sum.at(e) = frag_sum.at(e) + frag_elem.at(e); } } T *dest_row_ptr = unpermuted_output + source_token * num_cols; frag_load_store = dst_converter(frag_sum); *(float4 *)(dest_row_ptr + i) = *(float4 *)(frag_load_store.data()); } } template __global__ void moe_permute_topK_kernel(const T *input_bwd, const T *input_fwd, T *act_grad, const float *prob, float *prob_grad, const int *row_id_map, const int num_rows, const int num_topK, const int num_cols) { extern __shared__ int8_t s_mem[]; TCompute *s_prob = reinterpret_cast(s_mem); using FragmentLoadStore = cutlass::Array; using FragmentCompute = cutlass::Array; cutlass::NumericArrayConverter src_converter; cutlass::NumericArrayConverter dst_converter; const int source_token = blockIdx.x; const int tid = threadIdx.x; if (hasProb) { for (int i = tid; i < num_topK; i += blockDim.x) { s_prob[i] = TCompute(prob[source_token * num_topK + i]); } __syncthreads(); } float accum[topKTile] = {0.0f}; FragmentLoadStore frag_load_store; const T *source_row_ptr = input_bwd + source_token * num_cols; for (int i = tid * kElementsPerAccess; i < num_cols; i += blockDim.x * kElementsPerAccess) { cutlass::arch::global_load( frag_load_store, (source_row_ptr + i), true); FragmentCompute frag_src = src_converter(frag_load_store); int index = source_token; for (int k = 0; k < topKTile; k++) { if (k == num_topK) break; int dest_row = row_id_map[index]; index += num_rows; if (dest_row == -1) continue; if (hasProb) { frag_load_store = dst_converter(frag_src * s_prob[k]); } else { frag_load_store = dst_converter(frag_src); } T *dest_row_ptr = act_grad + dest_row * num_cols; *(float4 *)(dest_row_ptr + i) = *(float4 *)(frag_load_store.data()); if (hasProb) { const T *input_fwd_ptr = input_fwd + dest_row * num_cols; cutlass::arch::global_load( frag_load_store, (input_fwd_ptr + i), true); FragmentCompute frag_input_fwd = src_converter(frag_load_store); for (int e = 0; e < kElementsPerAccess; e++) { accum[k] += float(frag_src.at(e) * frag_input_fwd.at(e)); } } } } if (hasProb) { for (int k = 0; k < topKTile; k++) { if (k == num_topK) break; for (int mask = 16; mask > 0; mask /= 2) { accum[k] = accum[k] + __shfl_xor_sync(0xffffffff, accum[k], mask, 32); } } if (tid == 0) { for (int k = 0; k < topKTile; k++) { if (k == num_topK) break; prob_grad[source_token * num_topK + k] = accum[k]; } } } } template void moe_permute_topK_kernel_launcher( const T *input, T *output, const int *sorted_row_id, int *row_id_map, const float *prob, const int num_rows, const int num_topK, const int num_cols, const int num_out_tokens, cudaStream_t stream, float *prob_grad = nullptr, const T *input_fwd = nullptr) { if (FWD) { if (prob_grad == nullptr) { // permute_topK fwd int threads = 64; int blocks = (num_rows * num_topK + threads - 1) / threads; moe_permute_topK_row_map<<>>( sorted_row_id, row_id_map, num_rows, num_topK, num_out_tokens); blocks = num_rows; threads = std::min(num_cols / kElementsPerAccess, 1024); moe_permute_topK_kernel<<>>( input, nullptr, output, nullptr, nullptr, row_id_map, num_rows, num_topK, num_cols); } else { // unpermute_topK bwd int blocks = num_rows; int threads = 32; size_t smem_bytes = num_topK * sizeof(TCompute); if (num_topK == 1) { moe_permute_topK_kernel<<>>( input, input_fwd, output, prob, prob_grad, row_id_map, num_rows, num_topK, num_cols); } else if (num_topK <= 8) { moe_permute_topK_kernel<<>>( input, input_fwd, output, prob, prob_grad, row_id_map, num_rows, num_topK, num_cols); } else if (num_topK <= 16) { moe_permute_topK_kernel<<>>( input, input_fwd, output, prob, prob_grad, row_id_map, num_rows, num_topK, num_cols); } else if (num_topK <= 32) { moe_permute_topK_kernel<<>>( input, input_fwd, output, prob, prob_grad, row_id_map, num_rows, num_topK, num_cols); } else if (num_topK <= 64) { moe_permute_topK_kernel<<>>( input, input_fwd, output, prob, prob_grad, row_id_map, num_rows, num_topK, num_cols); } else if (num_topK <= 128) { moe_permute_topK_kernel<<>>( input, input_fwd, output, prob, prob_grad, row_id_map, num_rows, num_topK, num_cols); } else { throw std::runtime_error("num_topK cannot exceed 128."); } } } else { int blocks = num_rows; int threads = std::min(num_cols / kElementsPerAccess, 1024); size_t smem_bytes = num_topK * sizeof(TCompute); if (num_topK == 1) { // permute_topK bwd with topK==1 moe_recover_topK_kernel<<>>( input, output, row_id_map, prob, num_rows, num_topK, num_cols); } else if (prob == nullptr) { // permute_topK bwd moe_recover_topK_kernel<<>>( input, output, row_id_map, prob, num_rows, num_topK, num_cols); } else { // unpermute_topK fwd moe_recover_topK_kernel<<>>( input, output, row_id_map, prob, num_rows, num_topK, num_cols); } } } ///////////////////////////////////////////////////////////////////////////////////////////////// // // Permute_topK OP // ///////////////////////////////////////////////////////////////////////////////////////////////// std::tuple> moe_permute_topK_op( Tensor input, Tensor indices, int64_t num_out_tokens, std::vector workspace, int64_t max_expanded_token_num) { const int num_tokens = input.size(0); const int num_cols = input.size(1); const int num_topK = indices.size(1); // initialize the workspace on the first run if (workspace.empty()) { auto options = torch::TensorOptions().dtype(torch::kInt32).device(torch::kCUDA).requires_grad(false); Tensor sorted_indices = torch::empty(max_expanded_token_num, options); Tensor row_id = torch::range(0, max_expanded_token_num - 1, 1, options); Tensor sorted_row_id = torch::empty(max_expanded_token_num, torch::dtype(torch::kInt32).device(torch::kCUDA).requires_grad(false)); size_t temp_storage_bytes = 0; int *temp_ptr = nullptr; cub::DeviceRadixSort::SortPairs(nullptr, temp_storage_bytes, temp_ptr, temp_ptr, temp_ptr, temp_ptr, max_expanded_token_num); Tensor temp_storage = torch::empty(temp_storage_bytes, torch::dtype(torch::kInt8).device(torch::kCUDA).requires_grad(false)); workspace.push_back(sorted_indices); workspace.push_back(row_id); workspace.push_back(sorted_row_id); workspace.push_back(temp_storage); } int *indices_ptr = get_ptr(indices); int *sorted_indices_ptr = get_ptr(workspace[0]); int *row_id_ptr = get_ptr(workspace[1]); int *sorted_row_id_ptr = get_ptr(workspace[2]); void *d_temp_storage = get_ptr(workspace[3]); size_t temp_storage_bytes = std::numeric_limits::max(); cub::DeviceRadixSort::SortPairs(d_temp_storage, temp_storage_bytes, indices_ptr, sorted_indices_ptr, row_id_ptr, sorted_row_id_ptr, num_tokens * num_topK); // activations type const at::ScalarType _st = input.scalar_type(); // Output buffer alloc num_out_tokens = (num_out_tokens > 0) ? num_out_tokens : num_tokens * num_topK; Tensor permuted_output = torch::empty({num_out_tokens, num_cols}, torch::dtype(_st).device(torch::kCUDA).requires_grad(false)); Tensor row_id_map = torch::empty({num_tokens * num_topK}, torch::dtype(torch::kInt32).device(torch::kCUDA).requires_grad(false)); int *row_id_map_ptr = get_ptr(row_id_map); auto stream = at::cuda::getCurrentCUDAStream().stream(); switch (_st) { case at::ScalarType::Float: { using dType = float; using dTypeCompute = float; dType *input_ptr = get_ptr(input); dType *permuted_output_ptr = get_ptr(permuted_output); moe_permute_topK_kernel_launcher( input_ptr, permuted_output_ptr, sorted_row_id_ptr, row_id_map_ptr, nullptr, num_tokens, num_topK, num_cols, num_out_tokens, stream); break; } case at::ScalarType::Half: { using dType = cutlass::half_t; using dTypeCompute = cutlass::half_t; dType *input_ptr = get_ptr(input); dType *permuted_output_ptr = get_ptr(permuted_output); moe_permute_topK_kernel_launcher( input_ptr, permuted_output_ptr, sorted_row_id_ptr, row_id_map_ptr, nullptr, num_tokens, num_topK, num_cols, num_out_tokens, stream); break; } #ifdef ENABLE_BF16 case at::ScalarType::BFloat16: { using dType = cutlass::bfloat16_t; using dTypeCompute = cutlass::bfloat16_t; dType *input_ptr = get_ptr(input); dType *permuted_output_ptr = get_ptr(permuted_output); moe_permute_topK_kernel_launcher( input_ptr, permuted_output_ptr, sorted_row_id_ptr, row_id_map_ptr, nullptr, num_tokens, num_topK, num_cols, num_out_tokens, stream); break; } #endif #ifdef ENABLE_FP8 case at::ScalarType::Float8_e5m2: { using dType = cutlass::float_e5m2_t; using dTypeCompute = cutlass::half_t; dType *input_ptr = get_ptr(input); dType *permuted_output_ptr = get_ptr(permuted_output); moe_permute_topK_kernel_launcher( input_ptr, permuted_output_ptr, sorted_row_id_ptr, row_id_map_ptr, nullptr, num_tokens, num_topK, num_cols, num_out_tokens, stream); break; } case at::ScalarType::Float8_e4m3fn: { using dType = cutlass::float_e4m3_t; using dTypeCompute = cutlass::half_t; dType *input_ptr = get_ptr(input); dType *permuted_output_ptr = get_ptr(permuted_output); moe_permute_topK_kernel_launcher( input_ptr, permuted_output_ptr, sorted_row_id_ptr, row_id_map_ptr, nullptr, num_tokens, num_topK, num_cols, num_out_tokens, stream); break; } #endif default: throw std::runtime_error("Wrong activation tensor type."); } return std::make_tuple(permuted_output, row_id_map, workspace); } ///////////////////////////////////////////////////////////////////////////////////////////////// // // Unpermute_topK OP // ///////////////////////////////////////////////////////////////////////////////////////////////// Tensor moe_recover_topK_op( Tensor input, Tensor row_id_map, Tensor prob, int64_t num_tokens, int64_t num_topK) { const int num_cols = input.size(1); // activations type const at::ScalarType _st = input.scalar_type(); // Output buffer alloc Tensor unpermuted_output = torch::empty({num_tokens, num_cols}, torch::dtype(_st).device(torch::kCUDA).requires_grad(false)); int *row_id_map_ptr = get_ptr(row_id_map); float *prob_ptr = (prob.defined()) ? get_ptr(prob) : nullptr; auto stream = at::cuda::getCurrentCUDAStream().stream(); switch (_st) { case at::ScalarType::Float: { using dType = float; using dTypeCompute = float; dType *input_ptr = get_ptr(input); dType *unpermuted_output_ptr = get_ptr(unpermuted_output); moe_permute_topK_kernel_launcher( input_ptr, unpermuted_output_ptr, nullptr, row_id_map_ptr, prob_ptr, num_tokens, num_topK, num_cols, 0, stream); break; } case at::ScalarType::Half: { using dType = cutlass::half_t; using dTypeCompute = cutlass::half_t; dType *input_ptr = get_ptr(input); dType *unpermuted_output_ptr = get_ptr(unpermuted_output); moe_permute_topK_kernel_launcher( input_ptr, unpermuted_output_ptr, nullptr, row_id_map_ptr, prob_ptr, num_tokens, num_topK, num_cols, 0, stream); break; } #ifdef ENABLE_BF16 case at::ScalarType::BFloat16: { using dType = cutlass::bfloat16_t; using dTypeCompute = cutlass::bfloat16_t; dType *input_ptr = get_ptr(input); dType *unpermuted_output_ptr = get_ptr(unpermuted_output); moe_permute_topK_kernel_launcher( input_ptr, unpermuted_output_ptr, nullptr, row_id_map_ptr, prob_ptr, num_tokens, num_topK, num_cols, 0, stream); break; } #endif #ifdef ENABLE_FP8 case at::ScalarType::Float8_e5m2: { using dType = cutlass::float_e5m2_t; using dTypeCompute = cutlass::half_t; dType *input_ptr = get_ptr(input); dType *unpermuted_output_ptr = get_ptr(unpermuted_output); moe_permute_topK_kernel_launcher( input_ptr, unpermuted_output_ptr, nullptr, row_id_map_ptr, prob_ptr, num_tokens, num_topK, num_cols, 0, stream); break; } case at::ScalarType::Float8_e4m3fn: { using dType = cutlass::float_e4m3_t; using dTypeCompute = cutlass::half_t; dType *input_ptr = get_ptr(input); dType *unpermuted_output_ptr = get_ptr(unpermuted_output); moe_permute_topK_kernel_launcher( input_ptr, unpermuted_output_ptr, nullptr, row_id_map_ptr, prob_ptr, num_tokens, num_topK, num_cols, 0, stream); break; } #endif default: throw std::runtime_error("Wrong activation tensor type."); } return unpermuted_output; } std::tuple moe_recover_topK_bwd_op( Tensor input_bwd, Tensor input_fwd, Tensor row_id_map, Tensor prob) { const int num_tokens = prob.size(0); const int num_topK = prob.size(1); const int num_cols = input_bwd.size(1); int *row_id_map_ptr = get_ptr(row_id_map); float *prob_ptr = get_ptr(prob); // activations type const at::ScalarType _st = input_bwd.scalar_type(); // Output buffer alloc Tensor act_grad = torch::empty({input_fwd.size(0), num_cols}, torch::dtype(_st).device(torch::kCUDA).requires_grad(false)); Tensor prob_grad = torch::empty({num_tokens, num_topK}, torch::dtype(torch::kFloat32).device(torch::kCUDA).requires_grad(false)); float *prob_grad_ptr = get_ptr(prob_grad); auto stream = at::cuda::getCurrentCUDAStream().stream(); switch (_st) { case at::ScalarType::Float: { using dType = float; using dTypeCompute = float; dType *input_bwd_ptr = get_ptr(input_bwd); dType *input_fwd_ptr = get_ptr(input_fwd); dType *act_grad_ptr = get_ptr(act_grad); moe_permute_topK_kernel_launcher( input_bwd_ptr, act_grad_ptr, nullptr, row_id_map_ptr, prob_ptr, num_tokens, num_topK, num_cols, 0, stream, prob_grad_ptr, input_fwd_ptr); break; } case at::ScalarType::Half: { using dType = cutlass::half_t; using dTypeCompute = cutlass::half_t; dType *input_bwd_ptr = get_ptr(input_bwd); dType *input_fwd_ptr = get_ptr(input_fwd); dType *act_grad_ptr = get_ptr(act_grad); moe_permute_topK_kernel_launcher( input_bwd_ptr, act_grad_ptr, nullptr, row_id_map_ptr, prob_ptr, num_tokens, num_topK, num_cols, 0, stream, prob_grad_ptr, input_fwd_ptr); break; } #ifdef ENABLE_BF16 case at::ScalarType::BFloat16: { using dType = cutlass::bfloat16_t; using dTypeCompute = cutlass::bfloat16_t; dType *input_bwd_ptr = get_ptr(input_bwd); dType *input_fwd_ptr = get_ptr(input_fwd); dType *act_grad_ptr = get_ptr(act_grad); moe_permute_topK_kernel_launcher( input_bwd_ptr, act_grad_ptr, nullptr, row_id_map_ptr, prob_ptr, num_tokens, num_topK, num_cols, 0, stream, prob_grad_ptr, input_fwd_ptr); break; } #endif #ifdef ENABLE_FP8 case at::ScalarType::Float8_e5m2: { using dType = cutlass::float_e5m2_t; using dTypeCompute = cutlass::half_t; dType *input_bwd_ptr = get_ptr(input_bwd); dType *input_fwd_ptr = get_ptr(input_fwd); dType *act_grad_ptr = get_ptr(act_grad); moe_permute_topK_kernel_launcher( input_bwd_ptr, act_grad_ptr, nullptr, row_id_map_ptr, prob_ptr, num_tokens, num_topK, num_cols, 0, stream, prob_grad_ptr, input_fwd_ptr); break; } case at::ScalarType::Float8_e4m3fn: { using dType = cutlass::float_e4m3_t; using dTypeCompute = cutlass::half_t; dType *input_bwd_ptr = get_ptr(input_bwd); dType *input_fwd_ptr = get_ptr(input_fwd); dType *act_grad_ptr = get_ptr(act_grad); moe_permute_topK_kernel_launcher( input_bwd_ptr, act_grad_ptr, nullptr, row_id_map_ptr, prob_ptr, num_tokens, num_topK, num_cols, 0, stream, prob_grad_ptr, input_fwd_ptr); break; } #endif default: throw std::runtime_error("Wrong activation tensor type."); } return std::make_tuple(act_grad, prob_grad); }