main
cu 1,019 lines 40.7 KB
Raw
1 #include "../cuda_compat.h"
2 #include "dispatch_utils.h"
3 #include "torch_utils.h"
4
5 #include <cstring>
6
7 #ifndef USE_ROCM
8 #include <cub/cub.cuh>
9 #else
10 #include <hipcub/hipcub.hpp>
11 #endif
12
13 namespace vllm {
14
15 template <typename scalar_t>
16 __global__ void apply_repetition_penalties_kernel(
17 scalar_t* __restrict__ logits, // [num_seqs, vocab_size]
18 const bool* __restrict__ prompt_mask, // [num_seqs, vocab_size]
19 const bool* __restrict__ output_mask, // [num_seqs, vocab_size]
20 const scalar_t* __restrict__ repetition_penalties, // [num_seqs]
21 const int num_seqs, const int vocab_size, const int tile_size) {
22 // Each block handles one sequence and a tile of vocab
23 const int seq_idx = blockIdx.x;
24 if (seq_idx >= num_seqs) return;
25
26 const int tile_start = blockIdx.y * tile_size;
27 const int tile_end = min(tile_start + tile_size, vocab_size);
28
29 // Load repetition penalty for this sequence
30 const scalar_t penalty = repetition_penalties[seq_idx];
31
32 // Each thread processes multiple vocab items within the tile
33 for (int vocab_idx = tile_start + threadIdx.x; vocab_idx < tile_end;
34 vocab_idx += blockDim.x) {
35 const int64_t idx = static_cast<int64_t>(seq_idx) * vocab_size + vocab_idx;
36 const bool is_repeated = prompt_mask[idx] || output_mask[idx];
37 if (is_repeated) {
38 scalar_t logit = logits[idx];
39 if (logit > 0) {
40 logits[idx] = logit / penalty;
41 } else {
42 logits[idx] = logit * penalty;
43 }
44 }
45 }
46 }
47
48 template <int step>
49 static inline __device__ uint32_t extractBinIdx(float x) {
50 if constexpr (step == 0) {
51 __half hx = __float2half(x);
52 uint16_t bits = __half_as_ushort(hx);
53 bits = (bits & 0x8000) ? bits : ~bits & 0x7fff;
54 return bits >> 5;
55 } else {
56 uint32_t bits = __float_as_uint(x);
57 bits = (bits & 0x80000000) ? bits : ~bits & 0x7fffffff;
58
59 if constexpr (step == 1) {
60 return bits >> 21;
61 } else if constexpr (step == 2) {
62 return (bits >> 10) & 0x7ff;
63 } else if constexpr (step == 3) {
64 return bits & 0x3ff;
65 }
66 }
67 }
68
69 template <int shift>
70 static inline __device__ bool isPartialMatch(float x, uint32_t pattern) {
71 if constexpr (shift == 0) {
72 return true;
73 }
74 uint32_t bits = __float_as_uint(x);
75 bits = (bits & 0x80000000) ? bits : ~bits & 0x7fffffff;
76 return (bits ^ pattern) >> shift == 0;
77 }
78
79 /**
80 * Map a Func over the input data, using vectorized load instructions if
81 * possible.
82 *
83 * @tparam T element type
84 * @tparam IdxT indexing type
85 * @tparam Func void (T x, IdxT idx)
86 *
87 * @param thread_rank rank of the calling thread among all participating threads
88 * @param num_threads number of the threads that participate in processing
89 * @param in the input data
90 * @param len the number of elements to read
91 * @param f the lambda taking two arguments (T x, IdxT idx)
92 */
93 template <typename T, typename idxT, typename Func>
94 __device__ void vectorized_process(size_t thread_rank, size_t num_threads,
95 const T* in, idxT len, Func f) {
96 // Use dynamic WARP_SIZE from cuda_compat.h to support both
97 // Wave64 (MI300X/gfx942) and Wave32 (Strix Halo/gfx1151) architectures
98 constexpr int kWarpSize = WARP_SIZE;
99 using WideT = float4;
100 if constexpr (sizeof(T) >= sizeof(WideT)) {
101 for (idxT i = thread_rank; i < len; i += num_threads) {
102 f(in[i], i);
103 }
104 } else {
105 static_assert(sizeof(WideT) % sizeof(T) == 0);
106 constexpr int items_per_scalar = sizeof(WideT) / sizeof(T);
107 // TODO: it's UB
108 union {
109 WideT scalar;
110 T array[items_per_scalar];
111 } wide;
112
113 int skip_cnt =
114 (reinterpret_cast<size_t>(in) % sizeof(WideT))
115 ? ((sizeof(WideT) - reinterpret_cast<size_t>(in) % sizeof(WideT)) /
116 sizeof(T))
117 : 0;
118 if (skip_cnt > len) {
119 skip_cnt = len;
120 }
121 const WideT* in_cast = reinterpret_cast<decltype(in_cast)>(in + skip_cnt);
122 const idxT len_cast = (len - skip_cnt) / items_per_scalar;
123
124 for (idxT i = thread_rank; i < len_cast; i += num_threads) {
125 wide.scalar = in_cast[i];
126 const idxT real_i = skip_cnt + i * items_per_scalar;
127 #pragma unroll
128 for (int j = 0; j < items_per_scalar; ++j) {
129 f(wide.array[j], real_i + j);
130 }
131 }
132
133 static_assert(kWarpSize >= items_per_scalar);
134 // and because items_per_scalar > skip_cnt, kWarpSize > skip_cnt
135 // no need to use loop
136 if (thread_rank < skip_cnt) {
137 f(in[thread_rank], thread_rank);
138 }
139 // because len_cast = (len - skip_cnt) / items_per_scalar,
140 // len_cast * items_per_scalar + items_per_scalar > len - skip_cnt;
141 // and so
142 // len - (skip_cnt + len_cast * items_per_scalar) < items_per_scalar <=
143 // kWarpSize no need to use loop
144 const idxT remain_i = skip_cnt + len_cast * items_per_scalar + thread_rank;
145 if (remain_i < len) {
146 f(in[remain_i], remain_i);
147 }
148 }
149 }
150
151 template <int step, int kNumThreadsPerBlock, int kNumBins, int kNumFinalItems,
152 bool multipleBlocksPerRow, bool mergeBlocks,
153 bool useTopK512Optimization, typename SmemFinalType,
154 typename SmemOutputType>
155 __device__ bool processHistogramStep(
156 const int* indices, const float* logits, int rowEnd, uint32_t& logitPattern,
157 int& thresholdBinIdx, SmemOutputType& smemOutput, int* smemThresholdBinIdx,
158 int* smemFinalDstIdx, int* smemFinalBinSize, int* smemFoundTopKValues,
159 SmemFinalType& smemFinal, int stride1, int rowStart, int topK) {
160 // Clear the histogram.
161 #pragma unroll
162 for (int idx = threadIdx.x; idx < kNumBins; idx += kNumThreadsPerBlock) {
163 smemFinal.histo.data[idx] = 0;
164 }
165
166 // Make sure the histogram is ready.
167 __syncthreads();
168
169 // Update pattern
170 constexpr auto patternShift = step < 2 ? 0 : step == 2 ? 21 : 10;
171 if constexpr (step == 2) {
172 logitPattern = static_cast<uint32_t>(thresholdBinIdx & 0x7ff)
173 << patternShift;
174 } else if constexpr (step == 3) {
175 logitPattern |= static_cast<uint32_t>(thresholdBinIdx & 0x7ff)
176 << patternShift;
177 }
178
179 int negativeInfinityCount = 0;
180 auto distributeToBins = [&](float logit, int /* idx */ = 0) {
181 if (isPartialMatch<patternShift>(logit, logitPattern)) {
182 uint32_t binIdx = extractBinIdx<step>(logit);
183 if (useTopK512Optimization && __float_as_uint(logit) == 0xff800000u) {
184 ++negativeInfinityCount;
185 } else {
186 atomicAdd(&smemFinal.histo.data[binIdx], 1);
187 }
188 }
189 };
190
191 // Distribute the elements to the histogram bins.
192 if (stride1 == 1) {
193 vectorized_process(threadIdx.x, kNumThreadsPerBlock, logits + rowStart,
194 rowEnd - rowStart, distributeToBins);
195 } else {
196 for (int idx = rowStart + threadIdx.x; idx < rowEnd;
197 idx += kNumThreadsPerBlock) {
198 float logit = logits[idx * stride1];
199 distributeToBins(logit, idx);
200 }
201 }
202 if constexpr (useTopK512Optimization) {
203 if (negativeInfinityCount > 0) {
204 const auto bin = extractBinIdx<step>(-INFINITY);
205 atomicAdd(&smemFinal.histo.data[bin], negativeInfinityCount);
206 }
207 }
208 // Make sure the histogram is ready.
209 __syncthreads();
210
211 // Reads the value of the starting position in the smemOutput array
212 int lastValue = smemFoundTopKValues[0];
213
214 for (int round = 0; round < kNumBins / kNumThreadsPerBlock; round++) {
215 // Read the values from SMEM.
216 int idx = threadIdx.x + kNumThreadsPerBlock * round;
217 int binCount{0};
218 binCount = smemFinal.histo.data[idx];
219
220 // Make sure each thread has read its value.
221 __syncthreads();
222
223 // Compute the prefix sum.
224 int prefixSum{0}, totalSum{0};
225 using Scan = cub::BlockScan<int, kNumThreadsPerBlock>;
226 Scan(smemFinal.histo.scan).ExclusiveSum(binCount, prefixSum, totalSum);
227
228 // Update the histogram with the prefix sums.
229 prefixSum += lastValue;
230 totalSum += lastValue;
231 smemFinal.histo.data[idx] = prefixSum;
232
233 // Make sure the data is in shared memory.
234 __syncthreads();
235
236 // Find the last valid bin.
237 bool foundThreshold = false;
238 if (prefixSum < topK) {
239 int nextPrefixSum = threadIdx.x == kNumThreadsPerBlock - 1
240 ? totalSum
241 : smemFinal.histo.data[idx + 1];
242
243 if (nextPrefixSum >= topK) {
244 smemThresholdBinIdx[0] = idx;
245 smemFinalBinSize[0] = nextPrefixSum - prefixSum;
246 foundThreshold = true;
247 }
248 }
249
250 // Early exit: if any thread found the threshold, we can skip remaining
251 // rounds
252 if (__syncthreads_or(foundThreshold)) {
253 break;
254 }
255
256 lastValue = totalSum;
257 }
258
259 // Make sure the data is in shared memory.
260 __syncthreads();
261
262 // The threshold bin.
263 thresholdBinIdx = smemThresholdBinIdx[0];
264 // Resolve a -inf cutoff with exact tie emission, keeping sort padding out.
265 const bool finalBinFits =
266 smemFinalBinSize[0] <= kNumFinalItems &&
267 !(useTopK512Optimization && step < 3 &&
268 isPartialMatch<patternShift>(-INFINITY, logitPattern) &&
269 thresholdBinIdx == extractBinIdx<step>(-INFINITY));
270
271 auto processBins = [&](float logit, int idx) {
272 if (isPartialMatch<patternShift>(logit, logitPattern)) {
273 uint32_t binIdx = extractBinIdx<step>(logit);
274 // Only write elements with binIdx < thresholdBinIdx when:
275 // 1. This is step 0 and the threshold bin is small enough (no step 1)
276 // 2. This is step >= 1 (where pattern matching filters correctly)
277 // This prevents duplicates when step 0 and step 1 both run.
278 bool shouldWriteDirectly = (step == 0 && finalBinFits) || (step >= 1);
279 if (binIdx < thresholdBinIdx && shouldWriteDirectly) {
280 // The element is part of the top-k selection
281 int dstIdx = atomicAdd(&smemFoundTopKValues[0], 1);
282
283 if constexpr (mergeBlocks) {
284 smemOutput[dstIdx] = indices[idx];
285 } else if constexpr (multipleBlocksPerRow) {
286 smemOutput[dstIdx] = idx + rowStart;
287 reinterpret_cast<float*>(smemOutput + topK)[dstIdx] = logit;
288 } else {
289 smemOutput[dstIdx] = idx;
290 }
291 }
292 if constexpr (step < 3) {
293 // Only fill the final items for sorting if the threshold bin fits
294 if (binIdx == thresholdBinIdx && finalBinFits) {
295 int dstIdx = atomicAdd(&smemFinalDstIdx[0], 1);
296 smemFinal.items.logits[dstIdx] = logit;
297 if constexpr (mergeBlocks) {
298 smemFinal.items.indices[dstIdx] = indices[idx];
299 } else if constexpr (multipleBlocksPerRow) {
300 smemFinal.items.indices[dstIdx] = idx + rowStart;
301 } else {
302 smemFinal.items.indices[dstIdx] = idx;
303 }
304 }
305 } else {
306 if (binIdx == thresholdBinIdx) {
307 // The elements in the threshold bin share the same 32 bits at step 3
308 int dstIdx = atomicAdd(&smemFinal.histo.data[binIdx], 1);
309 if (dstIdx < topK) {
310 if constexpr (mergeBlocks) {
311 smemOutput[dstIdx] = indices[idx];
312 } else if constexpr (multipleBlocksPerRow) {
313 smemOutput[dstIdx] = idx + rowStart;
314 reinterpret_cast<float*>(smemOutput + topK)[dstIdx] = logit;
315 } else {
316 smemOutput[dstIdx] = idx;
317 }
318 }
319 }
320 }
321 }
322 };
323
324 if (stride1 == 1) {
325 vectorized_process(threadIdx.x, kNumThreadsPerBlock, logits + rowStart,
326 rowEnd - rowStart, processBins);
327 } else {
328 for (int idx = rowStart + threadIdx.x; idx < rowEnd;
329 idx += kNumThreadsPerBlock) {
330 float logit = logits[idx * stride1];
331 processBins(logit, idx);
332 }
333 }
334
335 // Make sure the elements are in shared memory.
336 __syncthreads();
337
338 // Check if we should continue to next step
339 return !finalBinFits;
340 }
341
342 // Follows half - 11 - 11 - 10 bit iterations
343 // Keep the adaptive kernel's device instantiation separate from legacy calls.
344 template <int kNumThreadsPerBlock, int kNumBins, bool useRadixSort,
345 bool multipleBlocksPerRow = false, bool mergeBlocks = false,
346 bool deviceLengthAware = false, bool useTopK512Optimization = false>
347 static __device__ void topKPerRowJob(const int* indices, const float* logits,
348 int rowStart, int rowEnd, int* outIndices,
349 float* outLogits, int stride1, int topK) {
350 static_assert(!deviceLengthAware || multipleBlocksPerRow != mergeBlocks);
351 static_assert(!useTopK512Optimization || kNumThreadsPerBlock == 1024);
352 // The number of slots for the final pass.
353 static constexpr int kNumFinalItems = useTopK512Optimization ? 1024 : 2048;
354 // The number of elements per thread for the final sort.
355 static constexpr int kNumFinalItemsPerThread =
356 kNumFinalItems / kNumThreadsPerBlock;
357 // The class to sort the elements during the final pass.
358 using FinalSort = cub::BlockRadixSort<float, kNumThreadsPerBlock,
359 kNumFinalItemsPerThread, int>;
360 using FinalSortTempStorage =
361 std::conditional_t<useRadixSort, typename FinalSort::TempStorage, int>;
362 // The class to compute the inclusive prefix-sum over the histogram.
363 using Scan = cub::BlockScan<int, kNumThreadsPerBlock>;
364
365 // The structure to store the final items (for the final pass).
366 struct FinalItems {
367 // Shared memory to store the indices for the final pass.
368 int indices[kNumFinalItems];
369 // Shared memory to store the logits for the final pass.
370 float logits[kNumFinalItems];
371 };
372
373 struct Histogram {
374 typename Scan::TempStorage scan;
375 int data[kNumBins];
376 };
377
378 // Shared memory to compute the block sort.
379 __shared__ union {
380 FinalItems items;
381 FinalSortTempStorage finalSort;
382 Histogram histo;
383 } smemFinal;
384
385 // Shared memory to store the selected indices.
386 // If we are processing using multiple blocks, we need to store the logits and
387 // indices.
388 extern __shared__ int32_t smemOutput[];
389
390 // Shared memory to store the threshold bin.
391 __shared__ int smemThresholdBinIdx[1];
392 // Shared memory counter to register the candidates for the final phase.
393 __shared__ int smemFinalDstIdx[1];
394 // Shared memory to determine if the threshold bin fits in the final items.
395 __shared__ int smemFinalBinSize[1];
396 // Shared memory to keep track of the top-k values found so far by the
397 // previous iterations
398 __shared__ int smemFoundTopKValues[1];
399
400 // The length of the row.
401 int rowLen = rowEnd - rowStart;
402
403 // Shortcut if the length of the row is smaller than Top-K. Indices are not
404 // sorted by their corresponding logit.
405 if (rowLen <= topK) {
406 for (int rowIt = threadIdx.x; rowIt < rowLen;
407 rowIt += kNumThreadsPerBlock) {
408 if constexpr (multipleBlocksPerRow) {
409 outIndices[rowIt] = rowIt + rowStart;
410 outLogits[rowIt] = logits[rowIt + rowStart];
411 } else {
412 outIndices[rowIt] = rowIt;
413 }
414 }
415 for (int rowIt = rowLen + threadIdx.x; rowIt < topK;
416 rowIt += kNumThreadsPerBlock) {
417 outIndices[rowIt] = -1;
418 if constexpr (multipleBlocksPerRow) {
419 outLogits[rowIt] = useTopK512Optimization ? -INFINITY : -FLT_MAX;
420 }
421 }
422
423 return;
424 }
425 // Initialize values
426 if (threadIdx.x == 0) {
427 smemFinalDstIdx[0] = 0;
428 smemFoundTopKValues[0] = 0;
429 }
430 __syncthreads();
431 int thresholdBinIdx = -1;
432 uint32_t logitPattern = 0;
433
434 // Step 0: Process first 11 bits of half representation
435 bool continueToNextStep =
436 processHistogramStep<0, kNumThreadsPerBlock, kNumBins, kNumFinalItems,
437 multipleBlocksPerRow, mergeBlocks,
438 useTopK512Optimization>(
439 indices, logits, rowEnd, logitPattern, thresholdBinIdx, smemOutput,
440 smemThresholdBinIdx, smemFinalDstIdx, smemFinalBinSize,
441 smemFoundTopKValues, smemFinal, stride1, rowStart, topK);
442
443 if (continueToNextStep) {
444 // Step 1: Process next 11 bits
445 continueToNextStep =
446 processHistogramStep<1, kNumThreadsPerBlock, kNumBins, kNumFinalItems,
447 multipleBlocksPerRow, mergeBlocks,
448 useTopK512Optimization>(
449 indices, logits, rowEnd, logitPattern, thresholdBinIdx, smemOutput,
450 smemThresholdBinIdx, smemFinalDstIdx, smemFinalBinSize,
451 smemFoundTopKValues, smemFinal, stride1, rowStart, topK);
452 }
453
454 if (continueToNextStep) {
455 // Step 2: Process next 11 bits
456 continueToNextStep =
457 processHistogramStep<2, kNumThreadsPerBlock, kNumBins, kNumFinalItems,
458 multipleBlocksPerRow, mergeBlocks,
459 useTopK512Optimization>(
460 indices, logits, rowEnd, logitPattern, thresholdBinIdx, smemOutput,
461 smemThresholdBinIdx, smemFinalDstIdx, smemFinalBinSize,
462 smemFoundTopKValues, smemFinal, stride1, rowStart, topK);
463 }
464
465 if (continueToNextStep) {
466 // Step 3: Process last 10 bits
467 processHistogramStep<3, kNumThreadsPerBlock, kNumBins, kNumFinalItems,
468 multipleBlocksPerRow, mergeBlocks,
469 useTopK512Optimization>(
470 indices, logits, rowEnd, logitPattern, thresholdBinIdx, smemOutput,
471 smemThresholdBinIdx, smemFinalDstIdx, smemFinalBinSize,
472 smemFoundTopKValues, smemFinal, stride1, rowStart, topK);
473 }
474
475 if (!continueToNextStep) {
476 // The histogram did not proceed to the final 10 bits, therefore we need to
477 // sort the final items The logits of the elements to be sorted in the final
478 // pass.
479 auto insertionSort = [&]() {
480 // Sorting with insertion sort
481 auto baseIdx = smemFoundTopKValues[0];
482 for (int i = threadIdx.x; i < smemFinalDstIdx[0];
483 i += kNumThreadsPerBlock) {
484 int outIndex = 0;
485 auto logit = smemFinal.items.logits[i];
486 for (int j = 0; j < smemFinalDstIdx[0]; j++) {
487 auto otherLogit = smemFinal.items.logits[j];
488 if (logit < otherLogit || (logit == otherLogit && i < j)) {
489 outIndex++;
490 }
491 }
492 // Store if outIndex is in bounds
493 if (outIndex + baseIdx < topK) {
494 smemOutput[outIndex + baseIdx] = smemFinal.items.indices[i];
495 if constexpr (multipleBlocksPerRow) {
496 reinterpret_cast<float*>(smemOutput + topK)[outIndex + baseIdx] =
497 smemFinal.items.logits[i];
498 }
499 }
500 }
501 };
502 if constexpr (useRadixSort) {
503 if (useTopK512Optimization && smemFinalDstIdx[0] <= 128) {
504 insertionSort();
505 } else {
506 // Sorting with radix sort
507 float finalLogits[kNumFinalItemsPerThread];
508 // The indices of the elements to be sorted in the final pass.
509 int finalIndices[kNumFinalItemsPerThread];
510
511 #pragma unroll
512 for (int ii = 0; ii < kNumFinalItemsPerThread; ++ii) {
513 finalLogits[ii] = useTopK512Optimization ? -INFINITY : -FLT_MAX;
514 if constexpr (useTopK512Optimization) finalIndices[ii] = -1;
515 }
516
517 // Read the elements from SMEM.
518 #pragma unroll
519 for (int ii = 0; ii < kNumFinalItemsPerThread; ++ii) {
520 int srcIdx = ii * kNumThreadsPerBlock + threadIdx.x;
521 if (srcIdx < smemFinalDstIdx[0]) {
522 finalLogits[ii] = smemFinal.items.logits[srcIdx];
523 finalIndices[ii] = smemFinal.items.indices[srcIdx];
524 }
525 }
526 // Make sure the shared memory has been read.
527 __syncthreads();
528
529 // Sort the elements.
530 FinalSort(smemFinal.finalSort)
531 .SortDescendingBlockedToStriped(finalLogits, finalIndices);
532
533 // Copy the data back to the shared memory storage.
534 int baseIdx = smemFoundTopKValues[0];
535
536 #pragma unroll
537 for (int ii = 0; ii < kNumFinalItemsPerThread; ++ii) {
538 int srcIdx = ii * kNumThreadsPerBlock + threadIdx.x;
539 int dstIdx = baseIdx + srcIdx;
540
541 if (dstIdx < topK) {
542 smemOutput[dstIdx] = finalIndices[ii];
543 if constexpr (multipleBlocksPerRow) {
544 reinterpret_cast<float*>(smemOutput + topK)[dstIdx] =
545 finalLogits[ii];
546 }
547 }
548 }
549 }
550 } else {
551 insertionSort();
552 }
553 __syncthreads();
554 }
555
556 // Store to global memory.
557 for (int i = threadIdx.x; i < topK; i += kNumThreadsPerBlock) {
558 if constexpr (multipleBlocksPerRow) {
559 outIndices[i] = smemOutput[i];
560 outLogits[i] = reinterpret_cast<float*>(smemOutput + topK)[i];
561 } else {
562 if (stride1 == 1) {
563 // stride1 == 1 will use vectorized_process, which indexes already skip
564 // the rowStart.
565 outIndices[i] = smemOutput[i];
566 } else {
567 outIndices[i] = smemOutput[i] - rowStart;
568 }
569 }
570 }
571 }
572
573 template <int kNumThreadsPerBlock, bool useRadixSort>
574 static __global__ __launch_bounds__(kNumThreadsPerBlock) void topKPerRowPrefill(
575 const float* logits, const int* rowStarts, const int* rowEnds,
576 int* outIndices, int stride0, int stride1, const int topK,
577 const int offsetIndex) {
578 // The number of bins in the histogram.
579 static constexpr int kNumBins = 2048;
580
581 // The row computed by this block.
582 int rowIdx = blockIdx.x + offsetIndex;
583
584 // The range of logits within the row.
585 int rowStart = rowStarts[rowIdx];
586 int rowEnd = rowEnds[rowIdx];
587
588 // Local pointers to this block
589 outIndices += static_cast<int64_t>(rowIdx) * topK;
590 logits += static_cast<int64_t>(rowIdx) * stride0;
591
592 topKPerRowJob<kNumThreadsPerBlock, kNumBins, useRadixSort>(
593 nullptr, logits, rowStart, rowEnd, outIndices, nullptr, stride1, topK);
594 }
595
596 template <int kNumThreadsPerBlock, bool useRadixSort,
597 bool multipleBlocksPerRow = false, bool mergeBlocks = false>
598 static __global__ __launch_bounds__(kNumThreadsPerBlock) void topKPerRowDecode(
599 const float* logits, const int* seqLens, int* outIndices, int stride0,
600 int stride1, const int topK, int next_n, int seqLensIs2D = 0,
601 float* outLogits = nullptr, const int numBlocksToMerge = 0,
602 const int* indices = nullptr) {
603 // The number of bins in the histogram.
604 static constexpr int kNumBins = 2048;
605
606 // The row computed by this block.
607 int rowIdx = blockIdx.x;
608
609 // The range of logits within the row.
610 int rowStart = 0;
611 int batch_idx = rowIdx / next_n;
612 int next_n_idx = rowIdx % next_n;
613 // seqLensIs2D=0: 1D seqLens — all rows in a batch share the same seq_len;
614 // kernel computes per-row effective length via offset.
615 // seqLensIs2D=1: 2D seqLens — each logit row has its own pre-computed
616 // effective length (flat index rowIdx = b*next_n + j maps
617 // directly to seqLens[b, j] in C-contiguous layout).
618 int seq_len = seqLensIs2D ? seqLens[rowIdx] : seqLens[batch_idx];
619 int rowEnd =
620 seqLensIs2D ? max(0, seq_len) : max(0, seq_len - next_n + next_n_idx + 1);
621
622 // Local pointers to this block
623 if constexpr (!multipleBlocksPerRow && !mergeBlocks) {
624 outIndices += static_cast<int64_t>(rowIdx) * topK;
625 } else if constexpr (multipleBlocksPerRow) {
626 const auto blockSize = rowEnd / gridDim.y; // 16384 / 2 = 8192
627 rowStart = blockSize * blockIdx.y; // 8192 * 1 = 8192
628 rowEnd = gridDim.y == blockIdx.y + 1 ? rowEnd : rowStart + blockSize;
629 outIndices +=
630 static_cast<int64_t>(rowIdx) * gridDim.y * topK + blockIdx.y * topK;
631 outLogits +=
632 static_cast<int64_t>(rowIdx) * gridDim.y * topK + blockIdx.y * topK;
633 } else if constexpr (mergeBlocks) {
634 rowEnd = numBlocksToMerge * topK;
635 indices += static_cast<int64_t>(rowIdx) * numBlocksToMerge * topK;
636 outIndices += static_cast<int64_t>(rowIdx) * topK;
637 }
638 logits += static_cast<int64_t>(rowIdx) * stride0;
639
640 topKPerRowJob<kNumThreadsPerBlock, kNumBins, useRadixSort,
641 multipleBlocksPerRow, mergeBlocks>(
642 indices, logits, rowStart, rowEnd, outIndices, outLogits, stride1, topK);
643 }
644
645 // Measured native-kernel policy for gfx950, FP32, and k=1024. This
646 // active-column split crossover is independent of the Python AITER/native
647 // backend crossover; the two policies can be retuned separately. At exactly
648 // 64K, an eager call may select AITER while a captured native replay uses two
649 // splits.
650 constexpr int kGfx950C4AShortRowNumBlocks = 3;
651 constexpr int kGfx950C4ALongRowNumBlocks = 2;
652 constexpr int kGfx950C4ATwoSplitMinRowLength = 64 * 1024;
653
654 template <int kNumThreadsPerBlock, bool mergeBlocks = false>
655 static __global__
656 __launch_bounds__(kNumThreadsPerBlock) void topKPerRowDecodeDeviceLengthAware(
657 const float* logits, const int* seqLens, int* outIndices, int stride0,
658 int stride1, const int topK, int next_n, int seqLensIs2D = 0,
659 float* outLogits = nullptr, const int* indices = nullptr) {
660 static constexpr int kNumBins = 2048;
661 int rowIdx = blockIdx.x;
662 int batch_idx = rowIdx / next_n;
663 int next_n_idx = rowIdx % next_n;
664 int seq_len = seqLensIs2D ? seqLens[rowIdx] : seqLens[batch_idx];
665 int rowEnd =
666 seqLensIs2D ? max(0, seq_len) : max(0, seq_len - next_n + next_n_idx + 1);
667 const int activeBlocks = rowEnd >= kGfx950C4ATwoSplitMinRowLength
668 ? kGfx950C4ALongRowNumBlocks
669 : kGfx950C4AShortRowNumBlocks;
670 int rowStart = 0;
671
672 if constexpr (!mergeBlocks) {
673 if (blockIdx.y >= activeBlocks) return;
674 const auto blockSize = rowEnd / activeBlocks;
675 rowStart = blockSize * blockIdx.y;
676 rowEnd = activeBlocks == blockIdx.y + 1 ? rowEnd : rowStart + blockSize;
677 outIndices +=
678 static_cast<int64_t>(rowIdx) * kGfx950C4AShortRowNumBlocks * topK +
679 blockIdx.y * topK;
680 outLogits +=
681 static_cast<int64_t>(rowIdx) * kGfx950C4AShortRowNumBlocks * topK +
682 blockIdx.y * topK;
683 } else {
684 rowEnd = activeBlocks * topK;
685 indices +=
686 static_cast<int64_t>(rowIdx) * kGfx950C4AShortRowNumBlocks * topK;
687 outIndices += static_cast<int64_t>(rowIdx) * topK;
688 }
689 logits += static_cast<int64_t>(rowIdx) * stride0;
690
691 topKPerRowJob<kNumThreadsPerBlock, kNumBins, true, !mergeBlocks, mergeBlocks,
692 true>(indices, logits, rowStart, rowEnd, outIndices, outLogits,
693 stride1, topK);
694 }
695
696 #ifdef USE_ROCM
697 // The grid and workspace stay fixed while graph replays change device lengths.
698 __device__ int gfx950TopK512ActiveBlocks(int rowLength, int numRows,
699 int maxBlocks) {
700 int activeBlocks = 1;
701 if (numRows <= 32 && rowLength > 16 * 1024) {
702 activeBlocks = rowLength <= 128 * 1024 ? 8
703 : rowLength <= 256 * 1024 ? 10
704 : 16;
705 } else if (numRows <= 128 && rowLength > 64 * 1024) {
706 activeBlocks = rowLength <= 512 * 1024 ? 4 : 10;
707 } else if (numRows > 128 && numRows <= 256 && rowLength > 256 * 1024) {
708 activeBlocks = numRows <= 160 ? 3 : 2;
709 }
710 // Every block in a merged row must contain at least k real indices.
711 return min(min(activeBlocks, maxBlocks), max(1, rowLength / 512));
712 }
713
714 template <int kNumThreadsPerBlock, bool mergeBlocks = false>
715 static __global__
716 __launch_bounds__(kNumThreadsPerBlock) void topKPerRowDecode512DeviceLengthAware(
717 const float* logits, const int* seqLens, int* outIndices,
718 int* outIndicesAux, float* outLogitsAux, int stride0, int next_n,
719 int seqLensIs2D, int maxBlocks) {
720 #if defined(__gfx950__)
721 constexpr int topK = 512;
722 constexpr int kNumBins = 2048;
723 const int rowIdx = blockIdx.x;
724 const int rowEnd =
725 seqLensIs2D
726 ? max(0, seqLens[rowIdx])
727 : max(0, seqLens[rowIdx / next_n] - next_n + rowIdx % next_n + 1);
728 const int activeBlocks =
729 gfx950TopK512ActiveBlocks(rowEnd, gridDim.x, maxBlocks);
730 outIndices += static_cast<int64_t>(rowIdx) * topK;
731 if constexpr (mergeBlocks) {
732 if (activeBlocks == 1) return;
733 const int64_t offset = static_cast<int64_t>(rowIdx) * maxBlocks * topK;
734 topKPerRowJob<kNumThreadsPerBlock, kNumBins, true, false, true, false,
735 true>(outIndicesAux + offset, outLogitsAux + offset, 0,
736 activeBlocks * topK, outIndices, nullptr, 1, topK);
737 } else {
738 if (blockIdx.y >= activeBlocks) return;
739 logits += static_cast<int64_t>(rowIdx) * stride0;
740 if (activeBlocks == 1) {
741 topKPerRowJob<kNumThreadsPerBlock, kNumBins, true, false, false, false,
742 true>(nullptr, logits, 0, rowEnd, outIndices, nullptr, 1,
743 topK);
744 return;
745 }
746 const int blockSize = rowEnd / activeBlocks;
747 const int rowStart = blockSize * blockIdx.y;
748 const int blockRowEnd =
749 blockIdx.y + 1 == activeBlocks ? rowEnd : rowStart + blockSize;
750 const int64_t offset =
751 (static_cast<int64_t>(rowIdx) * maxBlocks + blockIdx.y) * topK;
752 topKPerRowJob<kNumThreadsPerBlock, kNumBins, true, true, false, false,
753 true>(nullptr, logits, rowStart, blockRowEnd,
754 outIndicesAux + offset, outLogitsAux + offset, 1, topK);
755 }
756 #endif
757 }
758 #endif
759
760 } // namespace vllm
761
762 void apply_repetition_penalties_(
763 torch::stable::Tensor& logits, // [num_seqs, vocab_size], in-place
764 const torch::stable::Tensor& prompt_mask, // [num_seqs, vocab_size]
765 const torch::stable::Tensor& output_mask, // [num_seqs, vocab_size]
766 const torch::stable::Tensor& repetition_penalties) { // [num_seqs]
767 STD_TORCH_CHECK(logits.is_contiguous());
768 STD_TORCH_CHECK(prompt_mask.is_contiguous());
769 STD_TORCH_CHECK(output_mask.is_contiguous());
770 STD_TORCH_CHECK(repetition_penalties.is_contiguous());
771
772 int vocab_size = logits.size(-1);
773 int num_seqs = logits.size(0);
774
775 if (num_seqs == 0) return;
776
777 // Get number of SMs on the current device
778 int sms = 0;
779 cudaDeviceGetAttribute(&sms, cudaDevAttrMultiProcessorCount,
780 logits.get_device_index());
781
782 // Compute tile_num and tile_size
783 int tile_num =
784 std::min(vocab_size, std::max(1, (sms + num_seqs - 1) / num_seqs));
785 int tile_size = (vocab_size + tile_num - 1) / tile_num;
786
787 // Each block handles one sequence and a tile of vocab
788 dim3 grid(num_seqs, tile_num);
789 dim3 block(std::min(tile_size, 1024));
790 const torch::stable::accelerator::DeviceGuard device_guard(
791 logits.get_device_index());
792 const cudaStream_t stream = get_current_cuda_stream();
793 VLLM_STABLE_DISPATCH_FLOATING_TYPES(
794 logits.scalar_type(), "apply_repetition_penalties_kernel", [&] {
795 vllm::apply_repetition_penalties_kernel<scalar_t>
796 <<<grid, block, 0, stream>>>(
797 logits.mutable_data_ptr<scalar_t>(),
798 prompt_mask.const_data_ptr<bool>(),
799 output_mask.const_data_ptr<bool>(),
800 repetition_penalties.const_data_ptr<scalar_t>(), num_seqs,
801 vocab_size, tile_size);
802 });
803 }
804
805 void top_k_per_row_decode(const torch::stable::Tensor& logits, int64_t next_n,
806 const torch::stable::Tensor& seqLens,
807 torch::stable::Tensor& indices, int64_t numRows,
808 int64_t stride0, int64_t stride1, int64_t topK) {
809 constexpr int kSortingAlgorithmThreshold = 12288;
810 constexpr int kSplitWorkThreshold = 200 * 1000;
811 constexpr int kNumThreadsPerBlock = 512;
812 const torch::stable::accelerator::DeviceGuard device_guard(
813 logits.get_device_index());
814 const cudaStream_t stream = get_current_cuda_stream();
815 const auto numColumns = logits.size(1);
816
817 // True if seqLens is 2D (B, next_n): each logit row has its own pre-computed
818 // effective seq_len. False if seqLens is 1D (B,): all rows in a batch share
819 // the same seq_len and the kernel computes the per-row offset itself.
820 int seqLensIs2D = seqLens.dim() == 2 ? 1 : 0;
821
822 #ifdef USE_ROCM
823 const bool useGfx950DeviceLengthAwareTopK512 =
824 topK == 512 && stride1 == 1 && numRows > 0 && numRows <= 384 &&
825 numColumns <= 1024 * 1024 &&
826 std::strncmp(get_device_prop()->gcnArchName, "gfx950", 6) == 0;
827 if (useGfx950DeviceLengthAwareTopK512) {
828 int multipleBlocksPerRowConfig = 1;
829 if (numRows <= 32 && numColumns > 16 * 1024) {
830 multipleBlocksPerRowConfig = 16;
831 } else if (numRows <= 128 && numColumns > 64 * 1024) {
832 multipleBlocksPerRowConfig = 10;
833 } else if (numRows <= 160 && numColumns > 256 * 1024) {
834 multipleBlocksPerRowConfig = 3;
835 } else if (numRows <= 256 && numColumns > 256 * 1024) {
836 multipleBlocksPerRowConfig = 2;
837 }
838
839 constexpr int kNumThreadsPerSplitBlock = 1024;
840 if (multipleBlocksPerRowConfig == 1) {
841 vllm::topKPerRowDecode512DeviceLengthAware<kNumThreadsPerSplitBlock>
842 <<<numRows, kNumThreadsPerSplitBlock, 2 * topK * sizeof(int32_t),
843 stream>>>(logits.const_data_ptr<float>(),
844 seqLens.const_data_ptr<int>(),
845 indices.mutable_data_ptr<int>(), nullptr, nullptr,
846 static_cast<int>(stride0), static_cast<int>(next_n),
847 seqLensIs2D, multipleBlocksPerRowConfig);
848 return;
849 }
850 const auto outIndicesAux = torch::stable::empty(
851 {numRows, multipleBlocksPerRowConfig, topK},
852 torch::headeronly::ScalarType::Int, std::nullopt, logits.device());
853 const auto outLogitsAux = torch::stable::empty(
854 {numRows, multipleBlocksPerRowConfig, topK},
855 torch::headeronly::ScalarType::Float, std::nullopt, logits.device());
856 vllm::topKPerRowDecode512DeviceLengthAware<kNumThreadsPerSplitBlock>
857 <<<dim3(numRows, multipleBlocksPerRowConfig), kNumThreadsPerSplitBlock,
858 2 * topK * sizeof(int32_t), stream>>>(
859 logits.const_data_ptr<float>(), seqLens.const_data_ptr<int>(),
860 indices.mutable_data_ptr<int>(),
861 outIndicesAux.mutable_data_ptr<int>(),
862 outLogitsAux.mutable_data_ptr<float>(), static_cast<int>(stride0),
863 static_cast<int>(next_n), seqLensIs2D, multipleBlocksPerRowConfig);
864 constexpr int kNumThreadsPerBlockMerge = 1024;
865 vllm::topKPerRowDecode512DeviceLengthAware<kNumThreadsPerBlockMerge, true>
866 <<<numRows, kNumThreadsPerBlockMerge, topK * sizeof(int32_t), stream>>>(
867 nullptr, seqLens.const_data_ptr<int>(),
868 indices.mutable_data_ptr<int>(),
869 outIndicesAux.mutable_data_ptr<int>(),
870 outLogitsAux.mutable_data_ptr<float>(), 0, static_cast<int>(next_n),
871 seqLensIs2D, multipleBlocksPerRowConfig);
872 return;
873 }
874 #endif
875
876 if (numColumns < kSortingAlgorithmThreshold) {
877 // Use insertion sort
878 vllm::topKPerRowDecode<kNumThreadsPerBlock, false>
879 <<<numRows, kNumThreadsPerBlock, topK * sizeof(int32_t), stream>>>(
880 logits.const_data_ptr<float>(), seqLens.const_data_ptr<int>(),
881 indices.mutable_data_ptr<int>(), static_cast<int>(stride0),
882 static_cast<int>(stride1), static_cast<int>(topK),
883 static_cast<int>(next_n), seqLensIs2D);
884 } else if (numColumns < kSplitWorkThreshold) {
885 // From this threshold, use radix sort instead
886 vllm::topKPerRowDecode<kNumThreadsPerBlock, true>
887 <<<numRows, kNumThreadsPerBlock, topK * sizeof(int32_t), stream>>>(
888 logits.const_data_ptr<float>(), seqLens.const_data_ptr<int>(),
889 indices.mutable_data_ptr<int>(), static_cast<int>(stride0),
890 static_cast<int>(stride1), static_cast<int>(topK),
891 static_cast<int>(next_n), seqLensIs2D);
892 } else {
893 // Long sequences are run in two steps
894 #ifdef USE_ROCM
895 const bool useGfx950DeviceLengthAwareTopK1024 =
896 topK == 1024 && numRows > 64 && numRows <= 256 &&
897 std::strncmp(get_device_prop()->gcnArchName, "gfx950", 6) == 0;
898 if (useGfx950DeviceLengthAwareTopK1024) {
899 // Keep three blocks for short FULL-graph replays, but let each row use
900 // two blocks once its live device length reaches the long-context
901 // crossover. The grid and workspace shapes remain capture-stable.
902 constexpr int multipleBlocksPerRowConfig =
903 vllm::kGfx950C4AShortRowNumBlocks;
904 const auto outIndicesAux = torch::stable::empty(
905 {numRows, multipleBlocksPerRowConfig, topK},
906 torch::headeronly::ScalarType::Int, std::nullopt, logits.device());
907 const auto outLogitsAux = torch::stable::empty(
908 {numRows, multipleBlocksPerRowConfig, topK},
909 torch::headeronly::ScalarType::Float, std::nullopt, logits.device());
910
911 constexpr int kNumThreadsPerSplitBlock = 1024;
912 vllm::topKPerRowDecodeDeviceLengthAware<kNumThreadsPerSplitBlock>
913 <<<dim3(numRows, multipleBlocksPerRowConfig),
914 kNumThreadsPerSplitBlock, 2 * topK * sizeof(int32_t), stream>>>(
915 logits.const_data_ptr<float>(), seqLens.const_data_ptr<int>(),
916 outIndicesAux.mutable_data_ptr<int>(), static_cast<int>(stride0),
917 static_cast<int>(stride1), static_cast<int>(topK),
918 static_cast<int>(next_n), seqLensIs2D,
919 outLogitsAux.mutable_data_ptr<float>());
920 constexpr int kNumThreadsPerBlockMerge = 1024;
921 vllm::topKPerRowDecodeDeviceLengthAware<kNumThreadsPerBlockMerge, true>
922 <<<numRows, kNumThreadsPerBlockMerge, topK * sizeof(int32_t),
923 stream>>>(
924 outLogitsAux.const_data_ptr<float>(),
925 seqLens.const_data_ptr<int>(), indices.mutable_data_ptr<int>(),
926 multipleBlocksPerRowConfig * topK, 1, static_cast<int>(topK),
927 static_cast<int>(next_n), seqLensIs2D, nullptr,
928 outIndicesAux.const_data_ptr<int>());
929 return;
930 }
931 #endif
932
933 #ifdef USE_ROCM
934 const bool useGfx950TopK1024 =
935 topK == 1024 && numRows <= 64 &&
936 std::strncmp(get_device_prop()->gcnArchName, "gfx950", 6) == 0;
937 #else
938 constexpr bool useGfx950TopK1024 = false;
939 #endif
940 int multipleBlocksPerRowConfig = 10;
941 if (useGfx950TopK1024) {
942 if (numRows <= 32) {
943 multipleBlocksPerRowConfig = 8;
944 } else {
945 multipleBlocksPerRowConfig = 4;
946 }
947 }
948
949 const auto outIndicesAux = torch::stable::empty(
950 {numRows, multipleBlocksPerRowConfig, topK},
951 torch::headeronly::ScalarType::Int, std::nullopt, logits.device());
952 const auto outLogitsAux = torch::stable::empty(
953 {numRows, multipleBlocksPerRowConfig, topK},
954 torch::headeronly::ScalarType::Float, std::nullopt, logits.device());
955
956 if (useGfx950TopK1024) {
957 constexpr int kNumThreadsPerSplitBlock = 1024;
958 vllm::topKPerRowDecode<kNumThreadsPerSplitBlock, true, true>
959 <<<dim3(numRows, multipleBlocksPerRowConfig),
960 kNumThreadsPerSplitBlock, 2 * topK * sizeof(int32_t), stream>>>(
961 logits.const_data_ptr<float>(), seqLens.const_data_ptr<int>(),
962 outIndicesAux.mutable_data_ptr<int>(), static_cast<int>(stride0),
963 static_cast<int>(stride1), static_cast<int>(topK),
964 static_cast<int>(next_n), seqLensIs2D,
965 outLogitsAux.mutable_data_ptr<float>());
966 } else {
967 vllm::topKPerRowDecode<kNumThreadsPerBlock, true, true>
968 <<<dim3(numRows, multipleBlocksPerRowConfig), kNumThreadsPerBlock,
969 2 * topK * sizeof(int32_t), stream>>>(
970 logits.const_data_ptr<float>(), seqLens.const_data_ptr<int>(),
971 outIndicesAux.mutable_data_ptr<int>(), static_cast<int>(stride0),
972 static_cast<int>(stride1), static_cast<int>(topK),
973 static_cast<int>(next_n), seqLensIs2D,
974 outLogitsAux.mutable_data_ptr<float>());
975 }
976
977 constexpr int kNumThreadsPerBlockMerge = 1024;
978 vllm::topKPerRowDecode<kNumThreadsPerBlockMerge, true, false, true>
979 <<<numRows, kNumThreadsPerBlockMerge, topK * sizeof(int32_t), stream>>>(
980 outLogitsAux.const_data_ptr<float>(), seqLens.const_data_ptr<int>(),
981 indices.mutable_data_ptr<int>(), multipleBlocksPerRowConfig * topK,
982 1, static_cast<int>(topK), static_cast<int>(next_n), seqLensIs2D,
983 nullptr, multipleBlocksPerRowConfig,
984 outIndicesAux.const_data_ptr<int>());
985 }
986 }
987
988 void top_k_per_row_prefill(const torch::stable::Tensor& logits,
989 const torch::stable::Tensor& rowStarts,
990 const torch::stable::Tensor& rowEnds,
991 torch::stable::Tensor& indices, int64_t numRows,
992 int64_t stride0, int64_t stride1, int64_t topK) {
993 constexpr int kSortingAlgorithmThreshold = 12288;
994 constexpr int kNumThreadsPerBlock = 512;
995 const torch::stable::accelerator::DeviceGuard device_guard(
996 logits.get_device_index());
997 const cudaStream_t stream = get_current_cuda_stream();
998
999 int numInsertionBlocks =
1000 std::min(static_cast<int>(numRows), kSortingAlgorithmThreshold);
1001 vllm::topKPerRowPrefill<kNumThreadsPerBlock, false>
1002 <<<numInsertionBlocks, kNumThreadsPerBlock, topK * sizeof(int32_t),
1003 stream>>>(logits.const_data_ptr<float>(),
1004 rowStarts.const_data_ptr<int>(),
1005 rowEnds.const_data_ptr<int>(),
1006 indices.mutable_data_ptr<int>(), static_cast<int>(stride0),
1007 static_cast<int>(stride1), static_cast<int>(topK), 0);
1008
1009 if (numRows > kSortingAlgorithmThreshold) {
1010 int numRadixBlocks = numRows - kSortingAlgorithmThreshold;
1011 vllm::topKPerRowPrefill<kNumThreadsPerBlock, true>
1012 <<<numRadixBlocks, kNumThreadsPerBlock, topK * sizeof(int32_t),
1013 stream>>>(
1014 logits.const_data_ptr<float>(), rowStarts.const_data_ptr<int>(),
1015 rowEnds.const_data_ptr<int>(), indices.mutable_data_ptr<int>(),
1016 static_cast<int>(stride0), static_cast<int>(stride1),
1017 static_cast<int>(topK), kSortingAlgorithmThreshold);
1018 }
1019 }