main
cu 299 lines 11.9 KB
Raw
1 /*
2 Copyright (c) 2025, NVIDIA CORPORATION. All rights reserved.
3 Copyright 2025 SGLang Team. All Rights Reserved.
4
5 Licensed under the Apache License, Version 2.0 (the "License");
6 you may not use this file except in compliance with the License.
7 You may obtain a copy of the License at
8
9 http://www.apache.org/licenses/LICENSE-2.0
10
11 Unless required by applicable law or agreed to in writing, software
12 distributed under the License is distributed on an "AS IS" BASIS,
13 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 See the License for the specific language governing permissions and
15 limitations under the License.
16 ==============================================================================*/
17 /*
18 * Taken from SGLANG PR https://github.com/sgl-project/sglang/pull/6929
19 * by Alcanderian JieXin Liang
20 */
21 #include "libtorch_stable/torch_utils.h"
22
23 #include <torch/csrc/stable/library.h>
24
25 #include <cutlass/cutlass.h>
26 #include <cutlass/kernel_hardware_info.h>
27
28 #include <cute/tensor.hpp>
29 #include <iostream>
30
31 #include "cutlass_sm100_mla/device/sm100_mla.hpp"
32 #include "cutlass_sm100_mla/kernel/sm100_mla_tile_scheduler.hpp"
33
34 // clang-format off
35 #if !defined(CUDA_VERSION) || CUDA_VERSION < 12040
36 void sm100_cutlass_mla_decode(
37 torch::stable::Tensor const& out,
38 torch::stable::Tensor const& lse,
39 torch::stable::Tensor const& q_nope,
40 torch::stable::Tensor const& q_pe,
41 torch::stable::Tensor const& kv_c_and_k_pe_cache,
42 torch::stable::Tensor const& seq_lens,
43 torch::stable::Tensor const& page_table,
44 torch::stable::Tensor const& workspace,
45 double sm_scale,
46 int64_t num_kv_splits) {
47 STD_TORCH_CHECK(false, "CUDA version must be >= 12.4 for cutlass_mla_decode");
48 }
49 int64_t sm100_cutlass_mla_get_workspace_size(int64_t max_seq_len, int64_t num_batches, int64_t sm_count, int64_t num_kv_splits) {
50 STD_TORCH_CHECK(false, "CUDA version must be >= 12.4 for cutlass_mla_get_workspace_size");
51 }
52 #else
53
54 #define CUTLASS_CHECK(status) \
55 { \
56 cutlass::Status error = status; \
57 STD_TORCH_CHECK(error == cutlass::Status::kSuccess, cutlassGetStatusString(error)); \
58 }
59
60 using namespace cute;
61 using namespace cutlass::fmha::kernel;
62
63 template <bool v>
64 struct IsPersistent {
65 static const bool value = v;
66 };
67
68 template <typename T, typename TOut, bool IsPaged128, typename PersistenceOption = IsPersistent<true>>
69 struct MlaSm100 {
70 using Element = T;
71 using ElementAcc = float;
72 using ElementOut = TOut;
73
74 using TileShape = Shape<_128, _128, Shape<_512, _64>>;
75 using TileShapeH = cute::tuple_element_t<0, TileShape>;
76 using TileShapeD = cute::tuple_element_t<2, TileShape>;
77
78 // H K (D_latent D_rope) B
79 using ProblemShape = cute::tuple<TileShapeH, int, TileShapeD, int>;
80
81 using StrideQ = cute::tuple<int64_t, _1, int64_t>; // H D B
82 using StrideK = cute::tuple<int64_t, _1, int64_t>; // K D B
83 using StrideO = StrideK; // H D B
84 using StrideLSE = cute::tuple<_1, int>; // H B
85
86 using TileScheduler =
87 std::conditional_t<PersistenceOption::value, Sm100MlaPersistentTileScheduler, Sm100MlaIndividualTileScheduler>;
88
89 using FmhaKernel = cutlass::fmha::kernel::Sm100FmhaMlaKernelTmaWarpspecialized<
90 TileShape,
91 Element,
92 ElementAcc,
93 ElementOut,
94 ElementAcc,
95 TileScheduler,
96 /*kIsCpAsync=*/!IsPaged128>;
97 using Fmha = cutlass::fmha::device::MLA<FmhaKernel>;
98 };
99
100 template <typename T>
101 typename T::Fmha::Arguments args_from_options(
102 torch::stable::Tensor const& out,
103 torch::stable::Tensor const& lse,
104 torch::stable::Tensor const& q_nope,
105 torch::stable::Tensor const& q_pe,
106 torch::stable::Tensor const& kv_c_and_k_pe_cache,
107 torch::stable::Tensor const& seq_lens,
108 torch::stable::Tensor const& page_table,
109 double sm_scale,
110 int64_t num_kv_splits) {
111 cutlass::KernelHardwareInfo hw_info;
112 hw_info.device_id = q_nope.get_device_index();
113 hw_info.sm_count = cutlass::KernelHardwareInfo::query_device_multiprocessor_count(hw_info.device_id);
114
115 int batches = q_nope.size(0);
116 int page_count_per_seq = page_table.size(1);
117 int page_count_total = kv_c_and_k_pe_cache.size(0);
118 int page_size = kv_c_and_k_pe_cache.size(1);
119 int max_seq_len = page_size * page_count_per_seq;
120 using TileShapeH = typename T::TileShapeH;
121 using TileShapeD = typename T::TileShapeD;
122 auto problem_shape = cute::make_tuple(TileShapeH{}, max_seq_len, TileShapeD{}, batches);
123
124 auto [H, K, D, B] = problem_shape;
125 auto [D_latent, D_rope] = D;
126
127 float scale = float(sm_scale);
128
129 using StrideQ = typename T::StrideQ;
130 using StrideK = typename T::StrideK;
131 using StrideO = typename T::StrideO;
132 using StrideLSE = typename T::StrideLSE;
133
134 StrideQ stride_Q_nope = cute::make_tuple(
135 static_cast<int64_t>(q_nope.stride(1)), _1{}, static_cast<int64_t>(q_nope.stride(0)));
136 StrideQ stride_Q_pe = cute::make_tuple(
137 static_cast<int64_t>(q_pe.stride(1)), _1{}, static_cast<int64_t>(q_pe.stride(0)));
138
139 // Read the token and page strides from the cache tensor instead of assuming
140 // packed pages, so strided views (e.g. per-layer views into a cross-layer
141 // block-major cache) are addressed correctly.
142 StrideK stride_C = cute::make_tuple(
143 static_cast<int64_t>(kv_c_and_k_pe_cache.stride(1)), _1{},
144 static_cast<int64_t>(kv_c_and_k_pe_cache.stride(0)));
145 StrideLSE stride_PT = cute::make_stride(_1{}, page_count_per_seq);
146 StrideLSE stride_LSE = cute::make_tuple(_1{}, 0 + H);
147 StrideO stride_O = cute::make_tuple(static_cast<int64_t>(0 + D_latent), _1{}, static_cast<int64_t>(0 + H * D_latent));
148
149 using Element = typename T::Element;
150 using ElementOut = typename T::ElementOut;
151 using ElementAcc = typename T::ElementAcc;
152 auto Q_nope_ptr = static_cast<Element*>(q_nope.data_ptr());
153 auto Q_pe_ptr = static_cast<Element*>(q_pe.data_ptr());
154 auto C_ptr = static_cast<Element*>(kv_c_and_k_pe_cache.data_ptr());
155 typename T::Fmha::Arguments arguments{
156 problem_shape,
157 {scale,
158 Q_nope_ptr,
159 stride_Q_nope,
160 Q_pe_ptr,
161 stride_Q_pe,
162 C_ptr,
163 stride_C,
164 C_ptr + D_latent,
165 stride_C,
166 static_cast<int*>(seq_lens.data_ptr()),
167 static_cast<int*>(page_table.data_ptr()),
168 stride_PT,
169 page_count_total,
170 page_size},
171 {static_cast<ElementOut*>(out.data_ptr()),
172 stride_O,
173 static_cast<ElementAcc*>(lse.defined() ? lse.data_ptr() : nullptr),
174 stride_LSE},
175 hw_info,
176 // TODO(trevor-m): Change split_kv back to -1 when
177 // https://github.com/NVIDIA/cutlass/issues/2274 is fixed. Split_kv=1 will
178 // perform worse with larger context length and smaller batch sizes.
179 static_cast<int>(num_kv_splits), // split_kv
180 nullptr, // is_var_split_kv
181 };
182 // TODO(kaixih@nvidia): When split_kv=-1 and is_var_split_kv=false, we compute
183 // split_kv automatically based on batch size and sequence length to balance
184 // workload across available SMs. Consider using var_split_kv for manual
185 // control if needed.
186 T::Fmha::set_split_kv(arguments);
187 return arguments;
188 }
189
190 template <typename Element, typename ElementOut, bool IsPaged128, typename PersistenceOption>
191 void runMla(
192 torch::stable::Tensor const& out,
193 torch::stable::Tensor const& lse,
194 torch::stable::Tensor const& q_nope,
195 torch::stable::Tensor const& q_pe,
196 torch::stable::Tensor const& kv_c_and_k_pe_cache,
197 torch::stable::Tensor const& seq_lens,
198 torch::stable::Tensor const& page_table,
199 torch::stable::Tensor const& workspace,
200 double sm_scale,
201 int64_t num_kv_splits,
202 cudaStream_t stream) {
203 using MlaSm100Type = MlaSm100<Element, ElementOut, IsPaged128, PersistenceOption>;
204 typename MlaSm100Type::Fmha fmha;
205 auto arguments = args_from_options<MlaSm100Type>(out, lse, q_nope, q_pe, kv_c_and_k_pe_cache, seq_lens, page_table, sm_scale, num_kv_splits);
206
207 CUTLASS_CHECK(fmha.can_implement(arguments));
208
209 CUTLASS_CHECK(fmha.initialize(arguments, workspace.data_ptr(), stream));
210
211 CUTLASS_CHECK(fmha.run(arguments, workspace.data_ptr(), stream));
212 }
213
214 #define DISPATCH_BOOL(expr, const_expr, ...) \
215 [&]() -> bool { \
216 if (expr) { \
217 constexpr bool const_expr = true; \
218 return __VA_ARGS__(); \
219 } else { \
220 constexpr bool const_expr = false; \
221 return __VA_ARGS__(); \
222 } \
223 }()
224
225 void sm100_cutlass_mla_decode(
226 torch::stable::Tensor const& out,
227 torch::stable::Tensor const& lse,
228 torch::stable::Tensor const& q_nope,
229 torch::stable::Tensor const& q_pe,
230 torch::stable::Tensor const& kv_c_and_k_pe_cache,
231 torch::stable::Tensor const& seq_lens,
232 torch::stable::Tensor const& page_table,
233 torch::stable::Tensor const& workspace,
234 double sm_scale,
235 int64_t num_kv_splits) {
236 auto in_dtype = q_nope.scalar_type();
237 torch::stable::accelerator::DeviceGuard device_guard(q_nope.get_device_index());
238 const cudaStream_t stream = get_current_cuda_stream(q_nope.get_device_index());
239 const int page_size = kv_c_and_k_pe_cache.size(1);
240
241 // NOTE(alcanderian): IsPersistent has bug with manual split_kv.
242 // Kernel will hang if batch is too large with large num_kv_splits. (for example bs=8, num_kv_splits=8)
243 // Maybe per batch split kv will fix this.
244 DISPATCH_BOOL(page_size == 128, IsPaged128, [&] {
245 DISPATCH_BOOL(num_kv_splits <= 1, NotManualSplitKV, [&] {
246 if (in_dtype == torch::headeronly::ScalarType::Half) {
247 runMla<cutlass::half_t, cutlass::half_t, IsPaged128, IsPersistent<NotManualSplitKV>>(
248 out, lse, q_nope, q_pe, kv_c_and_k_pe_cache, seq_lens, page_table, workspace, sm_scale, num_kv_splits, stream);
249 } else if (in_dtype == torch::headeronly::ScalarType::BFloat16) {
250 runMla<cutlass::bfloat16_t, cutlass::bfloat16_t, IsPaged128, IsPersistent<NotManualSplitKV>>(
251 out, lse, q_nope, q_pe, kv_c_and_k_pe_cache, seq_lens, page_table, workspace, sm_scale, num_kv_splits, stream);
252 } else if (in_dtype == torch::headeronly::ScalarType::Float8_e4m3fn) {
253 runMla<cutlass::float_e4m3_t, cutlass::bfloat16_t, IsPaged128, IsPersistent<NotManualSplitKV>>(
254 out, lse, q_nope, q_pe, kv_c_and_k_pe_cache, seq_lens, page_table, workspace, sm_scale, num_kv_splits, stream);
255 } else {
256 STD_TORCH_CHECK(false, "Unsupported input data type of MLA");
257 }
258 return true;
259 });
260 return true;
261 });
262 }
263
264 int64_t sm100_cutlass_mla_get_workspace_size(int64_t max_seq_len, int64_t num_batches, int64_t sm_count, int64_t num_kv_splits) {
265 // Workspace size depends on ElementAcc and ElementLSE (same as ElementAcc)
266 // which are float, so Element type here doesn't matter.
267 using MlaSm100Type = MlaSm100<cutlass::half_t, cutlass::half_t, true>;
268
269 // Get split kv. Requires problem shape and sm_count only.
270 typename MlaSm100Type::Fmha::Arguments arguments;
271 using TileShapeH = typename MlaSm100Type::TileShapeH;
272 using TileShapeD = typename MlaSm100Type::TileShapeD;
273 arguments.problem_shape =
274 cute::make_tuple(TileShapeH{}, static_cast<int>(max_seq_len), TileShapeD{}, static_cast<int>(num_batches));
275 if (sm_count <= 0) {
276 int current_device = 0;
277 cudaGetDevice(&current_device);
278 arguments.hw_info.sm_count =
279 cutlass::KernelHardwareInfo::query_device_multiprocessor_count(current_device);
280 } else {
281 arguments.hw_info.sm_count = sm_count;
282 }
283 arguments.split_kv = static_cast<int>(num_kv_splits);
284 MlaSm100Type::Fmha::set_split_kv(arguments);
285
286 return MlaSm100Type::Fmha::get_workspace_size(arguments);
287 }
288
289 #endif
290
291 STABLE_TORCH_LIBRARY_IMPL(_C, CUDA, m) {
292 m.impl("sm100_cutlass_mla_decode", TORCH_BOX(&sm100_cutlass_mla_decode));
293 }
294
295 STABLE_TORCH_LIBRARY_IMPL(_C, CompositeExplicitAutograd, m) {
296 m.impl("sm100_cutlass_mla_get_workspace_size", TORCH_BOX(&sm100_cutlass_mla_get_workspace_size));
297 }
298
299 // clang-format on