| 1 | // Cooperative cluster TopK for DeepSeek V3 sparse attention indexer. |
| 2 | // See cooperative_topk.cuh for kernel implementation. |
| 3 | |
| 4 | #include <cuda_runtime.h> |
| 5 | |
| 6 | #include "torch_utils.h" |
| 7 | |
| 8 | #ifndef USE_ROCM |
| 9 | #include "cooperative_topk.cuh" |
| 10 | namespace ct = vllm::cooperative; |
| 11 | namespace hist4096 = vllm::topk_histogram_4096; |
| 12 | #endif |
| 13 | |
| 14 | #ifndef USE_ROCM |
| 15 | template <uint32_t TopK, uint32_t CS> |
| 16 | void launch_cooperative_cluster(ct::CooperativeTopKParams<TopK>& params, |
| 17 | size_t smem, cudaStream_t stream) { |
| 18 | auto kernel = []() { |
| 19 | if constexpr (CS == 2) { |
| 20 | return &ct::cooperative_topk_cs2<TopK>; |
| 21 | } else if constexpr (CS == 16) { |
| 22 | return &ct::cooperative_topk_cs16<TopK>; |
| 23 | } else if constexpr (CS == 8) { |
| 24 | return &ct::cooperative_topk_cs8<TopK>; |
| 25 | } else { |
| 26 | static_assert(CS == 4, "unsupported cooperative_topk cluster size"); |
| 27 | return &ct::cooperative_topk_cs4<TopK>; |
| 28 | } |
| 29 | }(); |
| 30 | if constexpr (CS > 8) { |
| 31 | cudaFuncSetAttribute(kernel, cudaFuncAttributeNonPortableClusterSizeAllowed, |
| 32 | 1); |
| 33 | } |
| 34 | cudaFuncSetAttribute(kernel, cudaFuncAttributeMaxDynamicSharedMemorySize, |
| 35 | smem); |
| 36 | |
| 37 | cudaLaunchConfig_t cfg = {}; |
| 38 | cfg.gridDim = dim3(params.num_rows, CS); |
| 39 | cfg.blockDim = dim3(hist4096::kBlockSize); |
| 40 | cfg.dynamicSmemBytes = smem; |
| 41 | cfg.stream = stream; |
| 42 | cudaLaunchAttribute attrs[1]; |
| 43 | attrs[0].id = cudaLaunchAttributeClusterDimension; |
| 44 | attrs[0].val.clusterDim = {1, CS, 1}; |
| 45 | cfg.numAttrs = 1; |
| 46 | cfg.attrs = attrs; |
| 47 | cudaError_t err = cudaLaunchKernelEx(&cfg, kernel, params); |
| 48 | STD_TORCH_CHECK(err == cudaSuccess, |
| 49 | "cooperative_topk launch failed: ", cudaGetErrorString(err)); |
| 50 | } |
| 51 | |
| 52 | template <uint32_t TopK> |
| 53 | void launch_cooperative_topk_impl(const torch::stable::Tensor& logits, |
| 54 | const torch::stable::Tensor& lengths, |
| 55 | torch::stable::Tensor& output, |
| 56 | torch::stable::Tensor& workspace, |
| 57 | int64_t max_seq_len) { |
| 58 | (void)max_seq_len; // Kept for signature parity with persistent_topk. |
| 59 | const int64_t num_rows = logits.size(0); |
| 60 | const cudaStream_t stream = get_current_cuda_stream(); |
| 61 | |
| 62 | const uint32_t stride = static_cast<uint32_t>(logits.stride(0)); |
| 63 | STD_TORCH_CHECK( |
| 64 | num_rows <= 64, |
| 65 | "cooperative_topk supports <=64 rows; use persistent_topk for " |
| 66 | "larger batches"); |
| 67 | |
| 68 | STD_TORCH_CHECK(stride % 4 == 0, |
| 69 | "cooperative_topk: stride must be multiple of 4 for TMA " |
| 70 | "alignment, got stride (max_model_len)=", |
| 71 | stride); |
| 72 | |
| 73 | STD_TORCH_CHECK(workspace.is_cuda(), "workspace must be CUDA tensor"); |
| 74 | STD_TORCH_CHECK( |
| 75 | workspace.scalar_type() == torch::headeronly::ScalarType::Byte, |
| 76 | "workspace must be uint8"); |
| 77 | |
| 78 | ct::CooperativeTopKParams<TopK> params; |
| 79 | params.input = logits.const_data_ptr<float>(); |
| 80 | params.output = output.mutable_data_ptr<int32_t>(); |
| 81 | params.lengths = lengths.const_data_ptr<int32_t>(); |
| 82 | params.num_rows = static_cast<uint32_t>(num_rows); |
| 83 | params.stride = stride; |
| 84 | params.tie_ws = |
| 85 | reinterpret_cast<hist4096::Tie*>(workspace.mutable_data_ptr<uint8_t>()); |
| 86 | |
| 87 | constexpr uint32_t kTieWsPerRow = |
| 88 | TopK <= hist4096::kBlockSize ? hist4096::kMaxTies : TopK; |
| 89 | STD_TORCH_CHECK( |
| 90 | workspace.size(0) >= |
| 91 | static_cast<int64_t>(num_rows * kTieWsPerRow * sizeof(hist4096::Tie)), |
| 92 | "workspace too small"); |
| 93 | |
| 94 | const bool supports_cluster16 = get_device_prop()->major >= 10; |
| 95 | if (num_rows <= 4 && supports_cluster16) { |
| 96 | launch_cooperative_cluster<TopK, 16>(params, ct::kSmemSize8, stream); |
| 97 | } else if (num_rows <= 8) { |
| 98 | launch_cooperative_cluster<TopK, 8>(params, ct::kSmemSize8, stream); |
| 99 | } else if (num_rows <= 33) { |
| 100 | launch_cooperative_cluster<TopK, 4>(params, ct::kSmemSize4, stream); |
| 101 | } else { |
| 102 | launch_cooperative_cluster<TopK, 2>(params, ct::kSmemSize2, stream); |
| 103 | } |
| 104 | } |
| 105 | #endif // USE_ROCM |
| 106 | |
| 107 | void cooperative_topk(const torch::stable::Tensor& logits, |
| 108 | const torch::stable::Tensor& lengths, |
| 109 | torch::stable::Tensor& output, |
| 110 | torch::stable::Tensor& workspace, int64_t k, |
| 111 | int64_t max_seq_len) { |
| 112 | #ifndef USE_ROCM |
| 113 | STD_TORCH_CHECK(logits.is_cuda(), "logits must be CUDA tensor"); |
| 114 | STD_TORCH_CHECK(lengths.is_cuda(), "lengths must be CUDA tensor"); |
| 115 | STD_TORCH_CHECK(output.is_cuda(), "output must be CUDA tensor"); |
| 116 | STD_TORCH_CHECK(logits.scalar_type() == torch::headeronly::ScalarType::Float, |
| 117 | "Only float32 supported"); |
| 118 | STD_TORCH_CHECK(lengths.scalar_type() == torch::headeronly::ScalarType::Int, |
| 119 | "lengths must be int32"); |
| 120 | STD_TORCH_CHECK(output.scalar_type() == torch::headeronly::ScalarType::Int, |
| 121 | "output must be int32"); |
| 122 | STD_TORCH_CHECK(logits.dim() == 2, "logits must be 2D"); |
| 123 | STD_TORCH_CHECK(lengths.dim() == 1 || lengths.dim() == 2, |
| 124 | "lengths must be 1D or 2D"); |
| 125 | STD_TORCH_CHECK(lengths.is_contiguous(), "lengths must be contiguous"); |
| 126 | STD_TORCH_CHECK(output.dim() == 2, "output must be 2D"); |
| 127 | const int64_t num_rows = logits.size(0); |
| 128 | STD_TORCH_CHECK(lengths.numel() == num_rows, "lengths size mismatch"); |
| 129 | STD_TORCH_CHECK(output.size(0) == num_rows && output.size(1) == k, |
| 130 | "output size mismatch"); |
| 131 | STD_TORCH_CHECK( |
| 132 | k == 512 || k == 1024 || k == 2048, |
| 133 | "cooperative_topk supports k=512, k=1024, or k=2048, got k=", k); |
| 134 | |
| 135 | if (k == 512) { |
| 136 | launch_cooperative_topk_impl<512>(logits, lengths, output, workspace, |
| 137 | max_seq_len); |
| 138 | } else if (k == 1024) { |
| 139 | launch_cooperative_topk_impl<1024>(logits, lengths, output, workspace, |
| 140 | max_seq_len); |
| 141 | } else { |
| 142 | launch_cooperative_topk_impl<2048>(logits, lengths, output, workspace, |
| 143 | max_seq_len); |
| 144 | } |
| 145 | #else |
| 146 | STD_TORCH_CHECK(false, "cooperative_topk is not supported on ROCm"); |
| 147 | #endif |
| 148 | } |