main
cu 432 lines 18.5 KB
Raw
1 //
2 // Based off of:
3 // https://github.com/NVIDIA/cutlass/blob/main/examples/55_hopper_mixed_dtype_gemm/55_hopper_int4_fp8_gemm.cu
4 //
5
6 #include <torch/csrc/stable/library.h>
7 #include <torch/csrc/stable/tensor.h>
8 #include "libtorch_stable/torch_utils.h"
9 #include "libtorch_stable/cutlass_extensions/torch_utils.hpp"
10 #include "w4a8_utils.cuh"
11
12 #include "cutlass/cutlass.h"
13 #include <limits>
14
15 #include "cute/tensor.hpp"
16 #include "cutlass/gemm/collective/collective_builder.hpp"
17 #include "cutlass/epilogue/collective/collective_builder.hpp"
18 #include "cutlass/gemm/device/gemm_universal_adapter.h"
19 #include "cutlass/gemm/kernel/gemm_universal.hpp"
20
21 #include "cutlass/util/packed_stride.hpp"
22 #include "cutlass/util/mixed_dtype_utils.hpp"
23
24 #include "libtorch_stable/cutlass_extensions/common.hpp"
25 #include "libtorch_stable/cutlass_extensions/epilogue/scaled_mm_epilogues_c3x.hpp"
26
27 #include <cuda_runtime.h>
28
29 namespace vllm::cutlass_w4a8 {
30
31 using namespace cute;
32
33 // -------------------------------------------------------------------------------------
34 // Static configuration shared across all instantiations
35 // -------------------------------------------------------------------------------------
36 using MmaType = cutlass::float_e4m3_t; // A/scale element type
37 using QuantType = cutlass::int4b_t; // B element type (packed int4)
38
39 static int constexpr TileShapeK = 128 * 8 / sizeof_bits<MmaType>::value;
40 static int constexpr ScalePackSize = 8; // pack 8 scale elements together
41 static int constexpr PackFactor = 8; // 8 4-bit packed into int32
42
43 // A matrix configuration
44 using ElementA = MmaType; // Element type for A matrix operand
45 using LayoutA = cutlass::layout::RowMajor; // Layout type for A matrix operand
46 using LayoutA_Transpose =
47 typename cutlass::layout::LayoutTranspose<LayoutA>::type;
48 constexpr int AlignmentA =
49 128 / cutlass::sizeof_bits<
50 ElementA>::value; // Memory access granularity/alignment of A
51 // matrix in units of elements (up to 16 bytes)
52 using StrideA = cutlass::detail::TagToStrideA_t<LayoutA>;
53
54 // B matrix configuration
55 using ElementB = QuantType; // Element type for B matrix operand
56 using LayoutB =
57 cutlass::layout::ColumnMajor; // Layout type for B matrix operand
58 using LayoutB_Transpose =
59 typename cutlass::layout::LayoutTranspose<LayoutB>::type;
60 constexpr int AlignmentB =
61 128 / cutlass::sizeof_bits<
62 ElementB>::value; // Memory access granularity/alignment of B
63 // matrix in units of elements (up to 16 bytes)
64 using StrideB = cutlass::detail::TagToStrideB_t<LayoutB>;
65
66 // Define the CuTe layout for reordered quantized tensor B
67 // LayoutAtomQuant places values that will be read by the same thread in
68 // contiguous locations in global memory. It specifies the reordering within a
69 // single warp's fragment
70 using LayoutAtomQuant =
71 decltype(cutlass::compute_memory_reordering_atom<MmaType>());
72 using LayoutB_Reordered = decltype(cute::tile_to_shape(
73 LayoutAtomQuant{}, Layout<Shape<int, int, int>, StrideB>{}));
74
75 // Group-wise scales
76 using ElementScale = MmaType;
77 using LayoutScale = cutlass::layout::RowMajor;
78
79 // Per-tok, per-chan scales
80 using ElementSChannel = float;
81
82 // C/D matrix configuration
83 using ElementC =
84 cutlass::bfloat16_t; // Element type for C and D matrix operands
85 using LayoutC =
86 cutlass::layout::RowMajor; // Layout type for C and D matrix operands
87 constexpr int AlignmentC =
88 128 / cutlass::sizeof_bits<
89 ElementC>::value; // Memory access granularity/alignment of C
90 // matrix in units of elements (up to 16 bytes)
91
92 using ElementD = ElementC;
93 using LayoutD = LayoutC;
94 constexpr int AlignmentD = 128 / cutlass::sizeof_bits<ElementD>::value;
95
96 // Core kernel configurations
97 using ElementAccumulator = float; // Element type for internal accumulation
98 using ElementCompute = float; // Element type for epilogue computation
99 using ArchTag = cutlass::arch::Sm90; // Tag indicating the minimum SM that
100 // supports the intended feature
101 using OperatorClass = cutlass::arch::OpClassTensorOp; // Operator class tag
102 using KernelSchedule =
103 cutlass::gemm::KernelTmaWarpSpecializedCooperative; // Kernel to launch
104 // based on the default
105 // setting in the
106 // Collective Builder
107 using EpilogueSchedule = cutlass::epilogue::TmaWarpSpecializedCooperative;
108 using EpilogueTileType = cutlass::epilogue::collective::EpilogueTileAuto;
109
110 // ----------------------------------------------------------------------------
111 // Kernel template — Tile/Cluster shapes
112 // ----------------------------------------------------------------------------
113 template <class TileShape_MN, class ClusterShape_MNK>
114 struct W4A8GemmKernel {
115 using TileShape =
116 decltype(cute::append(TileShape_MN{}, cute::Int<TileShapeK>{}));
117 using ClusterShape = ClusterShape_MNK;
118
119 // Epilogue per-tok, per-chan scales
120 using ChTokScalesEpilogue =
121 typename vllm::c3x::ScaledEpilogue<ElementAccumulator, ElementD,
122 TileShape>;
123 using EVTCompute = typename ChTokScalesEpilogue::EVTCompute;
124 using CollectiveEpilogue =
125 typename cutlass::epilogue::collective::CollectiveBuilder<
126 ArchTag, OperatorClass, TileShape, ClusterShape, EpilogueTileType,
127 ElementAccumulator, ElementSChannel,
128 // Transpose layout of D here since we use explicit swap + transpose
129 // the void type for C tells the builder to allocate 0 smem for the C
130 // matrix. We can enable this if beta == 0 by changing ElementC to
131 // void below.
132 ElementC, typename cutlass::layout::LayoutTranspose<LayoutC>::type,
133 AlignmentC, ElementD,
134 typename cutlass::layout::LayoutTranspose<LayoutD>::type, AlignmentD,
135 EpilogueSchedule, // This is the only epi supporting the required
136 // swap + transpose.
137 EVTCompute>::CollectiveOp;
138
139 // The Scale information must get paired with the operand that will be scaled.
140 // In this example, B is scaled so we make a tuple of B's information and the
141 // scale information.
142 using CollectiveMainloopShuffled =
143 typename cutlass::gemm::collective::CollectiveBuilder<
144 ArchTag, OperatorClass,
145 cute::tuple<ElementB, cutlass::Array<ElementScale, ScalePackSize>>,
146 LayoutB_Reordered, AlignmentB, ElementA, LayoutA_Transpose,
147 AlignmentA, ElementAccumulator, TileShape, ClusterShape,
148 cutlass::gemm::collective::StageCountAutoCarveout<static_cast<int>(
149 sizeof(typename CollectiveEpilogue::SharedStorage))>,
150 KernelSchedule>::CollectiveOp;
151
152 using GemmKernelShuffled = cutlass::gemm::kernel::GemmUniversal<
153 Shape<int, int, int, int>, // Indicates ProblemShape
154 CollectiveMainloopShuffled, CollectiveEpilogue>;
155 using GemmShuffled =
156 cutlass::gemm::device::GemmUniversalAdapter<GemmKernelShuffled>;
157
158 using StrideC = typename GemmKernelShuffled::StrideC;
159 using StrideD = typename GemmKernelShuffled::StrideD;
160 using StrideS = typename CollectiveMainloopShuffled::StrideScale;
161
162 static torch::stable::Tensor mm(
163 torch::stable::Tensor const& A,
164 torch::stable::Tensor const& B, // already packed
165 torch::stable::Tensor const& group_scales, // already packed
166 int64_t group_size, torch::stable::Tensor const& channel_scales,
167 torch::stable::Tensor const& token_scales,
168 std::optional<torch::headeronly::ScalarType> const& maybe_out_type) {
169 // TODO: param validation
170 int m = A.size(0);
171 int k = A.size(1);
172 int n = B.size(1);
173
174 // safely cast group_size to int
175 STD_TORCH_CHECK(
176 group_size > 0 && group_size <= std::numeric_limits<int>::max(),
177 "group_size out of supported range for int: ", group_size);
178 int const group_size_int = static_cast<int>(group_size);
179
180 // Allocate output
181 const torch::stable::accelerator::DeviceGuard device_guard(
182 A.get_device_index());
183 auto device = A.device();
184 auto stream = get_current_cuda_stream(device.index());
185 torch::stable::Tensor D = torch::stable::empty(
186 {m, n}, equivalent_scalar_type_v<ElementD>, std::nullopt, device);
187 // prepare arg pointers
188 auto A_ptr = static_cast<MmaType const*>(A.const_data_ptr());
189 auto B_ptr = static_cast<QuantType const*>(B.const_data_ptr());
190 auto D_ptr = static_cast<ElementD*>(D.data_ptr());
191 // can we avoid hardcode the 8 here
192 auto S_ptr =
193 static_cast<cutlass::Array<ElementScale, ScalePackSize> const*>(
194 group_scales.const_data_ptr());
195
196 // runtime layout for B
197 auto shape_B = cute::make_shape(n, k, 1);
198 LayoutB_Reordered layout_B_reordered =
199 cute::tile_to_shape(LayoutAtomQuant{}, shape_B);
200
201 // strides
202 int const scale_k = cutlass::ceil_div(k, group_size_int);
203 StrideA stride_A =
204 cutlass::make_cute_packed_stride(StrideA{}, cute::make_shape(m, k, 1));
205 // Reverse stride here due to swap and transpose
206 StrideD stride_D =
207 cutlass::make_cute_packed_stride(StrideD{}, cute::make_shape(n, m, 1));
208 StrideS stride_S = cutlass::make_cute_packed_stride(
209 StrideS{}, cute::make_shape(n, scale_k, 1));
210
211 // Create a structure of gemm kernel arguments suitable for invoking an
212 // instance of Gemm auto arguments =
213 // args_from_options<GemmShuffled>(options);
214 /// Populates a Gemm::Arguments structure from the given arguments
215 /// Swap the A and B tensors, as well as problem shapes here.
216 using Args = typename GemmShuffled::Arguments;
217 using MainloopArguments = typename GemmKernelShuffled::MainloopArguments;
218 using EpilogueArguments = typename GemmKernelShuffled::EpilogueArguments;
219
220 MainloopArguments mainloop_arguments{
221 B_ptr, layout_B_reordered, A_ptr, stride_A,
222 S_ptr, stride_S, group_size_int};
223
224 EpilogueArguments epilogue_arguments{
225 ChTokScalesEpilogue::prepare_args(channel_scales, token_scales),
226 nullptr,
227 {}, // no C
228 D_ptr,
229 stride_D};
230
231 Args arguments{cutlass::gemm::GemmUniversalMode::kGemm,
232 {n, m, k, 1}, // shape
233 mainloop_arguments,
234 epilogue_arguments};
235
236 // Workspace
237 size_t workspace_size = GemmShuffled::get_workspace_size(arguments);
238 torch::stable::Tensor workspace = torch::stable::empty(
239 workspace_size, torch::headeronly::ScalarType::Byte, std::nullopt,
240 device);
241
242 // Run GEMM
243 GemmShuffled gemm;
244 CUTLASS_CHECK(gemm.can_implement(arguments));
245 CUTLASS_CHECK(gemm.initialize(arguments, workspace.data_ptr(), stream));
246 CUTLASS_CHECK(gemm.run(stream));
247
248 return D;
249 }
250 };
251
252 // ----------------------------------------------------------------------------
253 // Kernel instantiations and dispatch logic
254 // ----------------------------------------------------------------------------
255 using Kernel_256x128_1x1x1 =
256 W4A8GemmKernel<Shape<_256, _128>, Shape<_1, _1, _1>>;
257 using Kernel_256x64_1x1x1 = W4A8GemmKernel<Shape<_256, _64>, Shape<_1, _1, _1>>;
258 using Kernel_256x32_1x1x1 = W4A8GemmKernel<Shape<_256, _32>, Shape<_1, _1, _1>>;
259 using Kernel_256x16_1x1x1 = W4A8GemmKernel<Shape<_256, _16>, Shape<_1, _1, _1>>;
260 using Kernel_128x256_2x1x1 =
261 W4A8GemmKernel<Shape<_128, _256>, Shape<_2, _1, _1>>;
262 using Kernel_128x256_1x1x1 =
263 W4A8GemmKernel<Shape<_128, _256>, Shape<_1, _1, _1>>;
264 using Kernel_128x128_1x1x1 =
265 W4A8GemmKernel<Shape<_128, _128>, Shape<_1, _1, _1>>;
266 using Kernel_128x64_1x1x1 = W4A8GemmKernel<Shape<_128, _64>, Shape<_1, _1, _1>>;
267 using Kernel_128x32_1x1x1 = W4A8GemmKernel<Shape<_128, _32>, Shape<_1, _1, _1>>;
268 using Kernel_128x16_1x1x1 = W4A8GemmKernel<Shape<_128, _16>, Shape<_1, _1, _1>>;
269
270 torch::stable::Tensor mm_dispatch(
271 torch::stable::Tensor const& A,
272 torch::stable::Tensor const& B, // already packed
273 torch::stable::Tensor const& group_scales, // already packed
274 int64_t group_size, torch::stable::Tensor const& channel_scales,
275 torch::stable::Tensor const& token_scales,
276 std::optional<torch::headeronly::ScalarType> const& maybe_out_type,
277 const std::string& schedule) {
278 if (schedule == "256x128_1x1x1") {
279 return Kernel_256x128_1x1x1::mm(A, B, group_scales, group_size,
280 channel_scales, token_scales,
281 maybe_out_type);
282 } else if (schedule == "256x64_1x1x1") {
283 return Kernel_256x64_1x1x1::mm(A, B, group_scales, group_size,
284 channel_scales, token_scales,
285 maybe_out_type);
286 } else if (schedule == "256x32_1x1x1") {
287 return Kernel_256x32_1x1x1::mm(A, B, group_scales, group_size,
288 channel_scales, token_scales,
289 maybe_out_type);
290 } else if (schedule == "256x16_1x1x1") {
291 return Kernel_256x16_1x1x1::mm(A, B, group_scales, group_size,
292 channel_scales, token_scales,
293 maybe_out_type);
294 } else if (schedule == "128x256_2x1x1") {
295 return Kernel_128x256_2x1x1::mm(A, B, group_scales, group_size,
296 channel_scales, token_scales,
297 maybe_out_type);
298 } else if (schedule == "128x256_1x1x1") {
299 return Kernel_128x256_1x1x1::mm(A, B, group_scales, group_size,
300 channel_scales, token_scales,
301 maybe_out_type);
302 } else if (schedule == "128x128_1x1x1") {
303 return Kernel_128x128_1x1x1::mm(A, B, group_scales, group_size,
304 channel_scales, token_scales,
305 maybe_out_type);
306 } else if (schedule == "128x64_1x1x1") {
307 return Kernel_128x64_1x1x1::mm(A, B, group_scales, group_size,
308 channel_scales, token_scales,
309 maybe_out_type);
310 } else if (schedule == "128x32_1x1x1") {
311 return Kernel_128x32_1x1x1::mm(A, B, group_scales, group_size,
312 channel_scales, token_scales,
313 maybe_out_type);
314 } else if (schedule == "128x16_1x1x1") {
315 return Kernel_128x16_1x1x1::mm(A, B, group_scales, group_size,
316 channel_scales, token_scales,
317 maybe_out_type);
318 }
319 STD_TORCH_CHECK(false, "Unknown W4A8 schedule: ", schedule);
320 return {};
321 }
322
323 torch::stable::Tensor mm(
324 torch::stable::Tensor const& A,
325 torch::stable::Tensor const& B, // already packed
326 torch::stable::Tensor const& group_scales, // already packed
327 int64_t group_size, torch::stable::Tensor const& channel_scales,
328 torch::stable::Tensor const& token_scales,
329 std::optional<torch::headeronly::ScalarType> const& maybe_out_type,
330 std::optional<std::string> maybe_schedule) {
331 // requested a specific schedule
332 if (maybe_schedule) {
333 return mm_dispatch(A, B, group_scales, group_size, channel_scales,
334 token_scales, maybe_out_type, *maybe_schedule);
335 }
336 std::string schedule;
337 int M = A.size(0);
338 int K = A.size(1);
339 int N = B.size(1);
340 // heuristic
341 if (M <= 16) {
342 schedule = (K == 16384 && N == 18432) ? "256x16_1x1x1" : "128x16_1x1x1";
343 } else if (M <= 32) {
344 schedule = (K == 16384 && N == 18432) ? "256x32_1x1x1" : "128x32_1x1x1";
345 } else if (M <= 64) {
346 if (K == 16384 && N == 18432)
347 schedule = "256x64_1x1x1";
348 else if (N <= 8192 && K <= 8192)
349 schedule = "128x32_1x1x1";
350 else
351 schedule = "128x64_1x1x1";
352 } else if (M <= 128) {
353 if (K == 16384 && N == 18432)
354 schedule = "256x128_1x1x1";
355 else if (N <= 8192)
356 schedule = "128x64_1x1x1";
357 else
358 schedule = "128x128_1x1x1";
359 } else if (M <= 256) {
360 if (N <= 4096)
361 schedule = "128x64_1x1x1";
362 else if (N <= 8192)
363 schedule = "128x128_1x1x1";
364 else
365 schedule = "128x256_1x1x1";
366 } else if (M <= 512 && N <= 4096) {
367 schedule = "128x128_1x1x1";
368 } else if (M <= 1024) {
369 schedule = "128x256_1x1x1";
370 } else {
371 schedule = "128x256_2x1x1";
372 }
373 return mm_dispatch(A, B, group_scales, group_size, channel_scales,
374 token_scales, maybe_out_type, schedule);
375 }
376
377 // ----------------------------------------------------------------------------
378 // Pre-processing utils
379 // ----------------------------------------------------------------------------
380 torch::stable::Tensor pack_scale_fp8(torch::stable::Tensor const& scales) {
381 STD_TORCH_CHECK(scales.scalar_type() ==
382 torch::headeronly::ScalarType::Float8_e4m3fn);
383 STD_TORCH_CHECK(scales.is_contiguous());
384 STD_TORCH_CHECK(scales.is_cuda());
385
386 auto packed_scales =
387 torch::stable::empty({scales.numel() * ScalePackSize},
388 scales.scalar_type(), std::nullopt, scales.device());
389 auto scales_ptr = static_cast<MmaType const*>(scales.const_data_ptr());
390 auto packed_scales_ptr =
391 static_cast<cutlass::Array<ElementScale, ScalePackSize>*>(
392 packed_scales.data_ptr());
393
394 cutlass::pack_scale_fp8(scales_ptr, packed_scales_ptr, scales.numel());
395
396 return packed_scales;
397 }
398
399 torch::stable::Tensor encode_and_reorder_int4b(torch::stable::Tensor const& B) {
400 STD_TORCH_CHECK(B.scalar_type() == torch::headeronly::ScalarType::Int);
401 STD_TORCH_CHECK(B.dim() == 2);
402
403 torch::stable::Tensor B_packed = torch::stable::empty_like(B);
404
405 int k = B.size(0) * PackFactor; // logical k
406 int n = B.size(1);
407 STD_TORCH_CHECK((n * k) % 32 == 0,
408 "need multiples of 32 int4s for 16B chunks");
409
410 auto B_ptr = static_cast<QuantType const*>(B.const_data_ptr());
411 auto B_packed_ptr = static_cast<QuantType*>(B_packed.data_ptr());
412 auto shape_B = cute::make_shape(n, k, 1);
413 auto layout_B = make_layout(shape_B, LayoutRight{}); // row major
414 LayoutB_Reordered layout_B_reordered =
415 cute::tile_to_shape(LayoutAtomQuant{}, shape_B);
416
417 bool ok = vllm::cutlass_w4a8_utils::unified_encode_int4b(B_ptr, B_packed_ptr,
418 n * k);
419 STD_TORCH_CHECK(ok, "unified_encode_int4b failed");
420 cutlass::reorder_tensor(B_packed_ptr, layout_B, layout_B_reordered);
421
422 return B_packed;
423 }
424
425 STABLE_TORCH_LIBRARY_IMPL(_C, CUDA, m) {
426 m.impl("cutlass_w4a8_mm", TORCH_BOX(&mm));
427 m.impl("cutlass_pack_scale_fp8", TORCH_BOX(&pack_scale_fp8));
428 m.impl("cutlass_encode_and_reorder_int4b",
429 TORCH_BOX(&encode_and_reorder_int4b));
430 }
431
432 } // namespace vllm::cutlass_w4a8