Skip to content

Commit 8854fe8

Browse files
committed
Revert "remove hybrid sort"
This reverts commit 55c4aa7.
1 parent 55c4aa7 commit 8854fe8

8 files changed

Lines changed: 334 additions & 24 deletions

src/cuda/cuda_sampling.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,4 +42,4 @@ void DispatchBlockwiseSoftmaxForward(cudaStream_t stream, float* output, const f
4242
int input_stride, int output_stride, int batch_count);
4343

4444
} // namespace cuda
45-
} // namespace Generators
45+
} // namespace Generators

src/cuda/cuda_topk.cu

Lines changed: 31 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -5,38 +5,47 @@
55

66
#include "cuda_topk.h"
77
#include "cuda_topk_full_sort.cuh"
8+
#include "cuda_topk_hybrid_sort.cuh"
89
#include "cuda_topk_select_sort.cuh"
910

1011
namespace Generators {
1112
namespace cuda {
1213

14+
// Helper to determine the optimal partition size for the hybrid sort algorithm
15+
// based on vocabulary and batch size.
16+
inline int GetHybridSortPartitionSize(int vocab_size, int batch_size) {
17+
if (vocab_size >= 147456) {
18+
return (vocab_size > 256 * 1024) ? 8192 : 4096;
19+
} else {
20+
if (vocab_size >= 65536 || batch_size >= 4 && vocab_size > 49152) {
21+
return 2048;
22+
}
23+
}
24+
25+
return 1024;
26+
}
27+
1328
TopkData::TopkData(int batch_size, int vocab_size, cudaStream_t stream) {
29+
// The intermediate buffers are used by hybrid and full sort algorithms.
30+
int partition_size = GetHybridSortPartitionSize(vocab_size, batch_size);
31+
size_t intermediate_buffer_elements = GetHybridSortIntermediateSize(batch_size, vocab_size, partition_size);
32+
1433
size_t vocab_batch_size = static_cast<size_t>(vocab_size) * batch_size;
1534

35+
// Selection sort uses a buffer of batch_size * 64 elements, which is smaller than intermediate_buffer_elements.
36+
size_t max_buffer_elements = std::max(vocab_batch_size, intermediate_buffer_elements);
37+
1638
// Allocate all necessary device memory
17-
// The buffers are sized for the full sort algorithm, which is the largest.
18-
intermediate_indices_1 = CudaMallocArray<int>(vocab_batch_size);
19-
intermediate_indices_2 = CudaMallocArray<int>(vocab_batch_size);
20-
intermediate_scores_1 = CudaMallocArray<float>(vocab_batch_size);
21-
intermediate_scores_2 = CudaMallocArray<float>(vocab_batch_size);
39+
intermediate_indices_1 = CudaMallocArray<int>(max_buffer_elements);
40+
intermediate_indices_2 = CudaMallocArray<int>(max_buffer_elements);
41+
intermediate_scores_1 = CudaMallocArray<float>(max_buffer_elements);
42+
intermediate_scores_2 = CudaMallocArray<float>(max_buffer_elements);
2243
batch_offsets = CudaMallocArray<int>(batch_size + 1);
2344

2445
cub_temp_storage_bytes = GetFullSortCubTempStorageBytes(vocab_batch_size, batch_size, stream);
2546
cub_temp_storage = CudaMallocArray<unsigned char>(this->cub_temp_storage_bytes);
2647
}
2748

28-
// Kernel to compact strided data into a dense layout.
29-
// Used to convert data from a [batch, stride] layout to a dense [batch, k] layout.
30-
template <typename T>
31-
__global__ void CompactStridedData(const T* input, T* output, int k, int batch_size, int input_stride) {
32-
const int batch_idx = blockIdx.x;
33-
for (int i = threadIdx.x; i < k; i += blockDim.x) {
34-
int in_idx = batch_idx * input_stride + i;
35-
int out_idx = batch_idx * k + i;
36-
output[out_idx] = input[in_idx];
37-
}
38-
}
39-
4049
void TopkDataCompact::CompactOutput(int batch_size, int vocab_size, cudaStream_t stream, int k) {
4150
topk_scores_compact = CudaMallocArray<float>(static_cast<size_t>(batch_size) * k);
4251
topk_indices_compact = CudaMallocArray<int>(static_cast<size_t>(batch_size) * k);
@@ -50,12 +59,15 @@ void TopkDataCompact::CompactOutput(int batch_size, int vocab_size, cudaStream_t
5059
void GetTopK(TopkData* topk_data, cudaStream_t stream, const float* scores_in, int vocab_size, int batch_size, int k) {
5160
assert(topk_data != nullptr);
5261

53-
if (k > 64) {
62+
if (k > kHybridSortMaxK) {
5463
LaunchSort(topk_data, stream, scores_in, topk_data->intermediate_scores_1.get(),
5564
topk_data->intermediate_indices_1.get(), vocab_size, batch_size);
56-
} else {
65+
} else if (k <= 8 || vocab_size < 1024) {
5766
// NOTE: This modifies scores_in in-place
5867
RunTopKViaSelectionSort(topk_data, stream, const_cast<float*>(scores_in), vocab_size, batch_size, k);
68+
} else {
69+
int partition_size = GetHybridSortPartitionSize(vocab_size, batch_size);
70+
RunTopKViaHybridSort(topk_data, stream, scores_in, vocab_size, batch_size, k, partition_size);
5971
}
6072
}
6173

src/cuda/cuda_topk.h

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,18 +18,20 @@ struct TopkData {
1818

1919
// --- Intermediate Buffers for Top-K Algorithms ---
2020

21-
// Used to hold initial vocabulary indices for full sort.
21+
// Used to hold initial vocabulary indices for full sort, and intermediate
22+
// indices during the reduction phase of hybrid sort.
2223
cuda_unique_ptr<int> intermediate_indices_1;
2324

24-
// A dedicated "ping-pong" buffer for full sort.
25+
// A dedicated "ping-pong" buffer for the hybrid sort index reduction.
2526
cuda_unique_ptr<int> intermediate_indices_2;
2627

2728
// Primary buffer for holding raw scores.
2829
// - Full sort: Holds the fully sorted raw scores.
2930
// - Selection sort: Not used directly for output, but reserved.
31+
// - Hybrid sort: Holds intermediate and final reduced raw scores.
3032
cuda_unique_ptr<float> intermediate_scores_1;
3133

32-
// A secondary "ping-pong" buffer.
34+
// A secondary "ping-pong" buffer used by the hybrid sort's score reduction phase.
3335
cuda_unique_ptr<float> intermediate_scores_2;
3436

3537
// General-purpose temporary storage for CUB's DeviceSegmentedRadixSort (for full sort only).
@@ -42,7 +44,7 @@ struct TopkData {
4244
// --- Information of Final Output (Input to Sampling Stage) ---
4345
const float* topk_scores = nullptr; // pointer to the top-k scores data (in intermediate_scores_1 or intermediate_scores_2)
4446
const int* topk_indices = nullptr; // pointer to the top-k indices data (in intermediate_indices_1 or intermediate_indices_2)
45-
int topk_stride = 0; // stride of the top-k output data: k for selection sort, vocab_size for full sort
47+
int topk_stride = 0; // stride of the top-k output data: k for selection sort, vocab_size for full sort, max_k (kHybridSortMaxK) for hybrid sort
4648
};
4749

4850
// For parity test, a derived struct to help compact output buffers.
@@ -62,6 +64,7 @@ void GetTopK(TopkData* topk_data, cudaStream_t stream, const float* scores_in, i
6264
// to `scores_out` and `indices_out` in a compact [batch_size, k] layout.
6365
void RunTopKViaSelectionSort(TopkData* data, cudaStream_t stream, float* scores_in, int vocab_size, int batch_size, int k);
6466
void RunTopKViaFullSort(TopkData* data, cudaStream_t stream, const float* scores_in, int vocab_size, int batch_size, int k);
67+
void RunTopKViaHybridSort(TopkData* data, cudaStream_t stream, const float* scores_in, int vocab_size, int batch_size, int k, int partition_size);
6568

6669
} // namespace cuda
6770
} // namespace Generators
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
#pragma once
5+
6+
#include <float.h> // For FLT_MAX
7+
8+
namespace Generators {
9+
namespace cuda {
10+
namespace bitonic {
11+
12+
struct KeyValue {
13+
float score;
14+
int index;
15+
};
16+
17+
// Performs a full bitonic sort in shared memory for `SortSize` elements.
18+
template <int kBlockSize, int SortSize>
19+
__device__ void SharedMemBitonicSort(KeyValue* smem_data) {
20+
for (int k = 2; k <= SortSize; k <<= 1) {
21+
for (int j = k >> 1; j > 0; j >>= 1) {
22+
for (int i = threadIdx.x; i < SortSize; i += kBlockSize) {
23+
int ixj = i ^ j;
24+
if (ixj > i) {
25+
bool ascending = ((i & k) == 0);
26+
KeyValue* a = &smem_data[i];
27+
KeyValue* b = &smem_data[ixj];
28+
bool is_greater = (a->score > b->score) || (a->score == b->score && a->index < b->index);
29+
if (is_greater != ascending) {
30+
KeyValue temp = *a;
31+
*a = *b;
32+
*b = temp;
33+
}
34+
}
35+
}
36+
__syncthreads();
37+
}
38+
}
39+
40+
for (int j = SortSize >> 1; j > 0; j >>= 1) {
41+
for (int i = threadIdx.x; i < SortSize; i += kBlockSize) {
42+
int ixj = i ^ j;
43+
if (ixj > i) {
44+
KeyValue* a = &smem_data[i];
45+
KeyValue* b = &smem_data[ixj];
46+
if ((a->score < b->score) || (a->score == b->score && a->index > b->index)) {
47+
KeyValue temp = *a;
48+
*a = *b;
49+
*b = temp;
50+
}
51+
}
52+
}
53+
__syncthreads();
54+
}
55+
}
56+
57+
namespace reduction {
58+
template <int N>
59+
__device__ void RegisterBitonicSort(float scores[N], int indices[N]) {
60+
for (int k = 2; k <= N; k <<= 1) {
61+
for (int j = k >> 1; j > 0; j >>= 1) {
62+
#pragma unroll
63+
for (int i = 0; i < N; ++i) {
64+
int ixj = i ^ j;
65+
if (ixj > i) {
66+
bool ascending = ((i & k) == 0);
67+
bool is_greater = (scores[i] > scores[ixj]) || (scores[i] == scores[ixj] && indices[i] < indices[ixj]);
68+
if (is_greater != ascending) {
69+
float temp_s = scores[i];
70+
scores[i] = scores[ixj];
71+
scores[ixj] = temp_s;
72+
int temp_i = indices[i];
73+
indices[i] = indices[ixj];
74+
indices[ixj] = temp_i;
75+
}
76+
}
77+
}
78+
}
79+
}
80+
for (int j = N >> 1; j > 0; j >>= 1) {
81+
#pragma unroll
82+
for (int i = 0; i < N; ++i) {
83+
int ixj = i ^ j;
84+
if (ixj > i) {
85+
if ((scores[i] < scores[ixj]) || (scores[i] == scores[ixj] && indices[i] > indices[ixj])) {
86+
float temp_s = scores[i];
87+
scores[i] = scores[ixj];
88+
scores[ixj] = temp_s;
89+
int temp_i = indices[i];
90+
indices[i] = indices[ixj];
91+
indices[ixj] = temp_i;
92+
}
93+
}
94+
}
95+
}
96+
}
97+
98+
template <int kBlockSize, int K, int PartitionsPerBlock>
99+
__global__ void BlockReduceTopK(const float* __restrict__ scores_in, const int* __restrict__ indices_in,
100+
float* __restrict__ scores_out, int* __restrict__ indices_out, int num_partitions_in) {
101+
constexpr int SortSize = K * PartitionsPerBlock;
102+
__shared__ KeyValue smem_buffer[SortSize];
103+
104+
const int batch_idx = blockIdx.y;
105+
const int block_start_partition = blockIdx.x * PartitionsPerBlock;
106+
const int num_partitions_to_process = min(PartitionsPerBlock, num_partitions_in - block_start_partition);
107+
108+
const int in_base_offset = batch_idx * num_partitions_in * K;
109+
const int out_base_offset = (batch_idx * gridDim.x + blockIdx.x) * K;
110+
111+
for (int i = threadIdx.x; i < SortSize; i += kBlockSize) {
112+
if (i < K * num_partitions_to_process) {
113+
int partition_idx = i / K;
114+
int element_idx = i % K;
115+
int global_offset = in_base_offset + (block_start_partition + partition_idx) * K + element_idx;
116+
smem_buffer[i].score = scores_in[global_offset];
117+
smem_buffer[i].index = indices_in[global_offset];
118+
} else {
119+
smem_buffer[i].score = -FLT_MAX;
120+
smem_buffer[i].index = -1;
121+
}
122+
}
123+
__syncthreads();
124+
125+
SharedMemBitonicSort<kBlockSize, SortSize>(smem_buffer);
126+
127+
if (threadIdx.x < K) {
128+
indices_out[out_base_offset + threadIdx.x] = smem_buffer[threadIdx.x].index;
129+
scores_out[out_base_offset + threadIdx.x] = smem_buffer[threadIdx.x].score;
130+
}
131+
}
132+
} // namespace reduction
133+
134+
} // namespace bitonic
135+
} // namespace cuda
136+
} // namespace Generators

src/cuda/cuda_topk_full_sort.cuh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
#include <cub/device/device_segmented_radix_sort.cuh>
77
#include "cuda_topk.h"
8+
#include "cuda_topk_hybrid_sort.cuh" // For CompactStridedData
89

910
namespace Generators {
1011
namespace cuda {

0 commit comments

Comments
 (0)