A high-throughput and memory-efficient inference and serving engine for LLMs
Mirrored from https://github.com/tduncan-lum/Nvidia-Optimization-Stack---VLLM.git
Synced Nov 10, 05:15 UTC
[Perf] Optimize grouped topk kernel, 1.2%~2% E2E Throughput improvement (#32058)
Signed-off-by: yewentao256 <zhyanwentao@126.com>
Wentao Ye committed
Jan 13, 2026 at 13:58 UTC
f28125d87b6a5976a1584267077d4f0287ce6ce2
2 files changed
+186
-283
csrc/moe/grouped_topk_kernels.cu
+181
-283
@@ -31,8 +31,6 @@ namespace moe {
31
32
constexpr unsigned FULL_WARP_MASK = 0xffffffff;
33
constexpr int32_t WARP_SIZE = 32;
34
-constexpr int32_t BLOCK_SIZE = 512;
35
-constexpr int32_t NUM_WARPS_PER_BLOCK = BLOCK_SIZE / WARP_SIZE;
34
35
namespace warp_topk {
36
@@ -65,14 +63,6 @@ __forceinline__ __device__ bool is_better_than(T val, T baseline, idxT index,
63
return res;
64
}
65
68
-template <typename T, typename idxT>
69
-int calc_smem_size_for_block_wide(int num_of_warp, int64_t k) {
70
- int64_t cache_topk = (sizeof(T) + sizeof(idxT)) * num_of_warp * k;
71
- int64_t n = std::max<int>(num_of_warp / 2 * k, num_of_warp * WARP_SIZE);
72
- return max(cache_topk,
73
- round_up_to_multiple_of<256>(n * sizeof(T)) + n * sizeof(idxT));
74
-}
75
-
66
template <int size, bool ascending, bool reverse, typename T, typename idxT,
67
bool is_stable>
68
struct BitonicMerge {
@@ -267,6 +257,15 @@ class WarpSort {
257
}
258
}
259
260
+ // Accessors for per-lane selected value/index.
261
+ // NOTE: For the common case `capacity == WARP_SIZE`, `max_arr_len_ == 1`
262
+ // and callers should use `i == 0`.
263
+ __device__ __forceinline__ idxT get_idx(int i = 0) const {
264
+ return idx_arr_[i];
265
+ }
266
+
267
+ __device__ __forceinline__ T get_val(int i = 0) const { return val_arr_[i]; }
268
+
269
protected:
270
static constexpr int max_arr_len_ = capacity / WARP_SIZE;
271
@@ -285,6 +284,7 @@ class WarpSelect : public WarpSort<capacity, greater, T, idxT, is_stable> {
284
__device__ WarpSelect(idxT k, T dummy)
285
: WarpSort<capacity, greater, T, idxT, is_stable>(k, dummy),
286
k_th_(dummy),
287
+ k_th_idx_(0),
288
k_th_lane_((k - 1) % WARP_SIZE) {
289
extern __shared__ char smem_buf[]; // extern __shared__ T smem_buf[];
290
@@ -346,9 +346,6 @@ class WarpSelect : public WarpSort<capacity, greater, T, idxT, is_stable> {
346
idxT idx = (lane_ < smem_buf_len_) ? idx_smem_[lane_] : 0;
347
merge_buf_(val, idx);
348
}
349
-
350
- // after done(), smem is used for merging results among warps
351
- __syncthreads();
349
}
350
351
private:
@@ -503,255 +500,186 @@ __device__ void topk_with_k2(T* output, T const* input, BiasT const* bias,
500
}
501
}
502
506
-template <typename T, typename BiasT, ScoringFunc SF>
507
-__global__ void topk_with_k2_kernel(T* output, T* input, BiasT const* bias,
508
- int64_t const num_tokens,
509
- int64_t const num_cases,
510
- int64_t const n_group,
511
- int64_t const num_experts_per_group) {
512
- int32_t warp_id = threadIdx.x / WARP_SIZE;
513
- int32_t lane_id = threadIdx.x % WARP_SIZE;
514
-
515
- int32_t case_id = blockIdx.x * NUM_WARPS_PER_BLOCK + warp_id;
516
- if (case_id < num_cases) {
517
- input += case_id * num_experts_per_group;
518
- // bias is per expert group, offset to current group
519
- int32_t group_id = case_id % n_group;
520
- BiasT const* group_bias = bias + group_id * num_experts_per_group;
521
- output += case_id;
522
-
523
- cg::thread_block block = cg::this_thread_block();
524
- cg::thread_block_tile<32> tile = cg::tiled_partition<32>(block);
503
+template <typename T, typename BiasT, typename IdxT, ScoringFunc SF>
504
+__global__ void grouped_topk_fused_kernel(
505
+ T* scores, float* topk_values, IdxT* topk_indices, BiasT const* bias,
506
+ int64_t const num_tokens, int64_t const num_experts, int64_t const n_group,
507
+ int64_t const topk_group, int64_t const topk, bool renormalize,
508
+ double routed_scaling_factor) {
509
+ int32_t const token_id = static_cast<int32_t>(blockIdx.x);
510
+ if (token_id >= num_tokens) {
511
+ return;
512
+ }
513
526
-#if (defined(__CUDA_ARCH__) && (__CUDA_ARCH__ >= 900))
527
- asm volatile("griddepcontrol.wait;");
528
-#endif
529
- topk_with_k2<T, BiasT, SF>(output, input, group_bias, tile, lane_id,
530
- num_experts_per_group);
514
+ int32_t const warp_id = threadIdx.x / WARP_SIZE;
515
+ int32_t const lane_id = threadIdx.x % WARP_SIZE;
516
+
517
+ int32_t const n_group_i32 = static_cast<int32_t>(n_group);
518
+ int32_t const topk_group_i32 = static_cast<int32_t>(topk_group);
519
+ int32_t const topk_i32 = static_cast<int32_t>(topk);
520
+ int32_t const num_experts_i32 = static_cast<int32_t>(num_experts);
521
+
522
+ int32_t const num_warps = blockDim.x / WARP_SIZE;
523
+ if (warp_id >= n_group_i32 || num_warps < n_group_i32) {
524
+ return;
525
}
532
-#if (defined(__CUDA_ARCH__) && (__CUDA_ARCH__ >= 900))
533
- asm volatile("griddepcontrol.launch_dependents;");
534
-#endif
535
-}
526
537
-template <typename T, typename BiasT, typename IdxT, ScoringFunc SF,
538
- int NGroup = -1>
539
-__global__ void group_idx_and_topk_idx_kernel(
540
- T* scores, T const* group_scores, float* topk_values, IdxT* topk_indices,
541
- BiasT const* bias, int64_t const num_tokens, int64_t const n_group,
542
- int64_t const topk_group, int64_t const topk, int64_t const num_experts,
543
- int64_t const num_experts_per_group, bool renormalize,
544
- double routed_scaling_factor) {
545
- int32_t warp_id = threadIdx.x / WARP_SIZE;
546
- int32_t lane_id = threadIdx.x % WARP_SIZE;
547
- int32_t case_id =
548
- blockIdx.x * NUM_WARPS_PER_BLOCK + warp_id; // one per token
549
- scores += case_id * num_experts;
550
- group_scores += case_id * n_group;
551
- topk_values += case_id * topk;
552
- topk_indices += case_id * topk;
553
-
554
- constexpr bool kUseStaticNGroup = (NGroup > 0);
555
- // use int32 to avoid implicit conversion
556
- int32_t const n_group_i32 =
557
- kUseStaticNGroup ? NGroup : static_cast<int32_t>(n_group);
558
-
559
- int32_t align_num_experts_per_group =
560
- warp_topk::round_up_to_multiple_of<WARP_SIZE>(num_experts_per_group);
527
+ int32_t const num_experts_per_group = num_experts_i32 / n_group_i32;
528
+
529
+ T* scores_token = scores + static_cast<int64_t>(token_id) * num_experts;
530
531
cg::thread_block block = cg::this_thread_block();
532
cg::thread_block_tile<32> tile = cg::tiled_partition<32>(block);
533
565
- extern __shared__ char smem_buf[]; // NOTE: reuse the shared memory here to
566
- // store the target topk idx
567
- int32_t* s_topk_idx = reinterpret_cast<int32_t*>(smem_buf);
568
- T* s_topk_value =
569
- reinterpret_cast<T*>(s_topk_idx + NUM_WARPS_PER_BLOCK * topk) +
570
- warp_id * topk;
571
- s_topk_idx += warp_id * topk;
572
-
573
- T value = neg_inf<T>();
574
- T topk_group_value = neg_inf<T>();
575
- int32_t num_equalto_topkth_group;
534
+ extern __shared__ char smem_buf[];
535
+ // warpSelect internal staging buffer layout
536
+ size_t const val_bytes =
537
+ static_cast<size_t>(num_warps) * WARP_SIZE * sizeof(T);
538
+ size_t const val_bytes_aligned =
539
+ warp_topk::round_up_to_multiple_of<256>(val_bytes);
540
+ size_t const idx_bytes =
541
+ static_cast<size_t>(num_warps) * WARP_SIZE * sizeof(int32_t);
542
+ size_t const internal_bytes = val_bytes_aligned + idx_bytes;
543
+
544
+ // user-managed shared memory starts after warpSelect internal staging.
545
+ uintptr_t ptr_u = reinterpret_cast<uintptr_t>(smem_buf + internal_bytes);
546
+ ptr_u = (ptr_u + 15) & ~static_cast<uintptr_t>(15); // align to 16B
547
+ T* s_group_scores = reinterpret_cast<T*>(ptr_u);
548
549
#if (defined(__CUDA_ARCH__) && (__CUDA_ARCH__ >= 900))
550
asm volatile("griddepcontrol.wait;"); // I think all prolog can be put before
551
// acqbulk because it's ptr arithmetic
552
#endif
553
582
- if (case_id < num_tokens) {
583
- // calculate group_idx
584
- int32_t target_num_min =
585
- WARP_SIZE - n_group_i32 + static_cast<int32_t>(topk_group);
586
- // The check is necessary to avoid abnormal input
587
- if (lane_id < n_group_i32 && is_finite(group_scores[lane_id])) {
588
- value = group_scores[lane_id];
589
- }
554
+ // phase 1: per-group scan
555
+ int32_t const group_offset = warp_id * num_experts_per_group;
556
+ topk_with_k2<T, BiasT, SF>(s_group_scores + warp_id,
557
+ scores_token + group_offset, bias + group_offset,
558
+ tile, lane_id, num_experts_per_group);
559
591
- int count_equal_to_top_value = WARP_SIZE - n_group_i32;
592
- int pre_count_equal_to_top_value = 0;
593
- // Use loop to find the largset top_group
594
- while (count_equal_to_top_value < target_num_min) {
595
- topk_group_value = cg::reduce(tile, value, cg::greater<T>());
596
- if (value == topk_group_value) {
597
- value = neg_inf<T>();
598
- }
599
- pre_count_equal_to_top_value = count_equal_to_top_value;
600
- count_equal_to_top_value =
601
- __popc(__ballot_sync(FULL_WARP_MASK, (value == neg_inf<T>())));
602
- }
603
- num_equalto_topkth_group = target_num_min - pre_count_equal_to_top_value;
604
- }
560
__syncthreads();
561
562
+ // phase 2: warp0 selects groups + merges candidates to final topk
563
+ if (warp_id != 0) {
564
+ return;
565
+ }
566
+
567
+ topk_values += static_cast<int64_t>(token_id) * topk;
568
+ topk_indices += static_cast<int64_t>(token_id) * topk;
569
+
570
+ // select topk_group groups by group score
571
warp_topk::WarpSelect</*capability*/ WARP_SIZE, /*greater*/ true, T, int32_t,
572
/* is_stable */ true>
609
- queue((int32_t)topk, neg_inf<T>());
610
-
611
- int count_equalto_topkth_group = 0;
612
- bool if_proceed_next_topk = topk_group_value != neg_inf<T>();
613
- if (case_id < num_tokens && if_proceed_next_topk) {
614
- auto process_group = [&](int i_group) {
615
- if ((group_scores[i_group] > topk_group_value) ||
616
- ((group_scores[i_group] == topk_group_value) &&
617
- (count_equalto_topkth_group < num_equalto_topkth_group))) {
618
- int32_t offset = i_group * num_experts_per_group;
619
- for (int32_t i = lane_id; i < align_num_experts_per_group;
620
- i += WARP_SIZE) {
621
- T candidates = neg_inf<T>();
622
- if (i < num_experts_per_group) {
623
- // apply scoring function (if any) and add bias
624
- T input = scores[offset + i];
625
- if (is_finite(input)) {
626
- T score = apply_scoring<SF>(input);
627
- candidates = score + static_cast<T>(bias[offset + i]);
628
- }
629
- }
630
- queue.add(candidates, offset + i);
631
- }
632
- if (group_scores[i_group] == topk_group_value) {
633
- count_equalto_topkth_group++;
634
- }
635
- }
636
- };
573
+ group_sel(static_cast<int32_t>(topk_group_i32), neg_inf<T>());
574
+
575
+ // all lanes must participate in WarpSelect::add().
576
+ T gscore = (lane_id < n_group_i32) ? s_group_scores[lane_id] : neg_inf<T>();
577
+ group_sel.add(gscore, lane_id);
578
+ group_sel.done();
579
+
580
+ // proceed only if the k-th selected group score is not -inf
581
+ bool proceed = false;
582
+ if (topk_group_i32 > 0) {
583
+ int const kth_lane = topk_group_i32 - 1;
584
+ // broadcast the k-th selected group score to all lanes
585
+ T kth_val = __shfl_sync(FULL_WARP_MASK, group_sel.get_val(0), kth_lane);
586
+ proceed = (kth_val != neg_inf<T>());
587
+ }
588
638
- if constexpr (kUseStaticNGroup) {
639
-#pragma unroll
640
- for (int i_group = 0; i_group < NGroup; ++i_group) {
641
- process_group(i_group);
642
- }
643
- } else {
644
- for (int i_group = 0; i_group < n_group_i32; ++i_group) {
645
- process_group(i_group);
646
- }
589
+ if (!proceed) {
590
+ for (int i = lane_id; i < topk_i32; i += WARP_SIZE) {
591
+ topk_indices[i] = static_cast<IdxT>(i);
592
+ topk_values[i] = 1.0f / static_cast<float>(topk_i32);
593
}
648
- queue.done();
649
- // Get the topk_idx
650
- queue.dumpIdx(s_topk_idx);
651
- }
652
-
653
- // Load the valid score value
654
- // Calculate the summation
655
- float topk_sum = 1e-20;
656
- if (case_id < num_tokens && if_proceed_next_topk) {
657
- for (int i = lane_id;
658
- i < warp_topk::round_up_to_multiple_of<WARP_SIZE>(topk);
659
- i += WARP_SIZE) {
660
- T value = cuda_cast<T, float>(0.0f);
661
- if (i < topk) {
662
- // Load the score value (without bias) for normalization
663
- T input = scores[s_topk_idx[i]];
664
- value = apply_scoring<SF>(input);
665
- s_topk_value[i] = value;
666
- }
667
- if (renormalize) {
668
- topk_sum +=
669
- cg::reduce(tile, cuda_cast<float, T>(value), cg::plus<float>());
594
+#if (defined(__CUDA_ARCH__) && (__CUDA_ARCH__ >= 900))
595
+ asm volatile("griddepcontrol.launch_dependents;");
596
+#endif
597
+ return;
598
+ }
599
+
600
+ // merge per-group topk candidates for selected groups, then select topk
601
+ warp_topk::WarpSelect</*capability*/ WARP_SIZE, /*greater*/ true, T, int32_t,
602
+ /* is_stable */ true>
603
+ expert_sel(static_cast<int32_t>(topk_i32), neg_inf<T>());
604
+
605
+ // selected group ids reside in lanes [0, topk_group)
606
+ int32_t sel_gid_lane = (lane_id < topk_group_i32) ? group_sel.get_idx(0) : 0;
607
+
608
+ // add candidates from selected groups to expert_sel
609
+ for (int32_t g = 0; g < topk_group_i32; ++g) {
610
+ int32_t gid = __shfl_sync(FULL_WARP_MASK, sel_gid_lane, g);
611
+ int32_t const offset = gid * num_experts_per_group;
612
+ int32_t const align_num_experts_per_group =
613
+ warp_topk::round_up_to_multiple_of<WARP_SIZE>(num_experts_per_group);
614
+ for (int32_t i = lane_id; i < align_num_experts_per_group; i += WARP_SIZE) {
615
+ // all lanes must call `add()` the same number of times.
616
+ T cand = neg_inf<T>();
617
+ int32_t idx = 0;
618
+ if (i < num_experts_per_group) {
619
+ idx = offset + i;
620
+ T input = scores_token[idx];
621
+ if (is_finite(input)) {
622
+ T score = apply_scoring<SF>(input);
623
+ cand = score + static_cast<T>(bias[idx]);
624
+ }
625
}
626
+ expert_sel.add(cand, idx);
627
}
628
}
629
+ expert_sel.done();
630
+
631
+ // compute unbiased routing weights + optional renorm.
632
+ float lane_unbiased = 0.0f;
633
+ IdxT lane_idx = 0;
634
+ if (lane_id < topk_i32) {
635
+ lane_idx = static_cast<IdxT>(expert_sel.get_idx(0));
636
+ T in = scores_token[static_cast<int32_t>(lane_idx)];
637
+ lane_unbiased = cuda_cast<float, T>(apply_scoring<SF>(in));
638
+ }
639
674
- __syncthreads();
640
+ float topk_sum = 1e-20f;
641
+ if (renormalize) {
642
+ topk_sum += cg::reduce(tile, lane_unbiased, cg::plus<float>());
643
+ }
644
676
- if (case_id < num_tokens) {
677
- if (if_proceed_next_topk) {
678
- float scale = routed_scaling_factor;
679
- if (renormalize) {
680
- scale /= topk_sum;
681
- }
682
- for (int i = lane_id; i < topk; i += WARP_SIZE) {
683
- float base = cuda_cast<float, T>(s_topk_value[i]);
684
- float value = base * scale;
685
- topk_indices[i] = s_topk_idx[i];
686
- topk_values[i] = value;
687
- }
688
- } else {
689
- for (int i = lane_id; i < topk; i += WARP_SIZE) {
690
- topk_indices[i] = i;
691
- topk_values[i] = 1.0f / topk;
692
- }
693
- }
694
- // Note: when if_proceed_next_topk==false, choose the first 8 experts as the
695
- // default result.
645
+ float scale = static_cast<float>(routed_scaling_factor);
646
+ if (renormalize) {
647
+ scale /= topk_sum;
648
+ }
649
+
650
+ if (lane_id < topk_i32) {
651
+ topk_indices[lane_id] = lane_idx;
652
+ topk_values[lane_id] = lane_unbiased * scale;
653
}
654
+
655
#if (defined(__CUDA_ARCH__) && (__CUDA_ARCH__ >= 900))
656
asm volatile("griddepcontrol.launch_dependents;");
657
#endif
658
}
659
702
-template <typename T, typename BiasT, typename IdxT, ScoringFunc SF>
703
-inline void launch_group_idx_and_topk_kernel(
704
- cudaLaunchConfig_t const& config, T* scores, T* group_scores,
705
- float* topk_values, IdxT* topk_indices, BiasT const* bias,
706
- int64_t const num_tokens, int64_t const n_group, int64_t const topk_group,
707
- int64_t const topk, int64_t const num_experts,
708
- int64_t const num_experts_per_group, bool const renormalize,
709
- double const routed_scaling_factor) {
710
- auto launch = [&](auto* kernel_instance2) {
711
- cudaLaunchKernelEx(&config, kernel_instance2, scores, group_scores,
712
- topk_values, topk_indices, bias, num_tokens, n_group,
713
- topk_group, topk, num_experts, num_experts_per_group,
714
- renormalize, routed_scaling_factor);
715
- };
716
-
717
- switch (n_group) {
718
- case 4: {
719
- launch(&group_idx_and_topk_idx_kernel<T, BiasT, IdxT, SF, 4>);
720
- break;
721
- }
722
- case 8: {
723
- launch(&group_idx_and_topk_idx_kernel<T, BiasT, IdxT, SF, 8>);
724
- break;
725
- }
726
- case 16: {
727
- launch(&group_idx_and_topk_idx_kernel<T, BiasT, IdxT, SF, 16>);
728
- break;
729
- }
730
- case 32: {
731
- launch(&group_idx_and_topk_idx_kernel<T, BiasT, IdxT, SF, 32>);
732
- break;
733
- }
734
- default: {
735
- launch(&group_idx_and_topk_idx_kernel<T, BiasT, IdxT, SF>);
736
- break;
737
- }
738
- }
739
-}
740
-
660
template <typename T, typename BiasT, typename IdxT>
742
-void invokeNoAuxTc(T* scores, T* group_scores, float* topk_values,
743
- IdxT* topk_indices, BiasT const* bias,
744
- int64_t const num_tokens, int64_t const num_experts,
745
- int64_t const n_group, int64_t const topk_group,
746
- int64_t const topk, bool const renormalize,
747
- double const routed_scaling_factor, int const scoring_func,
748
- bool enable_pdl = false, cudaStream_t const stream = 0) {
749
- int64_t num_cases = num_tokens * n_group;
750
- int64_t topk_with_k2_num_blocks = (num_cases - 1) / NUM_WARPS_PER_BLOCK + 1;
661
+void invokeNoAuxTc(T* scores, float* topk_values, IdxT* topk_indices,
662
+ BiasT const* bias, int64_t const num_tokens,
663
+ int64_t const num_experts, int64_t const n_group,
664
+ int64_t const topk_group, int64_t const topk,
665
+ bool const renormalize, double const routed_scaling_factor,
666
+ int const scoring_func, bool enable_pdl = false,
667
+ cudaStream_t const stream = 0) {
668
cudaLaunchConfig_t config;
752
- config.gridDim = topk_with_k2_num_blocks;
753
- config.blockDim = BLOCK_SIZE;
754
- config.dynamicSmemBytes = 0;
669
+ // One block per token; one warp per group.
670
+ config.gridDim = static_cast<uint32_t>(num_tokens);
671
+ config.blockDim = static_cast<uint32_t>(n_group) * WARP_SIZE;
672
+ // Dynamic shared memory: WarpSelect staging + per-group topk buffers.
673
+ int32_t const num_warps = static_cast<int32_t>(n_group);
674
+ size_t const val_bytes =
675
+ static_cast<size_t>(num_warps) * WARP_SIZE * sizeof(T);
676
+ size_t const val_bytes_aligned =
677
+ warp_topk::round_up_to_multiple_of<256>(val_bytes);
678
+ size_t const idx_bytes =
679
+ static_cast<size_t>(num_warps) * WARP_SIZE * sizeof(int32_t);
680
+ size_t const internal_bytes = val_bytes_aligned + idx_bytes;
681
+ size_t const extra_bytes = 16 + static_cast<size_t>(n_group) * sizeof(T);
682
+ config.dynamicSmemBytes = internal_bytes + extra_bytes;
683
config.stream = stream;
684
cudaLaunchAttribute attrs[1];
685
attrs[0].id = cudaLaunchAttributeProgrammaticStreamSerialization;
@@ -759,66 +687,35 @@ void invokeNoAuxTc(T* scores, T* group_scores, float* topk_values,
687
config.numAttrs = 1;
688
config.attrs = attrs;
689
auto const sf = static_cast<ScoringFunc>(scoring_func);
762
- int64_t const num_experts_per_group = num_experts / n_group;
763
- auto launch_topk_with_k2 = [&](auto* kernel_instance1) {
764
- cudaLaunchKernelEx(&config, kernel_instance1, group_scores, scores, bias,
765
- num_tokens, num_cases, n_group, num_experts_per_group);
766
- };
690
switch (sf) {
691
case SCORING_NONE: {
769
- auto* kernel_instance1 = &topk_with_k2_kernel<T, BiasT, SCORING_NONE>;
770
- launch_topk_with_k2(kernel_instance1);
771
- break;
692
+ auto* kernel_instance =
693
+ &grouped_topk_fused_kernel<T, BiasT, IdxT, SCORING_NONE>;
694
+ cudaLaunchKernelEx(&config, kernel_instance, scores, topk_values,
695
+ topk_indices, bias, num_tokens, num_experts, n_group,
696
+ topk_group, topk, renormalize, routed_scaling_factor);
697
+ return;
698
}
699
case SCORING_SIGMOID: {
774
- auto* kernel_instance1 = &topk_with_k2_kernel<T, BiasT, SCORING_SIGMOID>;
775
- launch_topk_with_k2(kernel_instance1);
776
- break;
700
+ auto* kernel_instance =
701
+ &grouped_topk_fused_kernel<T, BiasT, IdxT, SCORING_SIGMOID>;
702
+ cudaLaunchKernelEx(&config, kernel_instance, scores, topk_values,
703
+ topk_indices, bias, num_tokens, num_experts, n_group,
704
+ topk_group, topk, renormalize, routed_scaling_factor);
705
+ return;
706
}
707
default:
708
// should be guarded by higher level checks.
709
TORCH_CHECK(false, "Unsupported scoring_func in invokeNoAuxTc");
710
}
782
-
783
- int64_t topk_with_k_group_num_blocks =
784
- (num_tokens - 1) / NUM_WARPS_PER_BLOCK + 1;
785
- size_t dynamic_smem_in_bytes =
786
- warp_topk::calc_smem_size_for_block_wide<T, int32_t>(NUM_WARPS_PER_BLOCK,
787
- topk);
788
- config.gridDim = topk_with_k_group_num_blocks;
789
- config.blockDim = BLOCK_SIZE;
790
- config.dynamicSmemBytes = dynamic_smem_in_bytes;
791
- config.stream = stream;
792
- attrs[0].id = cudaLaunchAttributeProgrammaticStreamSerialization;
793
- attrs[0].val.programmaticStreamSerializationAllowed = enable_pdl;
794
- config.numAttrs = 1;
795
- config.attrs = attrs;
796
- switch (sf) {
797
- case SCORING_NONE: {
798
- launch_group_idx_and_topk_kernel<T, BiasT, IdxT, SCORING_NONE>(
799
- config, scores, group_scores, topk_values, topk_indices, bias,
800
- num_tokens, n_group, topk_group, topk, num_experts,
801
- num_experts_per_group, renormalize, routed_scaling_factor);
802
- break;
803
- }
804
- case SCORING_SIGMOID: {
805
- launch_group_idx_and_topk_kernel<T, BiasT, IdxT, SCORING_SIGMOID>(
806
- config, scores, group_scores, topk_values, topk_indices, bias,
807
- num_tokens, n_group, topk_group, topk, num_experts,
808
- num_experts_per_group, renormalize, routed_scaling_factor);
809
- break;
810
- }
811
- default:
812
- TORCH_CHECK(false, "Unsupported scoring_func in invokeNoAuxTc");
813
- }
711
}
712
816
-#define INSTANTIATE_NOAUX_TC(T, BiasT, IdxT) \
817
- template void invokeNoAuxTc<T, BiasT, IdxT>( \
818
- T * scores, T * group_scores, float* topk_values, IdxT* topk_indices, \
819
- BiasT const* bias, int64_t const num_tokens, int64_t const num_experts, \
820
- int64_t const n_group, int64_t const topk_group, int64_t const topk, \
821
- bool const renormalize, double const routed_scaling_factor, \
713
+#define INSTANTIATE_NOAUX_TC(T, BiasT, IdxT) \
714
+ template void invokeNoAuxTc<T, BiasT, IdxT>( \
715
+ T * scores, float* topk_values, IdxT* topk_indices, BiasT const* bias, \
716
+ int64_t const num_tokens, int64_t const num_experts, \
717
+ int64_t const n_group, int64_t const topk_group, int64_t const topk, \
718
+ bool const renormalize, double const routed_scaling_factor, \
719
int const scoring_func, bool enable_pdl, cudaStream_t const stream);
720
721
INSTANTIATE_NOAUX_TC(float, float, int32_t);
@@ -843,17 +740,21 @@ std::tuple<torch::Tensor, torch::Tensor> grouped_topk(
740
int64_t num_tokens = input_size[0];
741
int64_t num_experts = input_size[1];
742
TORCH_CHECK(input_size.size() == 2, "scores must be a 2D Tensor");
743
+ TORCH_CHECK(n_group > 0, "n_group must be positive");
744
+ TORCH_CHECK(topk > 0, "topk must be positive");
745
+ TORCH_CHECK(topk_group > 0, "topk_group must be positive");
746
+ TORCH_CHECK(topk_group <= n_group, "topk_group must be <= n_group");
747
TORCH_CHECK(num_experts % n_group == 0,
748
"num_experts should be divisible by n_group");
749
TORCH_CHECK(n_group <= 32,
750
"n_group should be smaller than or equal to 32 for now");
751
TORCH_CHECK(topk <= 32, "topk should be smaller than or equal to 32 for now");
752
+ TORCH_CHECK(topk <= topk_group * (num_experts / n_group),
753
+ "topk must be <= topk_group * (num_experts / n_group)");
754
TORCH_CHECK(scoring_func == vllm::moe::SCORING_NONE ||
755
scoring_func == vllm::moe::SCORING_SIGMOID,
756
"scoring_func must be SCORING_NONE (0) or SCORING_SIGMOID (1)");
757
855
- torch::Tensor group_scores = torch::empty(
856
- {num_tokens, n_group}, torch::dtype(data_type).device(torch::kCUDA));
758
// Always output float32 for topk_values (eliminates Python-side conversion)
759
torch::Tensor topk_values = torch::empty(
760
{num_tokens, topk}, torch::dtype(torch::kFloat32).device(torch::kCUDA));
@@ -868,7 +769,6 @@ std::tuple<torch::Tensor, torch::Tensor> grouped_topk(
769
case torch::kFloat16: \
770
vllm::moe::invokeNoAuxTc<T, half, IdxT>( \
771
reinterpret_cast<T*>(scores.mutable_data_ptr()), \
871
- reinterpret_cast<T*>(group_scores.mutable_data_ptr()), \
772
reinterpret_cast<float*>(topk_values.mutable_data_ptr()), \
773
reinterpret_cast<IdxT*>(topk_indices.mutable_data_ptr()), \
774
reinterpret_cast<half const*>(bias.data_ptr()), num_tokens, \
@@ -879,7 +779,6 @@ std::tuple<torch::Tensor, torch::Tensor> grouped_topk(
779
case torch::kFloat32: \
780
vllm::moe::invokeNoAuxTc<T, float, IdxT>( \
781
reinterpret_cast<T*>(scores.mutable_data_ptr()), \
882
- reinterpret_cast<T*>(group_scores.mutable_data_ptr()), \
782
reinterpret_cast<float*>(topk_values.mutable_data_ptr()), \
783
reinterpret_cast<IdxT*>(topk_indices.mutable_data_ptr()), \
784
reinterpret_cast<float const*>(bias.data_ptr()), num_tokens, \
@@ -890,7 +789,6 @@ std::tuple<torch::Tensor, torch::Tensor> grouped_topk(
789
case torch::kBFloat16: \
790
vllm::moe::invokeNoAuxTc<T, __nv_bfloat16, IdxT>( \
791
reinterpret_cast<T*>(scores.mutable_data_ptr()), \
893
- reinterpret_cast<T*>(group_scores.mutable_data_ptr()), \
792
reinterpret_cast<float*>(topk_values.mutable_data_ptr()), \
793
reinterpret_cast<IdxT*>(topk_indices.mutable_data_ptr()), \
794
reinterpret_cast<__nv_bfloat16 const*>(bias.data_ptr()), \
tests/models/utils.py
+5
@@ -454,6 +454,9 @@ def dummy_hf_overrides(
454
# Ensure at least 2 expert per group
455
# Since `grouped_topk` assumes top-2
456
n_group = getattr(text_config, "n_group", None)
457
+ # Kimi uses `num_expert_group` instead of `n_group`.
458
+ if n_group is None:
459
+ n_group = getattr(text_config, "num_expert_group", None)
460
num_experts = n_group * 2 if n_group is not None else 2
461
462
# we use three layers for Gemma-3n to check
@@ -487,6 +490,8 @@ def dummy_hf_overrides(
490
{
491
"num_experts": num_experts,
492
"num_experts_per_tok": 2,
493
+ # Kimi uses `num_experts_per_token`.
494
+ "num_experts_per_token": 2,
495
"num_local_experts": num_experts,
496
# Otherwise there will not be any expert layers
497
"first_k_dense_replace": 0,