main
cu 1,512 lines 58.8 KB
Raw
1 /*
2 * Adapted from
3 * https://github.com/NVIDIA/TensorRT-LLM/blob/v1.3.0rc2/cpp/tensorrt_llm/kernels/noAuxTcKernels.cu
4 * Copyright (c) 2025, The vLLM team.
5 * SPDX-FileCopyrightText: Copyright (c) 1993-2024 NVIDIA CORPORATION &
6 * AFFILIATES. All rights reserved. SPDX-License-Identifier: Apache-2.0
7 *
8 * Licensed under the Apache License, Version 2.0 (the "License");
9 * you may not use this file except in compliance with the License.
10 * You may obtain a copy of the License at
11 *
12 * http://www.apache.org/licenses/LICENSE-2.0
13 *
14 * Unless required by applicable law or agreed to in writing, software
15 * distributed under the License is distributed on an "AS IS" BASIS,
16 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17 * See the License for the specific language governing permissions and
18 * limitations under the License.
19 */
20 #include "moeTopKFuncs.cuh"
21
22 #include <torch/csrc/stable/tensor.h>
23 #include <torch/headeronly/core/ScalarType.h>
24
25 #include "libtorch_stable/torch_utils.h"
26
27 #include <cmath>
28 #include <cstdint>
29 #include <tuple>
30 #include <cuda_fp16.h>
31 #include <cuda_bf16.h>
32 #include <cuda/std/limits>
33 #include <cooperative_groups.h>
34 #include <cooperative_groups/reduce.h>
35 namespace cg = cooperative_groups;
36
37 namespace vllm {
38 namespace moe {
39
40 constexpr unsigned FULL_WARP_MASK = 0xffffffff;
41 static constexpr int WARP_SIZE = 32;
42 static constexpr int NumNemotronExperts = 512;
43 static constexpr int NumKimiK2Experts = 384;
44 static constexpr int NumDeepseekExperts = 256;
45 static constexpr int MaxSupportedExpertCount =
46 std::max({NumNemotronExperts, NumKimiK2Experts, NumDeepseekExperts});
47 static constexpr int MaxNumExpertsUnit = 128;
48 static constexpr int NumTopGroupScores = 2;
49 static constexpr int DefaultMaxNumTopExperts = 8;
50 static constexpr int MaxSupportedTopExperts = 22;
51 static constexpr int MaxNumTopGroups = 4;
52 // The empirical value for small batch
53 static constexpr int PDLEnableTokens = 16;
54 namespace warp_topk {
55
56 template <int size, typename T>
57 __host__ __device__ constexpr T round_up_to_multiple_of(T len) {
58 if (len == 0) {
59 return 0;
60 }
61 return ((len - 1) / size + 1) * size;
62 }
63
64 template <typename T>
65 constexpr __host__ __device__ bool isPowerOf2(T v) {
66 return (v && !(v & (v - 1)));
67 }
68
69 template <bool greater, typename T>
70 __forceinline__ __device__ bool is_better_than(T val, T baseline) {
71 return (val > baseline && greater) || (val < baseline && !greater);
72 }
73
74 template <bool greater, typename T, typename idxT>
75 __forceinline__ __device__ bool is_better_than(T val, T baseline, idxT index,
76 idxT baseline_index) {
77 bool res = (val > baseline && greater) || (val < baseline && !greater);
78 if (val == baseline) {
79 res = (index < baseline_index && greater) ||
80 (index < baseline_index && !greater);
81 }
82 return res;
83 }
84
85 template <int size, bool ascending, bool reverse, typename T, typename idxT,
86 bool is_stable>
87 struct BitonicMerge {
88 // input should be a bitonic sequence, and sort it to be a monotonic sequence
89 __device__ static void merge(T* __restrict__ val_arr,
90 idxT* __restrict__ idx_arr) {
91 static_assert(isPowerOf2(size));
92 static_assert(size >= 2 * WARP_SIZE);
93 constexpr int arr_len = size / WARP_SIZE;
94
95 constexpr int stride = arr_len / 2;
96 for (int i = 0; i < stride; ++i) {
97 int const other_i = i + stride;
98 T& val = val_arr[i];
99 T& other_val = val_arr[other_i];
100 bool is_better;
101 if constexpr (is_stable) {
102 is_better = is_better_than<ascending>(val, other_val, idx_arr[i],
103 idx_arr[other_i]);
104 } else {
105 is_better = is_better_than<ascending>(val, other_val);
106 }
107
108 if (is_better) {
109 T tmp = val;
110 val = other_val;
111 other_val = tmp;
112
113 idxT tmp2 = idx_arr[i];
114 idx_arr[i] = idx_arr[other_i];
115 idx_arr[other_i] = tmp2;
116 }
117 }
118
119 BitonicMerge<size / 2, ascending, reverse, T, idxT, is_stable>::merge(
120 val_arr, idx_arr);
121 BitonicMerge<size / 2, ascending, reverse, T, idxT, is_stable>::merge(
122 val_arr + arr_len / 2, idx_arr + arr_len / 2);
123 }
124 };
125
126 template <int size, bool ascending, typename T, typename idxT, bool is_stable>
127 struct BitonicSort {
128 __device__ static void sort(T* __restrict__ val_arr,
129 idxT* __restrict__ idx_arr) {
130 static_assert(isPowerOf2(size));
131 static_assert(size >= 2 * WARP_SIZE);
132 constexpr int arr_len = size / WARP_SIZE;
133
134 BitonicSort<size / 2, true, T, idxT, is_stable>::sort(val_arr, idx_arr);
135 BitonicSort<size / 2, false, T, idxT, is_stable>::sort(
136 val_arr + arr_len / 2, idx_arr + arr_len / 2);
137 BitonicMerge<size, ascending, ascending, T, idxT, is_stable>::merge(
138 val_arr, idx_arr);
139 }
140 };
141
142 template <bool ascending, typename T, typename idxT, bool is_stable>
143 struct BitonicSort<32, ascending, T, idxT, is_stable> {
144 __device__ static void sort(T* __restrict__ val_arr,
145 idxT* __restrict__ idx_arr) {
146 int const lane = threadIdx.x % WARP_SIZE;
147
148 // ascending doesn't matter before merging since all we need is a bitonic
149 // sequence
150 for (int stage = 0; stage < 4; ++stage) {
151 for (int stride = (1 << stage); stride > 0; stride /= 2) {
152 bool reverse = (lane >> stage) & 2;
153 bool is_second = lane & stride;
154
155 T other = __shfl_xor_sync(FULL_WARP_MASK, *val_arr, stride);
156 idxT other_idx = __shfl_xor_sync(FULL_WARP_MASK, *idx_arr, stride);
157
158 bool is_better;
159 if constexpr (is_stable) {
160 if constexpr (ascending) {
161 is_better = ((*val_arr > other) ||
162 ((*val_arr == other) && (*idx_arr < other_idx))) !=
163 (reverse != is_second);
164 } else {
165 is_better = ((*val_arr > other) ||
166 ((*val_arr == other) && (*idx_arr > other_idx))) !=
167 (reverse != is_second);
168 }
169 } else {
170 is_better = (*val_arr != other &&
171 (*val_arr > other) != (reverse != is_second));
172 }
173 if (is_better) {
174 *val_arr = other;
175 *idx_arr = other_idx;
176 }
177 }
178 }
179
180 BitonicMerge<32, ascending, ascending, T, idxT, is_stable>::merge(val_arr,
181 idx_arr);
182 }
183 };
184
185 template <bool ascending, bool reverse, typename T, typename idxT,
186 bool is_stable>
187 struct BitonicMerge<32, ascending, reverse, T, idxT, is_stable> {
188 __device__ static void merge(T* __restrict__ val_arr,
189 idxT* __restrict__ idx_arr) {
190 int const lane = threadIdx.x % WARP_SIZE;
191 for (int stride = WARP_SIZE / 2; stride > 0; stride /= 2) {
192 bool is_second = lane & stride;
193 T& val = *val_arr;
194 T other = __shfl_xor_sync(FULL_WARP_MASK, val, stride);
195 idxT& idx = *idx_arr;
196 idxT other_idx = __shfl_xor_sync(FULL_WARP_MASK, idx, stride);
197
198 bool is_better;
199 if constexpr (is_stable) {
200 if constexpr (ascending) {
201 is_better = ((*val_arr > other) ||
202 ((*val_arr == other) && (*idx_arr < other_idx))) ==
203 (reverse != is_second); // for min
204 } else {
205 is_better = ((*val_arr > other) ||
206 ((*val_arr == other) && (*idx_arr > other_idx))) ==
207 (reverse != is_second); // for max
208 }
209 } else {
210 is_better =
211 (val != other && ((val > other) == (ascending != is_second)));
212 }
213
214 if (is_better) {
215 val = other;
216 idx = other_idx;
217 }
218 }
219 }
220 };
221
222 template <int capacity, bool greater, typename T, typename idxT, bool is_stable>
223 class WarpSort {
224 public:
225 __device__ WarpSort(idxT k, T dummy)
226 : lane_(threadIdx.x % WARP_SIZE), k_(k), dummy_(dummy) {
227 static_assert(capacity >= WARP_SIZE && isPowerOf2(capacity));
228
229 for (int i = 0; i < max_arr_len_; ++i) {
230 val_arr_[i] = dummy_;
231 idx_arr_[i] = 0;
232 }
233 }
234
235 // Accessors for per-lane selected value/index.
236 // NOTE: For the common case `capacity == WARP_SIZE`, `max_arr_len_ == 1`
237 // and callers should use `i == 0`.
238 __device__ __forceinline__ idxT get_idx(int i = 0) const {
239 return idx_arr_[i];
240 }
241
242 __device__ __forceinline__ T get_val(int i = 0) const { return val_arr_[i]; }
243
244 protected:
245 static constexpr int max_arr_len_ = capacity / WARP_SIZE;
246
247 T val_arr_[max_arr_len_];
248 idxT idx_arr_[max_arr_len_];
249
250 int const lane_;
251 idxT const k_;
252 T const dummy_;
253
254 }; // end class WarpSort
255
256 template <int capacity, bool greater, typename T, typename idxT, bool is_stable>
257 class WarpSelect : public WarpSort<capacity, greater, T, idxT, is_stable> {
258 public:
259 __device__ WarpSelect(idxT k, T dummy)
260 : WarpSort<capacity, greater, T, idxT, is_stable>(k, dummy),
261 k_th_(dummy),
262 k_th_idx_(0),
263 k_th_lane_((k - 1) % WARP_SIZE) {
264 extern __shared__ char smem_buf[]; // extern __shared__ T smem_buf[];
265
266 int const num_of_warp = blockDim.x / WARP_SIZE;
267 int const warp_id = threadIdx.x / WARP_SIZE;
268 val_smem_ = reinterpret_cast<T*>(smem_buf);
269 val_smem_ += warp_id * WARP_SIZE;
270 idx_smem_ = reinterpret_cast<idxT*>(
271 smem_buf +
272 round_up_to_multiple_of<256>(num_of_warp * sizeof(T) * WARP_SIZE));
273 idx_smem_ += warp_id * WARP_SIZE;
274 }
275
276 __device__ void add(T const* in, idxT start, idxT end) {
277 idxT const end_for_fullwarp =
278 round_up_to_multiple_of<WARP_SIZE>(end - start) + start;
279 for (idxT i = start + lane_; i < end_for_fullwarp; i += WARP_SIZE) {
280 T val = (i < end) ? in[i] : dummy_;
281 add(val, i);
282 }
283 }
284
285 __device__ void add(T val, idxT idx) {
286 bool do_add;
287 if constexpr (is_stable) {
288 do_add = is_better_than<greater>(val, k_th_, idx, k_th_idx_);
289 } else {
290 do_add = is_better_than<greater>(val, k_th_);
291 }
292
293 uint32_t mask = __ballot_sync(FULL_WARP_MASK, do_add);
294 if (mask == 0) {
295 return;
296 }
297
298 int pos = smem_buf_len_ + __popc(mask & ((0x1u << lane_) - 1));
299 if (do_add && pos < WARP_SIZE) {
300 val_smem_[pos] = val;
301 idx_smem_[pos] = idx;
302 do_add = false;
303 }
304 smem_buf_len_ += __popc(mask);
305 if (smem_buf_len_ >= WARP_SIZE) {
306 __syncwarp();
307 merge_buf_(val_smem_[lane_], idx_smem_[lane_]);
308 smem_buf_len_ -= WARP_SIZE;
309 }
310 if (do_add) {
311 pos -= WARP_SIZE;
312 val_smem_[pos] = val;
313 idx_smem_[pos] = idx;
314 }
315 __syncwarp();
316 }
317
318 __device__ void done() {
319 if (smem_buf_len_) {
320 T val = (lane_ < smem_buf_len_) ? val_smem_[lane_] : dummy_;
321 idxT idx = (lane_ < smem_buf_len_) ? idx_smem_[lane_] : 0;
322 merge_buf_(val, idx);
323 }
324 }
325
326 private:
327 __device__ void set_k_th_() {
328 k_th_ = __shfl_sync(FULL_WARP_MASK, val_arr_[max_arr_len_ - 1], k_th_lane_);
329 if constexpr (is_stable) {
330 k_th_idx_ =
331 __shfl_sync(FULL_WARP_MASK, idx_arr_[max_arr_len_ - 1], k_th_lane_);
332 }
333 }
334
335 __device__ void merge_buf_(T val, idxT idx) {
336 BitonicSort<WARP_SIZE, greater, T, idxT, is_stable>::sort(&val, &idx);
337
338 T& old = val_arr_[max_arr_len_ - 1];
339
340 bool is_better;
341 if constexpr (is_stable) {
342 is_better =
343 is_better_than<greater>(val, old, idx, idx_arr_[max_arr_len_ - 1]);
344 } else {
345 is_better = is_better_than<greater>(val, old);
346 }
347
348 if (is_better) {
349 old = val;
350 idx_arr_[max_arr_len_ - 1] = idx;
351 }
352
353 BitonicMerge<capacity, greater, !greater, T, idxT, is_stable>::merge(
354 val_arr_, idx_arr_);
355
356 set_k_th_();
357 }
358
359 using WarpSort<capacity, greater, T, idxT, is_stable>::max_arr_len_;
360 using WarpSort<capacity, greater, T, idxT, is_stable>::val_arr_;
361 using WarpSort<capacity, greater, T, idxT, is_stable>::idx_arr_;
362 using WarpSort<capacity, greater, T, idxT, is_stable>::lane_;
363 using WarpSort<capacity, greater, T, idxT, is_stable>::k_;
364 using WarpSort<capacity, greater, T, idxT, is_stable>::dummy_;
365
366 T* val_smem_;
367 idxT* idx_smem_;
368 int smem_buf_len_ = 0;
369
370 T k_th_;
371 idxT k_th_idx_;
372 int const k_th_lane_;
373 }; // end class WarpSelect
374 } // namespace warp_topk
375
376 template <typename T_OUT, typename T_IN>
377 __device__ inline T_OUT cuda_cast(T_IN val) {
378 return val;
379 }
380
381 template <>
382 __device__ inline float cuda_cast<float, __nv_bfloat16>(__nv_bfloat16 val) {
383 return __bfloat162float(val);
384 }
385
386 template <typename T>
387 __device__ inline T neg_inf() {
388 // cuda::std::numeric_limits<T>::infinity() returns `0` for [T=bf16 or fp16]
389 // so we need to cast from fp32
390 return cuda_cast<T, float>(-cuda::std::numeric_limits<float>::infinity());
391 }
392
393 template <typename T>
394 __device__ inline bool is_finite(const T val) {
395 #if (__CUDACC_VER_MAJOR__ * 10000 + __CUDACC_VER_MINOR__ * 100 >= 120800)
396 return cuda::std::isfinite(val);
397 #else
398 return isfinite(cuda_cast<float, T>(val));
399 #endif
400 }
401
402 // Scoring function enums
403 enum ScoringFunc {
404 SCORING_NONE = 0, // no activation function
405 SCORING_SIGMOID = 1 // apply sigmoid
406 };
407
408 // Adapted from
409 // https://github.com/NVIDIA/TensorRT-LLM/blob/v1.3.0rc2/cpp/tensorrt_llm/kernels/noAuxTcKernels.cu
410 __device__ inline float sigmoid_accurate(float x) {
411 return 0.5f * tanhf(0.5f * x) + 0.5f;
412 }
413
414 template <typename T>
415 __device__ inline T apply_sigmoid(T val) {
416 float f = cuda_cast<float, T>(val);
417 return cuda_cast<T, float>(sigmoid_accurate(f));
418 }
419
420 template <ScoringFunc SF, typename T>
421 __device__ inline T apply_scoring(T val) {
422 if constexpr (SF == SCORING_NONE) {
423 return val;
424 } else if constexpr (SF == SCORING_SIGMOID) {
425 return apply_sigmoid(val);
426 } else {
427 static_assert(SF == SCORING_NONE || SF == SCORING_SIGMOID,
428 "Unsupported ScoringFunc in apply_scoring");
429 return val;
430 }
431 }
432
433 template <typename T, typename BiasT, ScoringFunc SF>
434 __device__ void topk_with_k2(T* output, T const* input, BiasT const* bias,
435 cg::thread_block_tile<32> const& tile,
436 int32_t const lane_id,
437 int const num_experts_per_group) {
438 // Get the top2 per thread
439 T largest = neg_inf<T>();
440 T second_largest = neg_inf<T>();
441
442 if (num_experts_per_group > WARP_SIZE) {
443 for (int i = lane_id; i < num_experts_per_group; i += WARP_SIZE) {
444 T value = apply_scoring<SF>(input[i]);
445 value = value + static_cast<T>(bias[i]);
446
447 if (value > largest) {
448 second_largest = largest;
449 largest = value;
450 } else if (value > second_largest) {
451 second_largest = value;
452 }
453 }
454 } else {
455 for (int i = lane_id; i < num_experts_per_group; i += WARP_SIZE) {
456 T value = apply_scoring<SF>(input[i]);
457 value = value + static_cast<T>(bias[i]);
458 largest = value;
459 }
460 }
461 // Get the top2 warpwise
462 T max1 = cg::reduce(tile, largest, cg::greater<T>());
463
464 T max2 = max1;
465 bool equal_to_max1 = (max1 == largest);
466
467 int count_max1 = __popc(__ballot_sync(FULL_WARP_MASK, equal_to_max1));
468
469 if (count_max1 == 1) {
470 largest = (largest == max1) ? second_largest : largest;
471 max2 = cg::reduce(tile, largest, cg::greater<T>());
472 }
473
474 if (lane_id == 0) {
475 *output = max1 + max2;
476 }
477 }
478
479 template <typename T, typename BiasT, typename IdxT, ScoringFunc SF>
480 __global__ void grouped_topk_fused_kernel(
481 T* scores, float* topk_values, IdxT* topk_indices, BiasT const* bias,
482 int64_t const num_tokens, int64_t const num_experts, int64_t const n_group,
483 int64_t const topk_group, int64_t const topk, bool renormalize,
484 double routed_scaling_factor) {
485 int32_t const token_id = static_cast<int32_t>(blockIdx.x);
486 if (token_id >= num_tokens) {
487 return;
488 }
489
490 int32_t const warp_id = threadIdx.x / WARP_SIZE;
491 int32_t const lane_id = threadIdx.x % WARP_SIZE;
492
493 int32_t const n_group_i32 = static_cast<int32_t>(n_group);
494 int32_t const topk_group_i32 = static_cast<int32_t>(topk_group);
495 int32_t const topk_i32 = static_cast<int32_t>(topk);
496 int32_t const num_experts_i32 = static_cast<int32_t>(num_experts);
497
498 int32_t const num_warps = blockDim.x / WARP_SIZE;
499 if (warp_id >= n_group_i32 || num_warps < n_group_i32) {
500 return;
501 }
502
503 int32_t const num_experts_per_group = num_experts_i32 / n_group_i32;
504
505 T* scores_token = scores + static_cast<int64_t>(token_id) * num_experts;
506
507 cg::thread_block block = cg::this_thread_block();
508 cg::thread_block_tile<32> tile = cg::tiled_partition<32>(block);
509
510 extern __shared__ char smem_buf[];
511 // warpSelect internal staging buffer layout
512 size_t const val_bytes =
513 static_cast<size_t>(num_warps) * WARP_SIZE * sizeof(T);
514 size_t const val_bytes_aligned =
515 warp_topk::round_up_to_multiple_of<256>(val_bytes);
516 size_t const idx_bytes =
517 static_cast<size_t>(num_warps) * WARP_SIZE * sizeof(int32_t);
518 size_t const internal_bytes = val_bytes_aligned + idx_bytes;
519
520 // user-managed shared memory starts after warpSelect internal staging.
521 uintptr_t ptr_u = reinterpret_cast<uintptr_t>(smem_buf + internal_bytes);
522 ptr_u = (ptr_u + 15) & ~static_cast<uintptr_t>(15); // align to 16B
523 T* s_group_scores = reinterpret_cast<T*>(ptr_u);
524
525 #if (defined(__CUDA_ARCH__) && (__CUDA_ARCH__ >= 900))
526 cudaGridDependencySynchronize(); // I think all prolog can be put before
527 // acqbulk because it's ptr arithmetic
528 #endif
529
530 // phase 1: per-group scan
531 int32_t const group_offset = warp_id * num_experts_per_group;
532 topk_with_k2<T, BiasT, SF>(s_group_scores + warp_id,
533 scores_token + group_offset, bias + group_offset,
534 tile, lane_id, num_experts_per_group);
535
536 __syncthreads();
537
538 // phase 2: warp0 selects groups + merges candidates to final topk
539 if (warp_id != 0) {
540 return;
541 }
542
543 topk_values += static_cast<int64_t>(token_id) * topk;
544 topk_indices += static_cast<int64_t>(token_id) * topk;
545
546 // select topk_group groups by group score
547 warp_topk::WarpSelect</*capability*/ WARP_SIZE, /*greater*/ true, T, int32_t,
548 /* is_stable */ true>
549 group_sel(static_cast<int32_t>(topk_group_i32), neg_inf<T>());
550
551 // all lanes must participate in WarpSelect::add().
552 T gscore = (lane_id < n_group_i32) ? s_group_scores[lane_id] : neg_inf<T>();
553 group_sel.add(gscore, lane_id);
554 group_sel.done();
555
556 // proceed only if the k-th selected group score is not -inf
557 bool proceed = false;
558 if (topk_group_i32 > 0) {
559 int const kth_lane = topk_group_i32 - 1;
560 // broadcast the k-th selected group score to all lanes
561 T kth_val = __shfl_sync(FULL_WARP_MASK, group_sel.get_val(0), kth_lane);
562 proceed = (kth_val != neg_inf<T>());
563 }
564
565 if (!proceed) {
566 for (int i = lane_id; i < topk_i32; i += WARP_SIZE) {
567 topk_indices[i] = static_cast<IdxT>(i);
568 topk_values[i] = 1.0f / static_cast<float>(topk_i32);
569 }
570 #if (defined(__CUDA_ARCH__) && (__CUDA_ARCH__ >= 900))
571 cudaTriggerProgrammaticLaunchCompletion();
572 #endif
573 return;
574 }
575
576 // merge per-group topk candidates for selected groups, then select topk
577 warp_topk::WarpSelect</*capability*/ WARP_SIZE, /*greater*/ true, T, int32_t,
578 /* is_stable */ true>
579 expert_sel(static_cast<int32_t>(topk_i32), neg_inf<T>());
580
581 // selected group ids reside in lanes [0, topk_group)
582 int32_t sel_gid_lane = (lane_id < topk_group_i32) ? group_sel.get_idx(0) : 0;
583
584 // add candidates from selected groups to expert_sel
585 for (int32_t g = 0; g < topk_group_i32; ++g) {
586 int32_t gid = __shfl_sync(FULL_WARP_MASK, sel_gid_lane, g);
587 int32_t const offset = gid * num_experts_per_group;
588 int32_t const align_num_experts_per_group =
589 warp_topk::round_up_to_multiple_of<WARP_SIZE>(num_experts_per_group);
590 for (int32_t i = lane_id; i < align_num_experts_per_group; i += WARP_SIZE) {
591 // all lanes must call `add()` the same number of times.
592 T cand = neg_inf<T>();
593 int32_t idx = 0;
594 if (i < num_experts_per_group) {
595 idx = offset + i;
596 T input = scores_token[idx];
597 if (is_finite(input)) {
598 T score = apply_scoring<SF>(input);
599 cand = score + static_cast<T>(bias[idx]);
600 }
601 }
602 expert_sel.add(cand, idx);
603 }
604 }
605 expert_sel.done();
606
607 // compute unbiased routing weights + optional renorm.
608 float lane_unbiased = 0.0f;
609 IdxT lane_idx = 0;
610 if (lane_id < topk_i32) {
611 lane_idx = static_cast<IdxT>(expert_sel.get_idx(0));
612 T in = scores_token[static_cast<int32_t>(lane_idx)];
613 lane_unbiased = cuda_cast<float, T>(apply_scoring<SF>(in));
614 }
615
616 float topk_sum = 1e-20f;
617 if (renormalize) {
618 topk_sum += cg::reduce(tile, lane_unbiased, cg::plus<float>());
619 }
620
621 float scale = static_cast<float>(routed_scaling_factor);
622 if (renormalize) {
623 scale /= topk_sum;
624 }
625
626 if (lane_id < topk_i32) {
627 topk_indices[lane_id] = lane_idx;
628 topk_values[lane_id] = lane_unbiased * scale;
629 }
630
631 #if (defined(__CUDA_ARCH__) && (__CUDA_ARCH__ >= 900))
632 cudaTriggerProgrammaticLaunchCompletion();
633 #endif
634 }
635
636 template <typename T, typename BiasT, typename IdxT, ScoringFunc SF,
637 int MaxNumExperts, bool UseGroups,
638 int MaxNumTopExperts = DefaultMaxNumTopExperts>
639 __global__ void grouped_topk_fused_small_expert_count_kernel(
640 T* scores, float* topkValues, IdxT* topkIndices, BiasT const* routingBias,
641 int64_t const numTokens, int64_t const numGroup, int64_t const topkGroup,
642 int64_t const topk, int64_t const numExperts,
643 int64_t const numExpertsPerGroup, bool const renormalize,
644 double const routedScalingFactor) {
645 #if (defined(__CUDA_ARCH__) && (__CUDA_ARCH__ >= 900))
646 cudaGridDependencySynchronize();
647 #endif
648 // declare shared memory structure
649 // number of experts is bounded by number of threads
650 __shared__ float __attribute((aligned(128))) smemScoreSigmoid[MaxNumExperts];
651 __shared__ float __attribute((aligned(128))) smemScoreBias[MaxNumExperts];
652 // number of expert groups is bounded by number of warps
653 int constexpr NumWarps = MaxNumExperts / WARP_SIZE;
654 __shared__ float __attribute((aligned(128))) smemGroupScores[NumWarps];
655
656 // needed for warp reduce
657 auto block = cg::this_thread_block();
658 auto warp = cg::tiled_partition<WARP_SIZE>(block);
659
660 // for the final reduction of weight norm, only some lanes need to participate
661 int32_t laneIdx = threadIdx.x % WARP_SIZE;
662 int32_t warpIdx = __shfl_sync(0xffffffff, threadIdx.x / WARP_SIZE, 0);
663
664 if constexpr (UseGroups) {
665 if (warpIdx >= numGroup) {
666 return;
667 }
668 }
669 // note that for invalid scores, we simply use a negative value:
670 // they work well even with the compacted format used in topK, and
671 // sigmoid / bias activated scores cannot be negative
672 const float invalidScoreFloat = float{-INFINITY};
673
674 // load bias already; each warp represents one expert group
675 auto threadExpert = threadIdx.x;
676 bool expertSelected = threadExpert < numExperts;
677 if constexpr (UseGroups) {
678 threadExpert = warpIdx * numExpertsPerGroup + laneIdx;
679 expertSelected = laneIdx < numExpertsPerGroup;
680 }
681
682 auto scoreIdx = int64_t{blockIdx.x} * int64_t{numExperts} + threadExpert;
683 auto biasVal = expertSelected ? static_cast<float>(routingBias[threadExpert])
684 : invalidScoreFloat;
685 topkValues += blockIdx.x * topk;
686 topkIndices += blockIdx.x * topk;
687
688 // get our assigned thread score; each warp represents one expert group
689 float score =
690 expertSelected ? static_cast<float>(scores[scoreIdx]) : invalidScoreFloat;
691 auto scoreSigmoid = apply_scoring<SF>(score);
692 // write the sigmoid score to shared for later use
693 if (expertSelected) {
694 smemScoreSigmoid[threadExpert] = scoreSigmoid;
695 }
696
697 // get the score with bias
698 // note that with invalid values, because sigmoid is < 1 and bias is -1,
699 // we must get a negative value, which is smaller than any valid value
700 auto scoreBias = float{scoreSigmoid + float{biasVal}};
701
702 if (expertSelected) {
703 smemScoreBias[threadExpert] = scoreBias;
704 }
705
706 // registers for top group score reduction
707 float topExpGroupScores[NumTopGroupScores];
708 [[maybe_unused]] int32_t topExpGroupIdx[NumTopGroupScores];
709 float topGroups[MaxNumTopGroups]; // bound of numGroup
710 int32_t topGroupIdx[MaxNumTopGroups];
711 float expertScoreGroup[MaxNumTopGroups];
712 int32_t expertIdxGroup[MaxNumTopGroups];
713 float topScores[MaxNumTopExperts]; // bound of topk
714 int32_t topExperts[MaxNumTopExperts];
715
716 if constexpr (UseGroups) {
717 reduce_topk::reduceTopK(warp, topExpGroupScores, topExpGroupIdx, scoreBias,
718 threadExpert,
719 /* minValue */ invalidScoreFloat);
720
721 // get the final group score and write it to shared
722 if (warp.thread_rank() == 0) {
723 auto groupScore = topExpGroupScores[0] + topExpGroupScores[1];
724 smemGroupScores[warpIdx] = groupScore;
725 }
726 }
727
728 // make group scores available to all warps
729 __syncthreads();
730
731 if constexpr (UseGroups) {
732 if (warpIdx == 0) {
733 // a single warp performs the selection of top groups, and goes on to
734 // select the final experts
735 float groupScore =
736 laneIdx < numGroup ? smemGroupScores[laneIdx] : invalidScoreFloat;
737
738 reduce_topk::reduceTopK(warp, topGroups, topGroupIdx, groupScore, laneIdx,
739 /* minValue */ invalidScoreFloat);
740 // final expert selection: get relevant indexes and scores from shared
741 #pragma unroll
742 for (int ii = 0; ii < MaxNumTopGroups; ++ii) { // bound of numGroup
743 auto groupIdx = topGroupIdx[ii];
744 expertIdxGroup[ii] = groupIdx * numExpertsPerGroup + laneIdx;
745
746 expertScoreGroup[ii] = (ii < topkGroup) && expertSelected
747 ? smemScoreBias[expertIdxGroup[ii]]
748 : invalidScoreFloat;
749 }
750
751 reduce_topk::reduceTopK(warp, topScores, topExperts, expertScoreGroup,
752 expertIdxGroup, /* minValue */ invalidScoreFloat,
753 topk);
754 }
755 } else if constexpr (MaxNumExperts > MaxNumExpertsUnit) {
756 // without groups, and the expert number is larger than MaxNumExpertsUnit,
757 // we need to use multiple warps to calculate the intermediate topk results
758
759 int constexpr NumExpertWarps = (MaxNumExperts - 1) / MaxNumExpertsUnit + 1;
760 int constexpr NumInterTopK = NumExpertWarps * MaxNumTopExperts;
761 __shared__ float
762 __attribute((aligned(128))) smemInterTopScores[NumInterTopK];
763 __shared__ int32_t
764 __attribute((aligned(128))) smemInterTopExperts[NumInterTopK];
765 if (warpIdx < NumExpertWarps) {
766 int offset = warpIdx * WARP_SIZE * MaxNumTopGroups;
767 #pragma unroll
768 for (int ii = 0; ii < MaxNumTopGroups; ++ii) {
769 auto expertIdx = ii * WARP_SIZE + laneIdx;
770 expertIdxGroup[ii] = offset + expertIdx;
771 expertScoreGroup[ii] = offset + expertIdx < numExperts
772 ? smemScoreBias[offset + expertIdx]
773 : invalidScoreFloat;
774 }
775 reduce_topk::reduceTopK(warp, topScores, topExperts, expertScoreGroup,
776 expertIdxGroup,
777 /* minValue */ invalidScoreFloat, topk);
778
779 if (laneIdx < topk) {
780 smemInterTopScores[warpIdx * MaxNumTopExperts + laneIdx] =
781 topScores[laneIdx];
782 smemInterTopExperts[warpIdx * MaxNumTopExperts + laneIdx] =
783 topExperts[laneIdx];
784 } else if (laneIdx >= topk && laneIdx < MaxNumTopExperts) {
785 smemInterTopScores[warpIdx * MaxNumTopExperts + laneIdx] =
786 invalidScoreFloat;
787 smemInterTopExperts[warpIdx * MaxNumTopExperts + laneIdx] =
788 MaxNumExperts - 1;
789 }
790 }
791 __syncthreads();
792 if (warpIdx == 0) {
793 int constexpr NumInterTopKPerThread = (NumInterTopK - 1) / WARP_SIZE + 1;
794 float intermediateScore[NumInterTopKPerThread];
795 int32_t intermediateExpert[NumInterTopKPerThread];
796 for (int i = laneIdx; i < NumInterTopKPerThread * WARP_SIZE;
797 i += WARP_SIZE) {
798 int ii = i / WARP_SIZE;
799 if (i < NumInterTopK) {
800 intermediateScore[ii] = smemInterTopScores[i];
801 intermediateExpert[ii] = smemInterTopExperts[i];
802 } else {
803 intermediateScore[ii] = invalidScoreFloat;
804 intermediateExpert[ii] = MaxNumExperts - 1;
805 }
806 }
807 reduce_topk::reduceTopK(warp, topScores, topExperts, intermediateScore,
808 intermediateExpert,
809 /* minValue */ invalidScoreFloat, topk);
810 }
811 } else {
812 // without groups, and the expert number is smaller than MaxNumExpertsUnit
813 // each thread just takes `MaxNumTopGroups` experts
814 if (warpIdx == 0) {
815 #pragma unroll
816 for (int ii = 0; ii < MaxNumTopGroups; ++ii) {
817 auto expertIdx = ii * WARP_SIZE + laneIdx;
818 expertIdxGroup[ii] = expertIdx;
819 expertScoreGroup[ii] = expertIdx < numExperts ? smemScoreBias[expertIdx]
820 : invalidScoreFloat;
821 }
822 reduce_topk::reduceTopK(warp, topScores, topExperts, expertScoreGroup,
823 expertIdxGroup,
824 /* minValue */ invalidScoreFloat, topk);
825 }
826 }
827
828 if (warpIdx == 0) {
829 // determine our lane's expert index and write to output
830 int32_t expertIdx =
831 laneIdx < topk ? topExperts[laneIdx] : MaxNumExperts - 1;
832 float scoreNorm = laneIdx < topk ? smemScoreSigmoid[expertIdx] : 0.F;
833 float finalScore = static_cast<float>(scoreNorm * routedScalingFactor);
834 // norm the value
835 if (renormalize) {
836 auto redNorm = cg::reduce(warp, scoreNorm, cg::plus<float>{});
837 finalScore /= (redNorm + 1e-20);
838 }
839 // store the topk scores and experts to output
840 if (laneIdx < topk) {
841 topkValues[laneIdx] = finalScore;
842 topkIndices[laneIdx] = expertIdx;
843 }
844 }
845
846 #if (defined(__CUDA_ARCH__) && (__CUDA_ARCH__ >= 900))
847 cudaTriggerProgrammaticLaunchCompletion();
848 #endif
849 }
850
851 // Adapted from
852 // https://github.com/flashinfer-ai/flashinfer/blob/06400d062a2d51564bbe781f6f811d0b75ca593e/include/flashinfer/trtllm/fused_moe/RoutingKernelTopK.cuh
853 namespace single_group_topk {
854 namespace detail {
855
856 static constexpr int BlockDim = 256;
857 static constexpr uint32_t FullWarpMask = 0xffffffffU;
858 static constexpr float InvalidScore = -INFINITY;
859
860 // TopK-only tuning: use wider workers and keep these tiers on the block path.
861 template <int MaxNumExperts, int MaxNumTopExperts>
862 static constexpr bool UseTunedBlockPath =
863 MaxNumTopExperts == 16 && (MaxNumExperts == 896 || MaxNumExperts == 1024);
864
865 template <typename T, typename BiasT, ScoringFunc SF>
866 __device__ __forceinline__ void preprocess_score(T input, BiasT correction_bias,
867 float& unbiased_score,
868 float& selection_score) {
869 unbiased_score = 0.0F;
870 selection_score = InvalidScore;
871 float const input_float = cuda_cast<float, T>(input);
872 float const bias = cuda_cast<float, BiasT>(correction_bias);
873 if (!is_finite(input_float) || !is_finite(bias)) {
874 return;
875 }
876
877 float const unbiased = apply_scoring<SF>(input_float);
878 float const biased = unbiased + bias;
879 if constexpr (SF == SCORING_NONE) {
880 if (!is_finite(biased)) {
881 return;
882 }
883 }
884 unbiased_score = unbiased;
885 selection_score = biased == 0.0F ? 0.0F : biased;
886 }
887
888 template <typename IdxT>
889 __device__ __forceinline__ void write_outputs(
890 cg::thread_block_tile<WARP_SIZE> const& warp, float lane_selection_score,
891 float lane_unbiased, int32_t lane_expert, int32_t lane, int32_t token,
892 int32_t topk, float* topk_values, IdxT* topk_indices, bool renormalize,
893 float routed_scaling_factor) {
894 bool const finite_selection =
895 lane < topk && lane_selection_score != InvalidScore;
896 lane_unbiased = finite_selection ? lane_unbiased : 0.0F;
897 unsigned const finite_mask = __ballot_sync(FullWarpMask, finite_selection);
898 float const sum = cg::reduce(warp, lane_unbiased, cg::plus<float>{});
899
900 if (lane < topk) {
901 float output = 0.0F;
902 if (finite_mask == 0) {
903 if (renormalize) {
904 output = 1.0F / static_cast<float>(topk);
905 }
906 } else if (finite_selection) {
907 float scale = routed_scaling_factor;
908 if (renormalize) {
909 scale /= sum + 1e-20F;
910 }
911 output = lane_unbiased * scale;
912 }
913
914 int64_t const output_index = int64_t{token} * topk + lane;
915 topk_values[output_index] = output;
916 topk_indices[output_index] = static_cast<IdxT>(lane_expert);
917 }
918 }
919
920 template <typename T, typename BiasT, typename IdxT, ScoringFunc SF,
921 int MaxNumExperts, int MaxNumTopExperts>
922 __global__ void __launch_bounds__(BlockDim)
923 single_group_topk_block_kernel(T const* scores, float* topk_values,
924 IdxT* topk_indices, BiasT const* bias,
925 int64_t num_experts, int64_t topk,
926 bool renormalize,
927 float routed_scaling_factor,
928 bool enable_pdl) {
929 static constexpr int NumChunks = (MaxNumExperts + WARP_SIZE - 1) / WARP_SIZE;
930 static constexpr int WorkerValuesPerLane =
931 UseTunedBlockPath<MaxNumExperts, MaxNumTopExperts> ? 8 : 4;
932 static constexpr int ExpertsPerWorkerWarp = WorkerValuesPerLane * WARP_SIZE;
933 using LaneOwnedRange =
934 reduce_topk::HighExpertLaneOwnedTopKRange<MaxNumExperts,
935 MaxNumTopExperts>;
936 static constexpr int NumWorkerWarps =
937 (MaxNumExperts + ExpertsPerWorkerWarp - 1) / ExpertsPerWorkerWarp;
938 static constexpr int NumIntermediate = NumWorkerWarps * MaxNumTopExperts;
939 static constexpr int MergeValuesPerLane =
940 (NumIntermediate + WARP_SIZE - 1) / WARP_SIZE;
941 static constexpr bool LaneOwnedResourcesFit =
942 NumWorkerWarps <= BlockDim / WARP_SIZE && MergeValuesPerLane <= 64;
943 static constexpr bool UseHierarchicalLaneTopK =
944 LaneOwnedRange::kEnabled && LaneOwnedResourcesFit;
945
946 static_assert(NumChunks <= 64);
947 static_assert(MaxNumTopExperts <= WARP_SIZE);
948
949 #if defined(__CUDA_ARCH__) && (__CUDA_ARCH__ >= 900)
950 if (enable_pdl) {
951 cudaGridDependencySynchronize();
952 }
953 #endif
954
955 __shared__ float __attribute((aligned(128))) biased_scores[MaxNumExperts];
956 __shared__ float __attribute((aligned(128))) unbiased_scores[MaxNumExperts];
957
958 int32_t const token = static_cast<int32_t>(blockIdx.x);
959 int32_t const lane = static_cast<int32_t>(threadIdx.x) % WARP_SIZE;
960 int32_t const warp_id = static_cast<int32_t>(threadIdx.x) / WARP_SIZE;
961 int32_t const num_experts_i32 = static_cast<int32_t>(num_experts);
962 int32_t const topk_i32 = static_cast<int32_t>(topk);
963 T const* token_scores = scores + int64_t{token} * num_experts;
964
965 for (int32_t expert = static_cast<int32_t>(threadIdx.x);
966 expert < num_experts_i32; expert += BlockDim) {
967 preprocess_score<T, BiasT, SF>(token_scores[expert], bias[expert],
968 unbiased_scores[expert],
969 biased_scores[expert]);
970 }
971 __syncthreads();
972
973 auto warp = cg::tiled_partition<WARP_SIZE>(cg::this_thread_block());
974
975 if constexpr (UseHierarchicalLaneTopK) {
976 __shared__ float
977 __attribute((aligned(128))) intermediate_scores[NumIntermediate];
978 __shared__ int32_t
979 __attribute((aligned(128))) intermediate_indices[NumIntermediate];
980
981 if (warp_id < NumWorkerWarps) {
982 float local_scores[WorkerValuesPerLane];
983 int32_t local_indices[WorkerValuesPerLane];
984 #pragma unroll
985 for (int index = 0; index < WorkerValuesPerLane; ++index) {
986 int32_t const expert =
987 warp_id * ExpertsPerWorkerWarp + index * WARP_SIZE + lane;
988 local_scores[index] =
989 expert < num_experts_i32 ? biased_scores[expert] : InvalidScore;
990 local_indices[index] = expert;
991 }
992
993 float lane_score;
994 int32_t lane_expert;
995 reduce_topk::reduceTopKForLane<MaxNumTopExperts>(
996 warp, lane_score, lane_expert, local_scores, local_indices,
997 InvalidScore, lane);
998 if (lane < MaxNumTopExperts) {
999 int32_t const intermediate = warp_id * MaxNumTopExperts + lane;
1000 bool const active = lane < topk_i32;
1001 intermediate_scores[intermediate] = active ? lane_score : InvalidScore;
1002 intermediate_indices[intermediate] =
1003 active ? lane_expert : MaxNumExperts;
1004 }
1005 }
1006 __syncthreads();
1007
1008 if (warp_id != 0) {
1009 return;
1010 }
1011
1012 float merge_scores[MergeValuesPerLane];
1013 int32_t merge_indices[MergeValuesPerLane];
1014 #pragma unroll
1015 for (int index = 0; index < MergeValuesPerLane; ++index) {
1016 int32_t const intermediate = index * WARP_SIZE + lane;
1017 bool const active = intermediate < NumIntermediate;
1018 merge_scores[index] =
1019 active ? intermediate_scores[intermediate] : InvalidScore;
1020 merge_indices[index] =
1021 active ? intermediate_indices[intermediate] : MaxNumExperts;
1022 }
1023
1024 float lane_score;
1025 int32_t lane_expert;
1026 reduce_topk::reduceTopKForLane<MaxNumTopExperts>(
1027 warp, lane_score, lane_expert, merge_scores, merge_indices,
1028 InvalidScore, lane);
1029 float const lane_unbiased =
1030 lane < topk_i32 && lane_expert >= 0 && lane_expert < num_experts_i32
1031 ? unbiased_scores[lane_expert]
1032 : 0.0F;
1033 write_outputs(warp, lane_score, lane_unbiased, lane_expert, lane, token,
1034 topk_i32, topk_values, topk_indices, renormalize,
1035 routed_scaling_factor);
1036 } else {
1037 if (warp_id != 0) {
1038 return;
1039 }
1040
1041 float local_scores[NumChunks];
1042 int32_t local_indices[NumChunks];
1043 #pragma unroll
1044 for (int index = 0; index < NumChunks; ++index) {
1045 int32_t const expert = index * WARP_SIZE + lane;
1046 local_scores[index] =
1047 expert < num_experts_i32 ? biased_scores[expert] : InvalidScore;
1048 local_indices[index] = expert;
1049 }
1050
1051 float top_scores[MaxNumTopExperts];
1052 int32_t top_experts[MaxNumTopExperts];
1053 reduce_topk::reduceTopK(warp, top_scores, top_experts, local_scores,
1054 local_indices, InvalidScore, topk_i32);
1055 float const lane_score = lane < topk_i32 ? top_scores[lane] : InvalidScore;
1056 int32_t const lane_expert = lane < topk_i32 ? top_experts[lane] : -1;
1057 float const lane_unbiased =
1058 lane < topk_i32 && lane_expert >= 0 && lane_expert < num_experts_i32
1059 ? unbiased_scores[lane_expert]
1060 : 0.0F;
1061 write_outputs(warp, lane_score, lane_unbiased, lane_expert, lane, token,
1062 topk_i32, topk_values, topk_indices, renormalize,
1063 routed_scaling_factor);
1064 }
1065
1066 #if defined(__CUDA_ARCH__) && (__CUDA_ARCH__ >= 900)
1067 if (enable_pdl) {
1068 cudaTriggerProgrammaticLaunchCompletion();
1069 }
1070 #endif
1071 }
1072
1073 template <int MaxNumExperts>
1074 struct WarpTopKLaunchConfig {
1075 static constexpr int DefaultBlockDim =
1076 MaxNumExperts <= 1024 ? MaxNumExperts : 1024;
1077 static constexpr int BlockDim = DefaultBlockDim > 256 ? 256 : DefaultBlockDim;
1078 static constexpr int NumWarps = BlockDim / WARP_SIZE;
1079 static constexpr int MaxBlockScale =
1080 (DefaultBlockDim + BlockDim - 1) / BlockDim;
1081 static constexpr int MaxBlocks = 1024 * MaxBlockScale;
1082
1083 static_assert(BlockDim % WARP_SIZE == 0);
1084
1085 static uint32_t grid_dim(int64_t num_tokens) {
1086 int64_t const token_blocks = (num_tokens + NumWarps - 1) / NumWarps;
1087 int64_t const selected =
1088 token_blocks < MaxBlocks ? token_blocks : MaxBlocks;
1089 return static_cast<uint32_t>(selected > 0 ? selected : 1);
1090 }
1091 };
1092
1093 template <typename T, typename BiasT, typename IdxT, ScoringFunc SF,
1094 int MaxNumExperts, int MaxNumTopExperts>
1095 __global__ void __launch_bounds__(WarpTopKLaunchConfig<MaxNumExperts>::BlockDim)
1096 single_group_topk_warp_kernel(T const* scores, float* topk_values,
1097 IdxT* topk_indices, BiasT const* bias,
1098 int64_t num_tokens, int64_t num_experts,
1099 int64_t topk, bool renormalize,
1100 float routed_scaling_factor,
1101 bool enable_pdl) {
1102 static constexpr int NumChunks = (MaxNumExperts + WARP_SIZE - 1) / WARP_SIZE;
1103 static constexpr int WarpBlockDim =
1104 WarpTopKLaunchConfig<MaxNumExperts>::BlockDim;
1105 using LaneOwnedRange =
1106 reduce_topk::HighExpertLaneOwnedTopKRange<MaxNumExperts,
1107 MaxNumTopExperts>;
1108
1109 static_assert(NumChunks <= 64);
1110 static_assert(MaxNumTopExperts <= WARP_SIZE);
1111
1112 #if defined(__CUDA_ARCH__) && (__CUDA_ARCH__ >= 900)
1113 if (enable_pdl) {
1114 cudaGridDependencySynchronize();
1115 }
1116 #endif
1117
1118 int32_t const lane = static_cast<int32_t>(threadIdx.x) % WARP_SIZE;
1119 int32_t const warp_id = static_cast<int32_t>(threadIdx.x) / WARP_SIZE;
1120 int32_t const global_warp =
1121 static_cast<int32_t>(blockIdx.x) * WarpBlockDim / WARP_SIZE + warp_id;
1122 int32_t const global_warp_stride =
1123 static_cast<int32_t>(gridDim.x) * WarpBlockDim / WARP_SIZE;
1124 int32_t const num_experts_i32 = static_cast<int32_t>(num_experts);
1125 int32_t const topk_i32 = static_cast<int32_t>(topk);
1126 auto warp = cg::tiled_partition<WARP_SIZE>(cg::this_thread_block());
1127
1128 for (int32_t token = global_warp; token < num_tokens;
1129 token += global_warp_stride) {
1130 T const* token_scores = scores + int64_t{token} * num_experts;
1131 float local_scores[NumChunks];
1132 int32_t local_indices[NumChunks];
1133 #pragma unroll
1134 for (int index = 0; index < NumChunks; ++index) {
1135 int32_t const expert = index * WARP_SIZE + lane;
1136 float unbiased;
1137 float selection;
1138 if (expert < num_experts_i32) {
1139 preprocess_score<T, BiasT, SF>(token_scores[expert], bias[expert],
1140 unbiased, selection);
1141 } else {
1142 selection = InvalidScore;
1143 }
1144 local_scores[index] = selection;
1145 local_indices[index] = expert;
1146 }
1147
1148 float lane_score;
1149 int32_t lane_expert;
1150 if constexpr (LaneOwnedRange::kEnabled) {
1151 reduce_topk::reduceTopKForLane<MaxNumTopExperts>(
1152 warp, lane_score, lane_expert, local_scores, local_indices,
1153 InvalidScore, lane);
1154 } else {
1155 float top_scores[MaxNumTopExperts];
1156 int32_t top_experts[MaxNumTopExperts];
1157 reduce_topk::reduceTopK(warp, top_scores, top_experts, local_scores,
1158 local_indices, InvalidScore, topk_i32);
1159 lane_score = lane < topk_i32 ? top_scores[lane] : InvalidScore;
1160 lane_expert = lane < topk_i32 ? top_experts[lane] : -1;
1161 }
1162
1163 float lane_unbiased = 0.0F;
1164 if (lane < topk_i32 && lane_expert >= 0 && lane_expert < num_experts_i32) {
1165 lane_unbiased = lane_score - cuda_cast<float, BiasT>(bias[lane_expert]);
1166 }
1167 write_outputs(warp, lane_score, lane_unbiased, lane_expert, lane, token,
1168 topk_i32, topk_values, topk_indices, renormalize,
1169 routed_scaling_factor);
1170 }
1171
1172 #if defined(__CUDA_ARCH__) && (__CUDA_ARCH__ >= 900)
1173 if (enable_pdl) {
1174 cudaTriggerProgrammaticLaunchCompletion();
1175 }
1176 #endif
1177 }
1178
1179 template <int Experts, int TopK>
1180 struct Tier {
1181 static constexpr int kExperts = Experts;
1182 static constexpr int kTopK = TopK;
1183 };
1184
1185 template <typename... Tiers>
1186 struct TierList {};
1187
1188 using SigmoidBiasTiers =
1189 TierList<Tier<128, 8>, Tier<256, 8>, Tier<384, 8>, Tier<512, 8>,
1190 Tier<512, 22>, Tier<768, 16>, Tier<896, 16>, Tier<1024, 16>>;
1191
1192 using PrecomputedSoftmaxBiasTiers =
1193 TierList<Tier<128, 4>, Tier<128, 8>, Tier<160, 8>, Tier<256, 8>,
1194 Tier<256, 16>, Tier<512, 8>, Tier<512, 16>, Tier<512, 22>,
1195 Tier<512, 32>, Tier<576, 8>, Tier<768, 16>, Tier<896, 16>,
1196 Tier<1024, 16>>;
1197
1198 template <typename T, typename BiasT, typename IdxT, ScoringFunc SF,
1199 int MaxNumExperts, int MaxNumTopExperts>
1200 void launch(T* scores, float* topk_values, IdxT* topk_indices,
1201 BiasT const* bias, int64_t num_tokens, int64_t num_experts,
1202 int64_t topk, bool renormalize, double routed_scaling_factor,
1203 bool enable_pdl, cudaLaunchConfig_t& config) {
1204 config.dynamicSmemBytes = 0;
1205 bool const use_block_kernel =
1206 UseTunedBlockPath<MaxNumExperts, MaxNumTopExperts> ||
1207 MaxNumExperts > 1024 || num_experts >= 1024 ||
1208 (num_experts >= 256 && num_tokens <= 1024);
1209 if (use_block_kernel) {
1210 config.gridDim = static_cast<uint32_t>(num_tokens);
1211 config.blockDim = BlockDim;
1212 cudaLaunchKernelEx(
1213 &config,
1214 &single_group_topk_block_kernel<T, BiasT, IdxT, SF, MaxNumExperts,
1215 MaxNumTopExperts>,
1216 scores, topk_values, topk_indices, bias, num_experts, topk, renormalize,
1217 static_cast<float>(routed_scaling_factor), enable_pdl);
1218 } else {
1219 using WarpConfig = WarpTopKLaunchConfig<MaxNumExperts>;
1220 config.gridDim = WarpConfig::grid_dim(num_tokens);
1221 config.blockDim = WarpConfig::BlockDim;
1222 cudaLaunchKernelEx(
1223 &config,
1224 &single_group_topk_warp_kernel<T, BiasT, IdxT, SF, MaxNumExperts,
1225 MaxNumTopExperts>,
1226 scores, topk_values, topk_indices, bias, num_tokens, num_experts, topk,
1227 renormalize, static_cast<float>(routed_scaling_factor), enable_pdl);
1228 }
1229 }
1230
1231 template <typename T, typename BiasT, typename IdxT, ScoringFunc SF>
1232 bool dispatch(TierList<>*, T*, float*, IdxT*, BiasT const*, int64_t, int64_t,
1233 int64_t, bool, double, bool, cudaLaunchConfig_t&) {
1234 return false;
1235 }
1236
1237 template <typename T, typename BiasT, typename IdxT, ScoringFunc SF,
1238 typename First, typename... Rest>
1239 bool dispatch(TierList<First, Rest...>*, T* scores, float* topk_values,
1240 IdxT* topk_indices, BiasT const* bias, int64_t num_tokens,
1241 int64_t num_experts, int64_t topk, bool renormalize,
1242 double routed_scaling_factor, bool enable_pdl,
1243 cudaLaunchConfig_t& config) {
1244 if (num_experts <= First::kExperts && topk <= First::kTopK) {
1245 launch<T, BiasT, IdxT, SF, First::kExperts, First::kTopK>(
1246 scores, topk_values, topk_indices, bias, num_tokens, num_experts, topk,
1247 renormalize, routed_scaling_factor, enable_pdl, config);
1248 return true;
1249 }
1250 return dispatch<T, BiasT, IdxT, SF>(
1251 static_cast<TierList<Rest...>*>(nullptr), scores, topk_values,
1252 topk_indices, bias, num_tokens, num_experts, topk, renormalize,
1253 routed_scaling_factor, enable_pdl, config);
1254 }
1255
1256 } // namespace detail
1257
1258 template <typename T, typename BiasT, typename IdxT, ScoringFunc SF>
1259 bool invoke(T* scores, float* topk_values, IdxT* topk_indices,
1260 BiasT const* bias, int64_t num_tokens, int64_t num_experts,
1261 int64_t topk, bool renormalize, double routed_scaling_factor,
1262 bool enable_pdl, cudaLaunchConfig_t& config) {
1263 static_assert(SF == SCORING_NONE || SF == SCORING_SIGMOID);
1264 if constexpr (SF == SCORING_SIGMOID) {
1265 return detail::dispatch<T, BiasT, IdxT, SF>(
1266 static_cast<detail::SigmoidBiasTiers*>(nullptr), scores, topk_values,
1267 topk_indices, bias, num_tokens, num_experts, topk, renormalize,
1268 routed_scaling_factor, enable_pdl, config);
1269 } else {
1270 return detail::dispatch<T, BiasT, IdxT, SF>(
1271 static_cast<detail::PrecomputedSoftmaxBiasTiers*>(nullptr), scores,
1272 topk_values, topk_indices, bias, num_tokens, num_experts, topk,
1273 renormalize, routed_scaling_factor, enable_pdl, config);
1274 }
1275 }
1276
1277 } // namespace single_group_topk
1278
1279 template <typename T, typename BiasT, typename IdxT, ScoringFunc SF>
1280 void invokeNoAuxTc(T* scores, float* topk_values, IdxT* topk_indices,
1281 BiasT const* bias, int64_t const num_tokens,
1282 int64_t const num_experts, int64_t const n_group,
1283 int64_t const topk_group, int64_t const topk,
1284 bool const renormalize, double const routed_scaling_factor,
1285 const bool enable_pdl = false,
1286 cudaStream_t const stream = 0) {
1287 cudaLaunchConfig_t config;
1288 config.stream = stream;
1289 cudaLaunchAttribute attrs[1];
1290 attrs[0].id = cudaLaunchAttributeProgrammaticStreamSerialization;
1291 attrs[0].val.programmaticStreamSerializationAllowed = enable_pdl;
1292 config.numAttrs = 1;
1293 config.attrs = attrs;
1294 if (n_group == 1 && topk_group == 1 &&
1295 single_group_topk::invoke<T, BiasT, IdxT, SF>(
1296 scores, topk_values, topk_indices, bias, num_tokens, num_experts,
1297 topk, renormalize, routed_scaling_factor, enable_pdl, config)) {
1298 return;
1299 }
1300
1301 // Check if we can use the optimized
1302 // grouped_topk_fused_small_expert_count_kernel
1303 bool const is_single_group =
1304 (n_group == 1) && (topk_group == 1) &&
1305 (num_experts <= MaxSupportedExpertCount) &&
1306 (topk <= DefaultMaxNumTopExperts || topk == MaxSupportedTopExperts);
1307
1308 int64_t const experts_per_group = num_experts / n_group;
1309 bool const is_multi_group =
1310 (n_group > 1) && (num_experts <= NumDeepseekExperts) &&
1311 (experts_per_group <= WARP_SIZE) &&
1312 (experts_per_group * topk_group <= MaxNumExpertsUnit) &&
1313 (topk <= DefaultMaxNumTopExperts) && (topk_group <= MaxNumTopGroups);
1314
1315 if (is_single_group || is_multi_group) {
1316 auto* kernel_instance =
1317 &grouped_topk_fused_small_expert_count_kernel<T, BiasT, IdxT, SF,
1318 NumDeepseekExperts, true>;
1319 int num_threads = NumDeepseekExperts;
1320 if (is_single_group) {
1321 // Special case for Nemotron, which selects top 22 from 512 experts, and 1
1322 // group only.
1323 if (num_experts == NumNemotronExperts && n_group == 1 &&
1324 topk == MaxSupportedTopExperts) {
1325 kernel_instance = &grouped_topk_fused_small_expert_count_kernel<
1326 T, BiasT, IdxT, SF, NumNemotronExperts, false,
1327 MaxSupportedTopExperts>;
1328 num_threads = NumNemotronExperts;
1329 } else if (num_experts > NumKimiK2Experts &&
1330 num_experts <= MaxSupportedExpertCount) {
1331 kernel_instance = &grouped_topk_fused_small_expert_count_kernel<
1332 T, BiasT, IdxT, SF, MaxSupportedExpertCount, false>;
1333 num_threads = MaxSupportedExpertCount;
1334 } else if (num_experts > MaxNumExpertsUnit &&
1335 num_experts <= NumKimiK2Experts) {
1336 kernel_instance = &grouped_topk_fused_small_expert_count_kernel<
1337 T, BiasT, IdxT, SF, NumKimiK2Experts, false>;
1338 num_threads = NumKimiK2Experts;
1339 } else {
1340 kernel_instance = &grouped_topk_fused_small_expert_count_kernel<
1341 T, BiasT, IdxT, SF, MaxNumExpertsUnit, false>;
1342 num_threads = MaxNumExpertsUnit;
1343 }
1344 }
1345 config.gridDim = num_tokens;
1346 config.blockDim = num_threads;
1347 config.dynamicSmemBytes = 0;
1348 cudaLaunchKernelEx(&config, kernel_instance, scores, topk_values,
1349 topk_indices, bias, num_tokens, n_group, topk_group,
1350 topk, num_experts, num_experts / n_group, renormalize,
1351 routed_scaling_factor);
1352 } else {
1353 auto* kernel_instance = &grouped_topk_fused_kernel<T, BiasT, IdxT, SF>;
1354 // One block per token; one warp per group.
1355 config.gridDim = static_cast<uint32_t>(num_tokens);
1356 config.blockDim = static_cast<uint32_t>(n_group) * WARP_SIZE;
1357 // Dynamic shared memory: WarpSelect staging + per-group topk buffers.
1358 int32_t const num_warps = static_cast<int32_t>(n_group);
1359 size_t const val_bytes =
1360 static_cast<size_t>(num_warps) * WARP_SIZE * sizeof(T);
1361 size_t const val_bytes_aligned =
1362 warp_topk::round_up_to_multiple_of<256>(val_bytes);
1363 size_t const idx_bytes =
1364 static_cast<size_t>(num_warps) * WARP_SIZE * sizeof(int32_t);
1365 size_t const internal_bytes = val_bytes_aligned + idx_bytes;
1366 size_t const extra_bytes = 16 + static_cast<size_t>(n_group) * sizeof(T);
1367 config.dynamicSmemBytes = internal_bytes + extra_bytes;
1368 cudaLaunchKernelEx(&config, kernel_instance, scores, topk_values,
1369 topk_indices, bias, num_tokens, num_experts, n_group,
1370 topk_group, topk, renormalize, routed_scaling_factor);
1371 }
1372 }
1373
1374 #define INSTANTIATE_NOAUX_TC(T, BiasT, IdxT, SF) \
1375 template void invokeNoAuxTc<T, BiasT, IdxT, SF>( \
1376 T * scores, float* topk_values, IdxT* topk_indices, BiasT const* bias, \
1377 int64_t const num_tokens, int64_t const num_experts, \
1378 int64_t const n_group, int64_t const topk_group, int64_t const topk, \
1379 bool const renormalize, double const routed_scaling_factor, \
1380 const bool enable_pdl, cudaStream_t const stream);
1381
1382 INSTANTIATE_NOAUX_TC(float, float, int32_t, SCORING_SIGMOID);
1383 INSTANTIATE_NOAUX_TC(float, half, int32_t, SCORING_SIGMOID);
1384 INSTANTIATE_NOAUX_TC(float, __nv_bfloat16, int32_t, SCORING_SIGMOID);
1385 INSTANTIATE_NOAUX_TC(half, float, int32_t, SCORING_SIGMOID);
1386 INSTANTIATE_NOAUX_TC(half, half, int32_t, SCORING_SIGMOID);
1387 INSTANTIATE_NOAUX_TC(half, __nv_bfloat16, int32_t, SCORING_SIGMOID);
1388 INSTANTIATE_NOAUX_TC(__nv_bfloat16, float, int32_t, SCORING_SIGMOID);
1389 INSTANTIATE_NOAUX_TC(__nv_bfloat16, half, int32_t, SCORING_SIGMOID);
1390 INSTANTIATE_NOAUX_TC(__nv_bfloat16, __nv_bfloat16, int32_t, SCORING_SIGMOID);
1391 INSTANTIATE_NOAUX_TC(float, float, int32_t, SCORING_NONE);
1392 INSTANTIATE_NOAUX_TC(float, half, int32_t, SCORING_NONE);
1393 INSTANTIATE_NOAUX_TC(float, __nv_bfloat16, int32_t, SCORING_NONE);
1394 INSTANTIATE_NOAUX_TC(half, float, int32_t, SCORING_NONE);
1395 INSTANTIATE_NOAUX_TC(half, half, int32_t, SCORING_NONE);
1396 INSTANTIATE_NOAUX_TC(half, __nv_bfloat16, int32_t, SCORING_NONE);
1397 INSTANTIATE_NOAUX_TC(__nv_bfloat16, float, int32_t, SCORING_NONE);
1398 INSTANTIATE_NOAUX_TC(__nv_bfloat16, half, int32_t, SCORING_NONE);
1399 INSTANTIATE_NOAUX_TC(__nv_bfloat16, __nv_bfloat16, int32_t, SCORING_NONE);
1400 } // end namespace moe
1401 } // namespace vllm
1402
1403 std::tuple<torch::stable::Tensor, torch::stable::Tensor> grouped_topk(
1404 torch::stable::Tensor const& scores, int64_t n_group, int64_t topk_group,
1405 int64_t topk, bool renormalize, double routed_scaling_factor,
1406 torch::stable::Tensor const& bias, int64_t scoring_func = 0) {
1407 const auto data_type = scores.scalar_type();
1408 const auto bias_type = bias.scalar_type();
1409 STD_TORCH_CHECK(scores.dim() == 2, "scores must be a 2D Tensor");
1410 const int64_t num_tokens = scores.size(0);
1411 const int64_t num_experts = scores.size(1);
1412 STD_TORCH_CHECK(n_group > 0, "n_group must be positive");
1413 STD_TORCH_CHECK(topk > 0, "topk must be positive");
1414 STD_TORCH_CHECK(topk_group > 0, "topk_group must be positive");
1415 STD_TORCH_CHECK(topk_group <= n_group, "topk_group must be <= n_group");
1416 STD_TORCH_CHECK(num_experts % n_group == 0,
1417 "num_experts should be divisible by n_group");
1418 STD_TORCH_CHECK(n_group <= 32,
1419 "n_group should be smaller than or equal to 32 for now");
1420 STD_TORCH_CHECK(topk <= 32,
1421 "topk should be smaller than or equal to 32 for now");
1422 STD_TORCH_CHECK(topk <= topk_group * (num_experts / n_group),
1423 "topk must be <= topk_group * (num_experts / n_group)");
1424 STD_TORCH_CHECK(
1425 scoring_func == vllm::moe::SCORING_NONE ||
1426 scoring_func == vllm::moe::SCORING_SIGMOID,
1427 "scoring_func must be SCORING_NONE (0) or SCORING_SIGMOID (1)");
1428
1429 // Always output float32 for topk_values (eliminates Python-side conversion)
1430 auto topk_values = torch::stable::new_empty(
1431 scores, {num_tokens, topk}, torch::headeronly::ScalarType::Float);
1432 auto topk_indices = torch::stable::new_empty(
1433 scores, {num_tokens, topk}, torch::headeronly::ScalarType::Int);
1434 const bool pdl_flag = num_tokens <= vllm::moe::PDLEnableTokens;
1435
1436 const torch::stable::accelerator::DeviceGuard device_guard(
1437 scores.get_device_index());
1438 const cudaStream_t stream =
1439 get_current_cuda_stream(scores.get_device_index());
1440 auto const sf = static_cast<vllm::moe::ScoringFunc>(scoring_func);
1441
1442 #define LAUNCH_KERNEL_SF(T, BiasT, IdxT) \
1443 do { \
1444 switch (sf) { \
1445 case vllm::moe::SCORING_NONE: \
1446 vllm::moe::invokeNoAuxTc<T, BiasT, IdxT, vllm::moe::SCORING_NONE>( \
1447 reinterpret_cast<T*>(scores.mutable_data_ptr()), \
1448 reinterpret_cast<float*>(topk_values.mutable_data_ptr()), \
1449 reinterpret_cast<IdxT*>(topk_indices.mutable_data_ptr()), \
1450 reinterpret_cast<BiasT const*>(bias.data_ptr()), num_tokens, \
1451 num_experts, n_group, topk_group, topk, renormalize, \
1452 routed_scaling_factor, pdl_flag, stream); \
1453 break; \
1454 case vllm::moe::SCORING_SIGMOID: \
1455 vllm::moe::invokeNoAuxTc<T, BiasT, IdxT, vllm::moe::SCORING_SIGMOID>( \
1456 reinterpret_cast<T*>(scores.mutable_data_ptr()), \
1457 reinterpret_cast<float*>(topk_values.mutable_data_ptr()), \
1458 reinterpret_cast<IdxT*>(topk_indices.mutable_data_ptr()), \
1459 reinterpret_cast<BiasT const*>(bias.data_ptr()), num_tokens, \
1460 num_experts, n_group, topk_group, topk, renormalize, \
1461 routed_scaling_factor, pdl_flag, stream); \
1462 break; \
1463 default: \
1464 STD_TORCH_CHECK(false, "Unsupported scoring_func"); \
1465 break; \
1466 } \
1467 } while (0)
1468
1469 #define LAUNCH_KERNEL(T, IdxT) \
1470 do { \
1471 switch (bias_type) { \
1472 case torch::headeronly::ScalarType::Half: \
1473 LAUNCH_KERNEL_SF(T, half, IdxT); \
1474 break; \
1475 case torch::headeronly::ScalarType::Float: \
1476 LAUNCH_KERNEL_SF(T, float, IdxT); \
1477 break; \
1478 case torch::headeronly::ScalarType::BFloat16: \
1479 LAUNCH_KERNEL_SF(T, __nv_bfloat16, IdxT); \
1480 break; \
1481 default: \
1482 STD_TORCH_CHECK( \
1483 false, \
1484 "Invalid bias dtype, only supports float16, float32, and " \
1485 "bfloat16"); \
1486 break; \
1487 } \
1488 } while (0)
1489
1490 switch (data_type) {
1491 case torch::headeronly::ScalarType::Half:
1492 // Handle Float16
1493 LAUNCH_KERNEL(half, int32_t);
1494 break;
1495 case torch::headeronly::ScalarType::Float:
1496 // Handle Float32
1497 LAUNCH_KERNEL(float, int32_t);
1498 break;
1499 case torch::headeronly::ScalarType::BFloat16:
1500 // Handle BFloat16
1501 LAUNCH_KERNEL(__nv_bfloat16, int32_t);
1502 break;
1503 default:
1504 // Handle other data types
1505 STD_TORCH_CHECK(
1506 false, "Invalid dtype, only supports float16, float32, and bfloat16");
1507 break;
1508 }
1509 #undef LAUNCH_KERNEL
1510 #undef LAUNCH_KERNEL_SF
1511 return {topk_values, topk_indices};
1512 }