main
cu 747 lines 34.4 KB
Raw
1 /*
2 * Copyright (c) 2025, NVIDIA CORPORATION. All rights reserved.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #include <torch/csrc/stable/library.h>
18 #include <torch/csrc/stable/tensor.h>
19 #include "libtorch_stable/torch_utils.h"
20
21 #include <cutlass/arch/arch.h>
22
23 #include "libtorch_stable/cutlass_extensions/common.hpp"
24
25 #include "cute/tensor.hpp"
26 #include "cutlass/tensor_ref.h"
27 #include "cutlass/epilogue/collective/default_epilogue.hpp"
28 #include "cutlass/epilogue/thread/linear_combination.h"
29 #include "cutlass/gemm/dispatch_policy.hpp"
30 #include "cutlass/gemm/group_array_problem_shape.hpp"
31 #include "cutlass/gemm/collective/collective_builder.hpp"
32 #include "cutlass/epilogue/collective/collective_builder.hpp"
33 #include "cutlass/gemm/device/gemm_universal_adapter.h"
34 #include "cutlass/gemm/kernel/gemm_universal.hpp"
35
36 #include "cutlass/util/command_line.h"
37 #include "cutlass/util/distribution.h"
38 #include "cutlass/util/host_tensor.h"
39 #include "cutlass/util/packed_stride.hpp"
40 #include "cutlass/util/tensor_view_io.h"
41 #include "cutlass/util/reference/device/gemm.h"
42 #include "cutlass/util/reference/device/tensor_compare.h"
43 #include "cutlass/util/reference/host/tensor_fill.h"
44 #include "cutlass/util/reference/host/gett.hpp"
45 #include "cutlass/util/reference/host/tensor_norm.h"
46 #include "cutlass/util/reference/host/tensor_compare.h"
47 #include <cassert>
48
49 using namespace cute;
50
51 template <typename ElementAB, typename ElementC, typename ElementSF,
52 typename ElementAccumulator, typename LayoutSFA, typename LayoutSFB,
53 typename ScaleConfig>
54 __global__ void __get_group_gemm_starts(
55 ElementAB** a_offsets, ElementAB** b_offsets, ElementC** out_offsets,
56 ElementSF** a_scales_offsets, ElementSF** b_scales_offsets,
57 ElementAccumulator** alpha_offsets, LayoutSFA* layout_sfa_base_as_int,
58 LayoutSFB* layout_sfb_base_as_int, ElementAB* a_base_as_int,
59 ElementAB* b_base_as_int, ElementC* out_base_as_int,
60 ElementSF* a_scales_base_as_int, ElementSF* b_scales_base_as_int,
61 ElementAccumulator* alphas_base_as_int, const int32_t* expert_offsets,
62 const int32_t* sf_offsets, const int32_t* problem_sizes_as_shapes,
63 int64_t* a_strides, int64_t* b_strides, int64_t* c_strides,
64 const int64_t a_stride_val, const int64_t b_stride_val,
65 const int64_t c_stride_val, const int K, const int N) {
66 int64_t expert_id = threadIdx.x;
67 if (expert_id >= gridDim.x * blockDim.x) {
68 return;
69 }
70 // Originally int32_t but upcasting to int64_t to avoid overflow
71 // during offset calculations
72 int64_t expert_offset = static_cast<int64_t>(expert_offsets[expert_id]);
73 int64_t sf_offset = static_cast<int64_t>(sf_offsets[expert_id]);
74 // size for block in block scale.
75 int64_t group_size = 16;
76 int64_t m = static_cast<int64_t>(problem_sizes_as_shapes[expert_id * 3]);
77 int64_t n = static_cast<int64_t>(problem_sizes_as_shapes[expert_id * 3 + 1]);
78 int64_t k = static_cast<int64_t>(problem_sizes_as_shapes[expert_id * 3 + 2]);
79 assert((m >= 0 && n == N && k == K && k % 2 == 0) &&
80 "unexpected problem sizes");
81
82 int64_t half_k = static_cast<int64_t>(k / 2);
83 int64_t group_k = static_cast<int64_t>(k / group_size);
84 // Shape of A as uint8/byte = [M, K // 2]
85 // Shape of B as uint8/byte = [E, N, K // 2]
86 a_offsets[expert_id] = a_base_as_int + expert_offset * half_k;
87
88 b_offsets[expert_id] = b_base_as_int + expert_id * n * half_k;
89 // Shape of C = [M, N]
90 out_offsets[expert_id] = out_base_as_int + expert_offset * n;
91 // Shape of a_scale = [sum(sf_sizes), K // group_size]
92 a_scales_offsets[expert_id] = a_scales_base_as_int + sf_offset * group_k;
93
94 assert((reinterpret_cast<uintptr_t>(a_scales_offsets[expert_id]) % 128) ==
95 0 &&
96 "TMA requires 128-byte alignment");
97
98 // Shape of B scale = [E, N, K // group_size]
99 b_scales_offsets[expert_id] = b_scales_base_as_int + expert_id * n * group_k;
100 assert((reinterpret_cast<uintptr_t>(b_scales_offsets[expert_id]) % 128) ==
101 0 &&
102 "TMA requires 128-byte alignment");
103 // Shape of alpha = [E]
104 alpha_offsets[expert_id] = alphas_base_as_int + expert_id;
105
106 // Initialize strides (constant across all experts, avoids separate kernels)
107 a_strides[expert_id] = a_stride_val;
108 b_strides[expert_id] = b_stride_val;
109 c_strides[expert_id] = c_stride_val;
110
111 LayoutSFA* layout_sfa_ptr = layout_sfa_base_as_int + expert_id;
112 LayoutSFB* layout_sfb_ptr = layout_sfb_base_as_int + expert_id;
113
114 *layout_sfa_ptr = ScaleConfig::tile_atom_to_shape_SFA(cute::make_shape(
115 static_cast<int>(m), static_cast<int>(n), static_cast<int>(k), 1));
116 *layout_sfb_ptr = ScaleConfig::tile_atom_to_shape_SFB(cute::make_shape(
117 static_cast<int>(m), static_cast<int>(n), static_cast<int>(k), 1));
118 }
119
120 #define __CALL_GET_STARTS_KERNEL_BLOCKSCALE(ELEMENT_AB_TYPE, SF_TYPE, \
121 TENSOR_C_TYPE, C_TYPE, LayoutSFA, \
122 LayoutSFB, ScaleConfig) \
123 else if (out_tensors.scalar_type() == TENSOR_C_TYPE) { \
124 __get_group_gemm_starts<ELEMENT_AB_TYPE, C_TYPE, SF_TYPE, float, \
125 LayoutSFA, LayoutSFB, ScaleConfig> \
126 <<<1, num_experts, 0, stream>>>( \
127 static_cast<ELEMENT_AB_TYPE**>(a_starts.data_ptr()), \
128 static_cast<ELEMENT_AB_TYPE**>(b_starts.data_ptr()), \
129 static_cast<C_TYPE**>(out_starts.data_ptr()), \
130 static_cast<SF_TYPE**>(a_scales_starts.data_ptr()), \
131 static_cast<SF_TYPE**>(b_scales_starts.data_ptr()), \
132 static_cast<float**>(alpha_starts.data_ptr()), \
133 reinterpret_cast<LayoutSFA*>(layout_sfa.data_ptr()), \
134 reinterpret_cast<LayoutSFB*>(layout_sfb.data_ptr()), \
135 static_cast<ELEMENT_AB_TYPE*>(a_tensors.data_ptr()), \
136 static_cast<ELEMENT_AB_TYPE*>(b_tensors.data_ptr()), \
137 static_cast<C_TYPE*>(out_tensors.data_ptr()), \
138 static_cast<SF_TYPE*>(a_scales.data_ptr()), \
139 static_cast<SF_TYPE*>(b_scales.data_ptr()), \
140 static_cast<float*>(alphas.data_ptr()), \
141 static_cast<int32_t*>(expert_offsets.data_ptr()), \
142 static_cast<int32_t*>(sf_offsets.data_ptr()), \
143 static_cast<int32_t*>(problem_sizes.data_ptr()), \
144 static_cast<int64_t*>(a_strides.data_ptr()), \
145 static_cast<int64_t*>(b_strides.data_ptr()), \
146 static_cast<int64_t*>(c_strides.data_ptr()), a_stride_val, \
147 b_stride_val, c_stride_val, K, N); \
148 }
149
150 template <typename LayoutSFA, typename LayoutSFB, typename ScaleConfig>
151 void run_get_group_gemm_starts(const torch::stable::Tensor& a_starts,
152 const torch::stable::Tensor& b_starts,
153 const torch::stable::Tensor& out_starts,
154 const torch::stable::Tensor& a_scales_starts,
155 const torch::stable::Tensor& b_scales_starts,
156 const torch::stable::Tensor& alpha_starts,
157 const torch::stable::Tensor& layout_sfa,
158 const torch::stable::Tensor& layout_sfb,
159 const torch::stable::Tensor& a_strides,
160 const torch::stable::Tensor& b_strides,
161 const torch::stable::Tensor& c_strides,
162 int64_t a_stride_val, int64_t b_stride_val,
163 int64_t c_stride_val,
164 /*these are used for their base addresses*/
165 torch::stable::Tensor const& a_tensors,
166 torch::stable::Tensor const& b_tensors,
167 torch::stable::Tensor const& out_tensors,
168 torch::stable::Tensor const& a_scales,
169 torch::stable::Tensor const& b_scales,
170 torch::stable::Tensor const& alphas,
171 torch::stable::Tensor const& expert_offsets,
172 torch::stable::Tensor const& sf_offsets,
173 torch::stable::Tensor const& problem_sizes,
174 int M, int N, int K) {
175 int num_experts = (int)expert_offsets.size(0);
176 const torch::stable::accelerator::DeviceGuard device_guard(
177 a_tensors.get_device_index());
178 auto stream = get_current_cuda_stream(a_tensors.get_device_index());
179
180 STD_TORCH_CHECK(out_tensors.size(1) == N,
181 "Output tensor shape doesn't match expected shape");
182 STD_TORCH_CHECK(K / 2 == b_tensors.size(2),
183 "b_tensors(dim = 2) and a_tensors(dim = 1) trailing"
184 " dimension must match");
185 if (false) {
186 }
187 //(ELEMENT_AB_TYPE, BS_TYPE, TENSOR_C_TYPE, C_TYPE, LayoutSFA, LayoutSFB,
188 // ScaleConfig)
189 __CALL_GET_STARTS_KERNEL_BLOCKSCALE(
190 cutlass::float_e2m1_t, cutlass::float_ue4m3_t,
191 torch::headeronly::ScalarType::BFloat16, cutlass::bfloat16_t, LayoutSFA,
192 LayoutSFB, ScaleConfig)
193 __CALL_GET_STARTS_KERNEL_BLOCKSCALE(cutlass::float_e2m1_t,
194 cutlass::float_ue4m3_t,
195 torch::headeronly::ScalarType::Half, half,
196 LayoutSFA, LayoutSFB, ScaleConfig)
197 else {
198 STD_TORCH_CHECK(false, "Invalid output type (must be float16 or bfloat16)");
199 }
200 }
201
202 template <typename OutType>
203 void run_fp4_blockwise_scaled_group_mm_sm100(
204 torch::stable::Tensor& output, const torch::stable::Tensor& a,
205 const torch::stable::Tensor& b, const torch::stable::Tensor& a_blockscale,
206 const torch::stable::Tensor& b_blockscales,
207 const torch::stable::Tensor& alphas,
208 const torch::stable::Tensor& problem_sizes,
209 const torch::stable::Tensor& expert_offsets,
210 const torch::stable::Tensor& sf_offsets, int M, int N, int K) {
211 const torch::stable::accelerator::DeviceGuard device_guard(
212 a.get_device_index());
213 using ProblemShape =
214 cutlass::gemm::GroupProblemShape<Shape<int32_t, int32_t, int32_t>>;
215 using ElementType = cutlass::float_e2m1_t;
216 using ElementSFType = cutlass::float_ue4m3_t;
217 using ElementA = cutlass::nv_float4_t<cutlass::float_e2m1_t>;
218 using ElementB = cutlass::nv_float4_t<cutlass::float_e2m1_t>;
219
220 using ElementC = OutType;
221 using ElementD = ElementC;
222 using ElementAccumulator = float;
223 // Layout definitions
224 using LayoutA = cutlass::layout::RowMajor;
225 using LayoutB = cutlass::layout::ColumnMajor;
226 using LayoutC = cutlass::layout::RowMajor;
227 using LayoutD = LayoutC;
228
229 // Alignment constraints
230 static constexpr int AlignmentA = 32;
231 static constexpr int AlignmentB = 32;
232 static constexpr int AlignmentC = 128 / cutlass::sizeof_bits<ElementC>::value;
233 static constexpr int AlignmentD = 128 / cutlass::sizeof_bits<ElementD>::value;
234
235 // Architecture definitions
236 using ArchTag = cutlass::arch::Sm100;
237 using EpilogueOperatorClass =
238 cutlass::arch::OpClassTensorOp; // Epilogue Operator class tag
239 using MainloopOperatorClass =
240 cutlass::arch::OpClassBlockScaledTensorOp; // Mainloop Operator class tag
241 using StageCountType =
242 cutlass::gemm::collective::StageCountAuto; // Stage count maximized based
243 // on the tile size
244
245 using ClusterShape = Shape<_1, _1, _1>;
246 struct MMA1SMConfig {
247 using MmaTileShape = Shape<_128, _128, _128>;
248 using KernelSchedule = cutlass::gemm::
249 KernelPtrArrayTmaWarpSpecialized1SmNvf4Sm100; // Kernel to launch
250 using EpilogueSchedule =
251 cutlass::epilogue::PtrArrayTmaWarpSpecialized1Sm; // Epilogue to launch
252 };
253
254 using CollectiveEpilogue =
255 typename cutlass::epilogue::collective::CollectiveBuilder<
256 ArchTag, EpilogueOperatorClass, typename MMA1SMConfig::MmaTileShape,
257 ClusterShape, Shape<_128, _64>, ElementAccumulator,
258 ElementAccumulator, ElementC, LayoutC*, AlignmentC, ElementD,
259 LayoutC*, AlignmentD,
260 typename MMA1SMConfig::EpilogueSchedule>::CollectiveOp;
261
262 using CollectiveMainloop =
263 typename cutlass::gemm::collective::CollectiveBuilder<
264 ArchTag, MainloopOperatorClass, ElementA, LayoutA*, AlignmentA,
265 ElementB, LayoutB*, AlignmentB, ElementAccumulator,
266 typename MMA1SMConfig::MmaTileShape, ClusterShape,
267 cutlass::gemm::collective::StageCountAutoCarveout<static_cast<int>(
268 sizeof(typename CollectiveEpilogue::SharedStorage))>,
269 typename MMA1SMConfig::KernelSchedule>::CollectiveOp;
270
271 using GemmKernel =
272 cutlass::gemm::kernel::GemmUniversal<ProblemShape, CollectiveMainloop,
273 CollectiveEpilogue>;
274
275 using Gemm1SM = cutlass::gemm::device::GemmUniversalAdapter<GemmKernel>;
276 using Gemm = Gemm1SM;
277 static_assert(
278 cute::is_same_v<
279 typename Gemm::GemmKernel::TileScheduler,
280 cutlass::gemm::kernel::detail::PersistentTileSchedulerSm100Group<
281 ProblemShape, Gemm::GemmKernel::SchedulerPipelineStageCount>>,
282 "SM100 NVFP4 grouped GEMM must use PersistentTileSchedulerSm100Group "
283 "for batch invariance (VLLM_BATCH_INVARIANT=1). "
284 "If you want to change this, add a dedicated config/code path "
285 "for batch invariant mode, instead of relaxing this check.");
286 using StrideA = typename Gemm::GemmKernel::InternalStrideA;
287 using StrideB = typename Gemm::GemmKernel::InternalStrideB;
288 using StrideC = typename Gemm::GemmKernel::InternalStrideC;
289 using StrideD = typename Gemm::GemmKernel::InternalStrideD;
290
291 using LayoutSFA =
292 typename Gemm::GemmKernel::CollectiveMainloop::InternalLayoutSFA;
293 using LayoutSFB =
294 typename Gemm::GemmKernel::CollectiveMainloop::InternalLayoutSFB;
295 using ScaleConfig =
296 typename Gemm::GemmKernel::CollectiveMainloop::Sm1xxBlkScaledConfig;
297
298 using UnderlyingProblemShape = ProblemShape::UnderlyingProblemShape;
299 int num_experts = static_cast<int>(expert_offsets.size(0));
300
301 torch::stable::Tensor a_ptrs =
302 torch::stable::empty(num_experts, torch::headeronly::ScalarType::Long,
303 std::nullopt, a.device());
304 torch::stable::Tensor b_ptrs =
305 torch::stable::empty(num_experts, torch::headeronly::ScalarType::Long,
306 std::nullopt, a.device());
307 torch::stable::Tensor out_ptrs =
308 torch::stable::empty(num_experts, torch::headeronly::ScalarType::Long,
309 std::nullopt, a.device());
310 torch::stable::Tensor a_scales_ptrs =
311 torch::stable::empty(num_experts, torch::headeronly::ScalarType::Long,
312 std::nullopt, a.device());
313 torch::stable::Tensor b_scales_ptrs =
314 torch::stable::empty(num_experts, torch::headeronly::ScalarType::Long,
315 std::nullopt, a.device());
316 torch::stable::Tensor alpha_ptrs =
317 torch::stable::empty(num_experts, torch::headeronly::ScalarType::Long,
318 std::nullopt, a.device());
319 torch::stable::Tensor layout_sfa = torch::stable::empty(
320 {num_experts, 5}, torch::headeronly::ScalarType::Long, std::nullopt,
321 a.device());
322 torch::stable::Tensor layout_sfb = torch::stable::empty(
323 {num_experts, 5}, torch::headeronly::ScalarType::Long, std::nullopt,
324 a.device());
325 torch::stable::Tensor a_strides1 =
326 torch::stable::empty(num_experts, torch::headeronly::ScalarType::Long,
327 std::nullopt, a.device());
328 torch::stable::Tensor b_strides1 =
329 torch::stable::empty(num_experts, torch::headeronly::ScalarType::Long,
330 std::nullopt, a.device());
331 torch::stable::Tensor c_strides1 =
332 torch::stable::empty(num_experts, torch::headeronly::ScalarType::Long,
333 std::nullopt, a.device());
334
335 run_get_group_gemm_starts<LayoutSFA, LayoutSFB, ScaleConfig>(
336 a_ptrs, b_ptrs, out_ptrs, a_scales_ptrs, b_scales_ptrs, alpha_ptrs,
337 layout_sfa, layout_sfb, a_strides1, b_strides1, c_strides1,
338 a.stride(0) * 2, b.stride(1) * 2, output.stride(0), a, b, output,
339 a_blockscale, b_blockscales, alphas, expert_offsets, sf_offsets,
340 problem_sizes, M, N, K);
341
342 // Create an instance of the GEMM
343 Gemm gemm_op;
344
345 // Initialize problem_sizes_as_shapes correctly
346 UnderlyingProblemShape* problem_sizes_as_shapes =
347 static_cast<UnderlyingProblemShape*>(problem_sizes.data_ptr());
348
349 // Set the Scheduler info
350 cutlass::KernelHardwareInfo hw_info;
351 using RasterOrderOptions = typename cutlass::gemm::kernel::detail::
352 PersistentTileSchedulerSm100GroupParams<
353 typename ProblemShape::UnderlyingProblemShape>::RasterOrderOptions;
354 typename Gemm::GemmKernel::TileSchedulerArguments scheduler;
355 scheduler.raster_order = RasterOrderOptions::AlongM;
356 hw_info.device_id = a.get_device_index();
357 static std::unordered_map<int, int> cached_sm_counts;
358 if (cached_sm_counts.find(hw_info.device_id) == cached_sm_counts.end()) {
359 cached_sm_counts[hw_info.device_id] =
360 cutlass::KernelHardwareInfo::query_device_multiprocessor_count(
361 hw_info.device_id);
362 }
363 hw_info.sm_count = min(cached_sm_counts[hw_info.device_id], INT_MAX);
364
365 // Mainloop Arguments
366 typename GemmKernel::MainloopArguments mainloop_args{
367 static_cast<const ElementType**>(a_ptrs.data_ptr()),
368 static_cast<StrideA*>(a_strides1.data_ptr()),
369 static_cast<const ElementType**>(b_ptrs.data_ptr()),
370 static_cast<StrideB*>(b_strides1.data_ptr()),
371 static_cast<const ElementSFType**>(a_scales_ptrs.data_ptr()),
372 reinterpret_cast<LayoutSFA*>(layout_sfa.data_ptr()),
373 static_cast<const ElementSFType**>(b_scales_ptrs.data_ptr()),
374 reinterpret_cast<LayoutSFB*>(layout_sfb.data_ptr())};
375
376 // Epilogue Arguments
377 typename GemmKernel::EpilogueArguments epilogue_args{
378 {}, // epilogue.thread
379 nullptr,
380 static_cast<StrideC*>(c_strides1.data_ptr()),
381 static_cast<ElementD**>(out_ptrs.data_ptr()),
382 static_cast<StrideC*>(c_strides1.data_ptr())};
383 auto& fusion_args = epilogue_args.thread;
384 fusion_args.alpha_ptr_array =
385 reinterpret_cast<float**>(alpha_ptrs.data_ptr());
386 fusion_args.dAlpha = {_0{}, _0{}, 1};
387
388 // Gemm Arguments
389 typename GemmKernel::Arguments args{
390 cutlass::gemm::GemmUniversalMode::kGrouped,
391 {num_experts, problem_sizes_as_shapes, nullptr},
392 mainloop_args,
393 epilogue_args,
394 hw_info,
395 scheduler};
396
397 size_t workspace_size = Gemm::get_workspace_size(args);
398 auto workspace =
399 torch::stable::empty(workspace_size, torch::headeronly::ScalarType::Byte,
400 std::nullopt, a.device());
401 const cudaStream_t stream = get_current_cuda_stream(a.get_device_index());
402
403 auto can_implement_status = gemm_op.can_implement(args);
404 STD_TORCH_CHECK(
405 can_implement_status == cutlass::Status::kSuccess,
406 "Failed to implement GEMM: status=", (int)can_implement_status);
407
408 // Run the GEMM
409 auto status = gemm_op.initialize(args, workspace.data_ptr());
410 STD_TORCH_CHECK(status == cutlass::Status::kSuccess,
411 "Failed to initialize GEMM: status=", (int)status,
412 " workspace_size=", workspace_size,
413 " num_experts=", num_experts, " M=", M, " N=", N, " K=", K);
414
415 status = gemm_op.run(args, workspace.data_ptr(), stream);
416 STD_TORCH_CHECK(status == cutlass::Status::kSuccess, "Failed to run GEMM");
417 }
418
419 void run_fp4_blockwise_scaled_group_mm_sm120(
420 torch::stable::Tensor& output, const torch::stable::Tensor& a,
421 const torch::stable::Tensor& b, const torch::stable::Tensor& a_blockscale,
422 const torch::stable::Tensor& b_blockscales,
423 const torch::stable::Tensor& alphas,
424 const torch::stable::Tensor& problem_sizes,
425 const torch::stable::Tensor& expert_offsets,
426 const torch::stable::Tensor& sf_offsets, int M, int N, int K) {
427 const torch::stable::accelerator::DeviceGuard device_guard(
428 a.get_device_index());
429 using ProblemShape =
430 cutlass::gemm::GroupProblemShape<Shape<int32_t, int32_t, int32_t>>;
431 using ElementType = cutlass::float_e2m1_t;
432 using ElementSFType = cutlass::float_ue4m3_t;
433 using ElementA = cutlass::nv_float4_t<cutlass::float_e2m1_t>;
434 using ElementB = cutlass::nv_float4_t<cutlass::float_e2m1_t>;
435
436 // NOTE: For SM120 it seems templating the output type is not supported and
437 // we need to hardcode the output type to bfloat16
438 using ElementC = cutlass::bfloat16_t;
439 using ElementD = ElementC;
440 using ElementAccumulator = float;
441 // Layout definitions
442 using LayoutA = cutlass::layout::RowMajor;
443 using LayoutB = cutlass::layout::ColumnMajor;
444 using LayoutC = cutlass::layout::RowMajor;
445 using LayoutD = LayoutC;
446
447 // Alignment constraints
448 static constexpr int AlignmentA = 32;
449 static constexpr int AlignmentB = 32;
450 static constexpr int AlignmentC = 128 / cutlass::sizeof_bits<ElementC>::value;
451 static constexpr int AlignmentD = 128 / cutlass::sizeof_bits<ElementD>::value;
452
453 // Architecture definitions
454 using ArchTag = cutlass::arch::Sm120;
455 using OperatorClass = cutlass::arch::OpClassBlockScaledTensorOp;
456
457 using ClusterShape = Shape<_1, _1, _1>;
458 using MmaTileShape = Shape<_128, _128, _128>;
459
460 using FusionOperation = cutlass::epilogue::fusion::LinearCombination<
461 ElementD, ElementAccumulator, ElementC, ElementAccumulator>;
462
463 using CollectiveEpilogue =
464 typename cutlass::epilogue::collective::CollectiveBuilder<
465 ArchTag, OperatorClass, MmaTileShape, ClusterShape,
466 cutlass::epilogue::collective::EpilogueTileAuto, ElementAccumulator,
467 ElementAccumulator, ElementC, LayoutC*, AlignmentC, ElementD,
468 LayoutD*, AlignmentD,
469 cutlass::epilogue::collective::EpilogueScheduleAuto,
470 FusionOperation>::CollectiveOp;
471
472 using CollectiveMainloop =
473 typename cutlass::gemm::collective::CollectiveBuilder<
474 ArchTag, OperatorClass, ElementA, LayoutA*, AlignmentA, ElementB,
475 LayoutB*, AlignmentB, ElementAccumulator, MmaTileShape, ClusterShape,
476 cutlass::gemm::collective::StageCountAutoCarveout<static_cast<int>(
477 sizeof(typename CollectiveEpilogue::SharedStorage))>,
478 cutlass::gemm::collective::KernelScheduleAuto>::CollectiveOp;
479
480 using GemmKernel =
481 cutlass::gemm::kernel::GemmUniversal<ProblemShape, CollectiveMainloop,
482 CollectiveEpilogue>;
483
484 using Gemm = cutlass::gemm::device::GemmUniversalAdapter<GemmKernel>;
485 static_assert(
486 cute::is_base_of_v<
487 cutlass::gemm::KernelPtrArrayTmaWarpSpecializedCooperative,
488 typename Gemm::GemmKernel::CollectiveMainloop::DispatchPolicy::
489 Schedule>,
490 "SM120 NVFP4 grouped GEMM must use a cooperative ptr-array mainloop "
491 "schedule for batch invariance (VLLM_BATCH_INVARIANT=1). "
492 "If you want to change this, add a dedicated config/code path "
493 "for batch invariant mode, instead of relaxing this check.");
494 using StrideA = typename Gemm::GemmKernel::InternalStrideA;
495 using StrideB = typename Gemm::GemmKernel::InternalStrideB;
496 using StrideC = typename Gemm::GemmKernel::InternalStrideC;
497 using StrideD = typename Gemm::GemmKernel::InternalStrideD;
498
499 using LayoutSFA =
500 typename Gemm::GemmKernel::CollectiveMainloop::InternalLayoutSFA;
501 using LayoutSFB =
502 typename Gemm::GemmKernel::CollectiveMainloop::InternalLayoutSFB;
503 using ScaleConfig =
504 typename Gemm::GemmKernel::CollectiveMainloop::Sm1xxBlkScaledConfig;
505
506 using UnderlyingProblemShape = ProblemShape::UnderlyingProblemShape;
507 int num_experts = static_cast<int>(expert_offsets.size(0));
508
509 torch::stable::Tensor a_ptrs =
510 torch::stable::empty(num_experts, torch::headeronly::ScalarType::Long,
511 std::nullopt, a.device());
512 torch::stable::Tensor b_ptrs =
513 torch::stable::empty(num_experts, torch::headeronly::ScalarType::Long,
514 std::nullopt, a.device());
515 torch::stable::Tensor out_ptrs =
516 torch::stable::empty(num_experts, torch::headeronly::ScalarType::Long,
517 std::nullopt, a.device());
518 torch::stable::Tensor a_scales_ptrs =
519 torch::stable::empty(num_experts, torch::headeronly::ScalarType::Long,
520 std::nullopt, a.device());
521 torch::stable::Tensor b_scales_ptrs =
522 torch::stable::empty(num_experts, torch::headeronly::ScalarType::Long,
523 std::nullopt, a.device());
524 torch::stable::Tensor alpha_ptrs =
525 torch::stable::empty(num_experts, torch::headeronly::ScalarType::Long,
526 std::nullopt, a.device());
527 torch::stable::Tensor layout_sfa = torch::stable::empty(
528 {num_experts, 5}, torch::headeronly::ScalarType::Long, std::nullopt,
529 a.device());
530 torch::stable::Tensor layout_sfb = torch::stable::empty(
531 {num_experts, 5}, torch::headeronly::ScalarType::Long, std::nullopt,
532 a.device());
533 torch::stable::Tensor a_strides1 =
534 torch::stable::empty(num_experts, torch::headeronly::ScalarType::Long,
535 std::nullopt, a.device());
536 torch::stable::Tensor b_strides1 =
537 torch::stable::empty(num_experts, torch::headeronly::ScalarType::Long,
538 std::nullopt, a.device());
539 torch::stable::Tensor c_strides1 =
540 torch::stable::empty(num_experts, torch::headeronly::ScalarType::Long,
541 std::nullopt, a.device());
542
543 run_get_group_gemm_starts<LayoutSFA, LayoutSFB, ScaleConfig>(
544 a_ptrs, b_ptrs, out_ptrs, a_scales_ptrs, b_scales_ptrs, alpha_ptrs,
545 layout_sfa, layout_sfb, a_strides1, b_strides1, c_strides1,
546 a.stride(0) * 2, b.stride(1) * 2, output.stride(0), a, b, output,
547 a_blockscale, b_blockscales, alphas, expert_offsets, sf_offsets,
548 problem_sizes, M, N, K);
549
550 // Create an instance of the GEMM
551 Gemm gemm_op;
552
553 // Initialize problem_sizes_as_shapes correctly
554 UnderlyingProblemShape* problem_sizes_as_shapes =
555 static_cast<UnderlyingProblemShape*>(problem_sizes.data_ptr());
556
557 // Set the Scheduler info
558 cutlass::KernelHardwareInfo hw_info;
559 using RasterOrderOptions = cutlass::gemm::kernel::detail::RasterOrderOptions;
560 typename Gemm::GemmKernel::TileSchedulerArguments scheduler;
561 scheduler.raster_order = RasterOrderOptions::AlongM;
562 hw_info.device_id = a.get_device_index();
563 static std::unordered_map<int, int> cached_sm_counts;
564 if (cached_sm_counts.find(hw_info.device_id) == cached_sm_counts.end()) {
565 cached_sm_counts[hw_info.device_id] =
566 cutlass::KernelHardwareInfo::query_device_multiprocessor_count(
567 hw_info.device_id);
568 }
569 hw_info.sm_count = min(cached_sm_counts[hw_info.device_id], INT_MAX);
570
571 // Mainloop Arguments
572 typename GemmKernel::MainloopArguments mainloop_args{
573 static_cast<const ElementType**>(a_ptrs.data_ptr()),
574 static_cast<StrideA*>(a_strides1.data_ptr()),
575 static_cast<const ElementType**>(b_ptrs.data_ptr()),
576 static_cast<StrideB*>(b_strides1.data_ptr()),
577 static_cast<const ElementSFType**>(a_scales_ptrs.data_ptr()),
578 reinterpret_cast<LayoutSFA*>(layout_sfa.data_ptr()),
579 static_cast<const ElementSFType**>(b_scales_ptrs.data_ptr()),
580 reinterpret_cast<LayoutSFB*>(layout_sfb.data_ptr())};
581
582 // Epilogue Arguments
583 typename GemmKernel::EpilogueArguments epilogue_args{
584 {}, // epilogue.thread
585 nullptr,
586 static_cast<StrideC*>(c_strides1.data_ptr()),
587 static_cast<ElementD**>(out_ptrs.data_ptr()),
588 static_cast<StrideC*>(c_strides1.data_ptr())};
589 auto& fusion_args = epilogue_args.thread;
590 fusion_args.alpha_ptr_array =
591 reinterpret_cast<float**>(alpha_ptrs.data_ptr());
592 fusion_args.dAlpha = {_0{}, _0{}, 1};
593 fusion_args.beta = 0.0f;
594
595 // Gemm Arguments
596 typename GemmKernel::Arguments args{
597 cutlass::gemm::GemmUniversalMode::kGrouped,
598 {num_experts, problem_sizes_as_shapes, nullptr},
599 mainloop_args,
600 epilogue_args,
601 hw_info,
602 scheduler};
603
604 size_t workspace_size = Gemm::get_workspace_size(args);
605 auto workspace =
606 torch::stable::empty(workspace_size, torch::headeronly::ScalarType::Byte,
607 std::nullopt, a.device());
608 const cudaStream_t stream = get_current_cuda_stream(a.get_device_index());
609
610 auto can_implement_status = gemm_op.can_implement(args);
611 STD_TORCH_CHECK(
612 can_implement_status == cutlass::Status::kSuccess,
613 "Failed to implement GEMM: status=", (int)can_implement_status);
614
615 // Run the GEMM
616 auto status = gemm_op.initialize(args, workspace.data_ptr());
617 STD_TORCH_CHECK(status == cutlass::Status::kSuccess,
618 "Failed to initialize GEMM: status=", (int)status,
619 " workspace_size=", workspace_size,
620 " num_experts=", num_experts, " M=", M, " N=", N, " K=", K);
621
622 status = gemm_op.run(args, workspace.data_ptr(), stream);
623 STD_TORCH_CHECK(status == cutlass::Status::kSuccess, "Failed to run GEMM");
624 }
625
626 template <typename OutType>
627 void run_fp4_blockwise_scaled_group_mm(
628 torch::stable::Tensor& output, const torch::stable::Tensor& a,
629 const torch::stable::Tensor& b, const torch::stable::Tensor& a_blockscale,
630 const torch::stable::Tensor& b_blockscales,
631 const torch::stable::Tensor& alphas,
632 const torch::stable::Tensor& problem_sizes,
633 const torch::stable::Tensor& expert_offsets,
634 const torch::stable::Tensor& sf_offsets, int M, int N, int K) {
635 int32_t version_num = get_sm_version_num();
636 #if defined ENABLE_NVFP4_SM120 && ENABLE_NVFP4_SM120
637 if (version_num >= 120 && version_num < 130) {
638 run_fp4_blockwise_scaled_group_mm_sm120(
639 output, a, b, a_blockscale, b_blockscales, alphas, problem_sizes,
640 expert_offsets, sf_offsets, M, N, K);
641 return;
642 }
643 #endif
644 #if defined ENABLE_NVFP4_SM100 && ENABLE_NVFP4_SM100
645 if (version_num >= 100 && version_num < 120) {
646 run_fp4_blockwise_scaled_group_mm_sm100<OutType>(
647 output, a, b, a_blockscale, b_blockscales, alphas, problem_sizes,
648 expert_offsets, sf_offsets, M, N, K);
649 return;
650 }
651 #endif
652 STD_TORCH_CHECK_NOT_IMPLEMENTED(
653 false,
654 "No compiled cutlass_fp4_group_mm kernel for CUDA device capability: ",
655 version_num, ". Required capability: 100 or 120");
656 }
657
658 #if (defined ENABLE_NVFP4_SM100 && ENABLE_NVFP4_SM100) || \
659 (defined ENABLE_NVFP4_SM120 && ENABLE_NVFP4_SM120)
660 constexpr auto FLOAT4_E2M1X2 = torch::headeronly::ScalarType::Byte;
661 constexpr auto SF_DTYPE = torch::headeronly::ScalarType::Float8_e4m3fn;
662 #endif
663
664 #define CHECK_TYPE(x, st, m) \
665 STD_TORCH_CHECK(x.scalar_type() == st, \
666 ": Inconsistency of torch::stable::Tensor type:", m)
667 #define CHECK_TH_CUDA(x, m) \
668 STD_TORCH_CHECK(x.is_cuda(), m, ": must be a CUDA tensor.")
669 #define CHECK_CONTIGUOUS(x, m) \
670 STD_TORCH_CHECK(x.is_contiguous(), m, ": must be contiguous.")
671 #define CHECK_INPUT(x, st, m) \
672 CHECK_TH_CUDA(x, m); \
673 CHECK_CONTIGUOUS(x, m); \
674 CHECK_TYPE(x, st, m)
675
676 void cutlass_fp4_group_mm(torch::stable::Tensor& output,
677 const torch::stable::Tensor& a,
678 const torch::stable::Tensor& b,
679 const torch::stable::Tensor& a_blockscale,
680 const torch::stable::Tensor& b_blockscales,
681 const torch::stable::Tensor& alphas,
682 const torch::stable::Tensor& problem_sizes,
683 const torch::stable::Tensor& expert_offsets,
684 const torch::stable::Tensor& sf_offsets) {
685 #if (defined ENABLE_NVFP4_SM100 && ENABLE_NVFP4_SM100) || \
686 (defined ENABLE_NVFP4_SM120 && ENABLE_NVFP4_SM120)
687 // Input validation
688 CHECK_INPUT(a, FLOAT4_E2M1X2, "a");
689 CHECK_INPUT(b, FLOAT4_E2M1X2, "b");
690 CHECK_INPUT(a_blockscale, SF_DTYPE, "a_blockscale");
691 CHECK_INPUT(b_blockscales, SF_DTYPE, "b_blockscales");
692 CHECK_INPUT(alphas, torch::headeronly::ScalarType::Float, "alphas");
693
694 STD_TORCH_CHECK(
695 a_blockscale.dim() == 2,
696 "expected a_blockscale to be of shape [num_experts, rounded_m,"
697 " k // group_size], observed rank: ",
698 a_blockscale.dim())
699 STD_TORCH_CHECK(b_blockscales.dim() == 3,
700 "expected b_blockscale to be of shape: "
701 " [num_experts, n, k // group_size], observed rank: ",
702 b_blockscales.dim())
703 STD_TORCH_CHECK(problem_sizes.dim() == 2,
704 "problem_sizes must be a 2D tensor");
705 STD_TORCH_CHECK(problem_sizes.size(1) == 3,
706 "problem_sizes must have the shape (num_experts, 3)");
707 STD_TORCH_CHECK(
708 problem_sizes.size(0) == expert_offsets.size(0),
709 "Number of experts in problem_sizes must match expert_offsets");
710 STD_TORCH_CHECK(
711 problem_sizes.scalar_type() == torch::headeronly::ScalarType::Int,
712 "problem_sizes must be int32.");
713
714 int M = static_cast<int>(a.size(0));
715 int N = static_cast<int>(b.size(1));
716 int E = static_cast<int>(b.size(0));
717 int K = static_cast<int>(2 * b.size(2));
718
719 if (output.scalar_type() == torch::headeronly::ScalarType::BFloat16) {
720 run_fp4_blockwise_scaled_group_mm<cutlass::bfloat16_t>(
721 output, a, b, a_blockscale, b_blockscales, alphas, problem_sizes,
722 expert_offsets, sf_offsets, M, N, K);
723 } else {
724 #if defined ENABLE_NVFP4_SM120 && ENABLE_NVFP4_SM120
725 int32_t version_num = get_sm_version_num();
726 if (version_num >= 120 && version_num < 130) {
727 STD_TORCH_CHECK_NOT_IMPLEMENTED(
728 false, "SM120 NVFP4 MOE only supports bfloat16 output, got: ",
729 output.scalar_type());
730 }
731 #endif
732 run_fp4_blockwise_scaled_group_mm<cutlass::half_t>(
733 output, a, b, a_blockscale, b_blockscales, alphas, problem_sizes,
734 expert_offsets, sf_offsets, M, N, K);
735 }
736 #else
737 STD_TORCH_CHECK_NOT_IMPLEMENTED(
738 false,
739 "No compiled cutlass_fp4_group_mm kernel, vLLM must "
740 "be compiled with ENABLE_NVFP4_SM100 or ENABLE_NVFP4_SM120 for SM100/120 "
741 "and CUDA 12.8 or above.");
742 #endif
743 }
744
745 STABLE_TORCH_LIBRARY_IMPL(_C, CUDA, m) {
746 m.impl("cutlass_fp4_group_mm", TORCH_BOX(&cutlass_fp4_group_mm));
747 }