Files
2026-07-13 13:18:33 +08:00

195 lines
6.9 KiB
Plaintext

// Copyright (c) Microsoft Corporation.
// SPDX-License-Identifier: Apache-2.0
// DeepSpeed Team
#include <cassert>
#include "custom_cuda_layers.h"
#include "memory_access_utils.h"
namespace cg = cooperative_groups;
namespace td_sort {
constexpr int threads = 512;
constexpr int granularity = 16;
constexpr int mem_vals = granularity / sizeof(int32_t);
constexpr int max_buffer_size = (threads + 1) * mem_vals;
#ifdef __HIP_PLATFORM_AMD__
constexpr int warp_size = ROCM_WAVEFRONT_SIZE;
#else
constexpr int warp_size = 32;
#endif
constexpr int max_warps = threads / warp_size;
} // namespace td_sort
template <int VALS_PER_THREAD>
__global__ void scan_sort(int32_t* data, int reserved_tokens, int original_tokens)
{
cg::thread_block tb = cg::this_thread_block();
cg::thread_block_tile<td_sort::warp_size> warp = cg::tiled_partition<td_sort::warp_size>(tb);
__shared__ int32_t indices_buffer[td_sort::max_buffer_size];
__shared__ int32_t intermediate_buffer[td_sort::max_warps];
__shared__ int32_t sorted_indices_buffer[td_sort::max_buffer_size];
for (int i = tb.thread_index().x * td_sort::mem_vals; i < original_tokens + 1;
i += tb.group_dim().x * td_sort::mem_vals) {
uint32_t zeros[td_sort::mem_vals] = {0, 0, 0, 0};
mem_access::store_shared<td_sort::granularity>(indices_buffer + i, zeros);
}
int32_t local_vals[VALS_PER_THREAD];
// We flatten layers/batch into a single indexing dimension
int32_t* data_block = data + tb.group_index().x * reserved_tokens;
// The next two loops really could be fused for a more logical code layout, but don't want to
// move the barrier forward
#pragma unroll
for (int i = 0; i < VALS_PER_THREAD; i++) {
const int iter_idx = i * td_sort::threads + tb.thread_index().x;
if (iter_idx < reserved_tokens) {
mem_access::load_global<sizeof(int32_t)>(local_vals + i, data_block + iter_idx);
} else {
local_vals[i] = 0;
}
}
tb.sync();
#pragma unroll
for (int i = 0; i < VALS_PER_THREAD; i++) {
const int iter_idx = i * td_sort::threads + tb.thread_index().x;
if (iter_idx < reserved_tokens) {
const int32_t one = 1;
mem_access::store_shared<sizeof(int32_t)>(indices_buffer + local_vals[i], &one);
}
}
tb.sync();
int32_t local_input[td_sort::mem_vals];
mem_access::load_shared<td_sort::granularity>(
local_input, indices_buffer + tb.thread_index().x * td_sort::mem_vals);
int32_t reduce_vals[td_sort::mem_vals];
reduce_vals[0] = local_input[0];
#pragma unroll
for (int i = 1; i < td_sort::mem_vals; i++) {
reduce_vals[i] = local_input[i] + reduce_vals[i - 1];
}
int32_t step_1_val = reduce_vals[td_sort::mem_vals - 1];
// Short span exclusive scan algorithm (less work efficient)
#pragma unroll
for (int i = 1; i < td_sort::warp_size; i *= 2) {
int32_t step_val = warp.shfl_up(step_1_val, i);
step_1_val = (warp.thread_rank() < i) ? step_1_val : step_1_val + step_val;
}
if (warp.thread_rank() == td_sort::warp_size - 1) {
mem_access::store_shared<sizeof(int32_t)>(intermediate_buffer + warp.meta_group_rank(),
&step_1_val);
}
tb.sync();
if (warp.meta_group_rank() == 0) {
int32_t step_2_val = 0;
if (warp.thread_rank() < td_sort::max_warps) {
mem_access::load_shared<sizeof(int32_t)>(&step_2_val,
intermediate_buffer + warp.thread_rank());
}
#pragma unroll
for (int i = 1; i < td_sort::warp_size; i *= 2) {
int32_t step_val = warp.shfl_up(step_2_val, i);
step_2_val = (warp.thread_rank() < i) ? step_2_val : step_2_val + step_val;
}
if (warp.thread_rank() < td_sort::max_warps) {
mem_access::store_shared<sizeof(int32_t)>(intermediate_buffer + warp.thread_rank(),
&step_2_val);
}
}
tb.sync();
int step_2_val = 0;
if (warp.meta_group_rank() > 0) {
mem_access::load_shared<sizeof(int32_t)>(&step_2_val,
intermediate_buffer + warp.meta_group_rank() - 1);
}
const int thread_offset = reduce_vals[td_sort::mem_vals - 1];
#pragma unroll
for (int i = 0; i < td_sort::mem_vals; i++) {
reduce_vals[i] += step_1_val + step_2_val - thread_offset;
}
mem_access::store_shared<td_sort::granularity>(
indices_buffer + tb.thread_index().x * td_sort::mem_vals, reduce_vals);
if (tb.thread_index().x == 0) {
indices_buffer[original_tokens] = original_tokens - indices_buffer[original_tokens];
}
tb.sync();
for (int i = 0; i < VALS_PER_THREAD; i++) {
const int iter_idx = i * td_sort::threads + tb.thread_index().x;
if (iter_idx < reserved_tokens) {
if (local_vals[i] == 0) {
int zero = 0;
mem_access::store_shared<sizeof(int32_t)>(sorted_indices_buffer, &zero);
} else {
int sorted_idx;
mem_access::load_shared<sizeof(int32_t)>(&sorted_idx,
indices_buffer + local_vals[i] - 1);
mem_access::store_shared<sizeof(int32_t)>(sorted_indices_buffer + sorted_idx,
local_vals + i);
}
}
}
tb.sync();
#pragma unroll
for (int i = 0; i < VALS_PER_THREAD; i++) {
const int iter_idx = i * td_sort::threads + tb.thread_index().x;
if (iter_idx < reserved_tokens) {
int32_t store_val;
mem_access::load_shared<sizeof(int32_t)>(&store_val, sorted_indices_buffer + iter_idx);
mem_access::store_global<sizeof(int32_t)>(data_block + iter_idx, &store_val);
}
}
}
void launch_token_sort(int32_t* indices,
int layers,
int batch_size,
int reserved_size,
int original_tokens,
cudaStream_t stream)
{
// Each sort is completely independent, can flatten this dimension
dim3 grid(layers * batch_size);
dim3 block(td_sort::threads);
const int vals_per_thread = (reserved_size + td_sort::threads - 1) / td_sort::threads;
if (vals_per_thread == 1) {
scan_sort<1><<<grid, block, 0, stream>>>(indices, reserved_size, original_tokens);
} else if (vals_per_thread == 2) {
scan_sort<2><<<grid, block, 0, stream>>>(indices, reserved_size, original_tokens);
} else if (vals_per_thread == 3) {
scan_sort<3><<<grid, block, 0, stream>>>(indices, reserved_size, original_tokens);
} else if (vals_per_thread == 4) {
scan_sort<4><<<grid, block, 0, stream>>>(indices, reserved_size, original_tokens);
} else {
assert(false);
}
}