main
cuh 992 lines 37.5 KB
Raw
1 #pragma once
2
3 #include "cutlass/numeric_conversion.h"
4 #include "cutlass_extensions/vllm_custom_types.cuh"
5 #include "cutlass_extensions/cute_utils.cuh"
6 #include "cutlass_extensions/vllm_type_utils.cuh"
7
8 // this file extends:
9 // https://github.com/NVIDIA/cutlass/blob/cutlass-3.5.0/include/cutlass/numeric_conversion.h
10 // with vllm specific type conversions, namely: vllm_uint4b8_t, vllm_uint8b128_t
11 // as well as adds interleaved numeric array converters for specific types.
12 // (interleaved numeric array converters can be more efficient for subbyte
13 // types)
14
15 namespace cutlass {
16
17 // InterleavedNumericArrayConverter is like NumericArrayConverter but also
18 // deinterleaves converted elements based on IlvBlkLayout, interleaving can
19 // make subbyte converts more efficient by allowing for efficient extraction
20 // of subbyte elements from a 32bit register.
21 template <typename IlvBlkLayout, typename T, typename S, int N,
22 FloatRoundStyle Round = FloatRoundStyle::round_to_nearest,
23 class Enable = void>
24 struct InterleavedNumericArrayConverter {
25 using Converter = NumericArrayConverter<T, S, N, Round>;
26
27 using result_type = typename Converter::result_type;
28 using source_type = typename Converter::source_type;
29
30 CUTLASS_DEVICE
31 static result_type convert(source_type const& source) {
32 if (cute::elect_one_sync()) {
33 if constexpr (std::is_same_v<IlvBlkLayout, void>) {
34 printf(
35 "Convert %s <= %s (N = %d, IlvBlkLayout = void), not implemented\n",
36 nameof_v<T>, nameof_v<S>, N);
37 } else {
38 printf(
39 "Convert %s <= %s (N = %d, size(IlvBlkLayout{}) = %d), not "
40 "implemented\n",
41 nameof_v<T>, nameof_v<S>, N, size(IlvBlkLayout{}));
42 }
43 __brkpt();
44 }
45 return {};
46 }
47
48 CUTLASS_DEVICE
49 result_type operator()(source_type const& s) const { return convert(s); }
50 };
51
52 template <typename IlvBlkLayout, typename T, typename S, int N,
53 FloatRoundStyle Round>
54 struct InterleavedNumericArrayConverter<
55 IlvBlkLayout, T, S, N, Round,
56 std::enable_if_t<is_identity_layout<IlvBlkLayout>()>> {
57 using Converter = NumericArrayConverter<T, S, N, Round>;
58
59 using result_type = typename Converter::result_type;
60 using source_type = typename Converter::source_type;
61
62 CUTLASS_DEVICE
63 static result_type convert(source_type const& source) {
64 return Converter::convert(source);
65 }
66
67 CUTLASS_DEVICE
68 result_type operator()(source_type const& s) const { return convert(s); }
69 };
70
71 template <typename RegConvert32bit, typename T, typename S, int N>
72 struct ArrayConverterPacked32Bit {
73 using result_type = Array<T, N>;
74 using source_type = Array<S, N>;
75
76 using result_packed_8_t = Array<T, 8>;
77 using result_packed_4_t = Array<T, 4>;
78 using result_packed_2_t = Array<T, 2>;
79 using src_packed_8_t = Array<S, 8>;
80 using src_packed_4_t = Array<S, 4>;
81 using src_packed_2_t = Array<S, 2>;
82
83 static_assert(N % 2 == 0, "N must be a multiple of 2");
84 static_assert(cutlass::sizeof_bits_v<S> >= 4); // TODO: add 16 packed sources
85 static_assert(32 % cutlass::sizeof_bits_v<S> == 0);
86 static constexpr auto src_elems_per_32bit_reg =
87 32 / cutlass::sizeof_bits_v<S>;
88
89 // Maybe not Valid. ScalarConverter will not actually work unless
90 // NumericConverter<T, S, Round> is implemented. However it won't be used
91 // anyways since we assert N % 2 == 0, just here for compliance with
92 // VectorizedConverter.
93 using ScalarConverter = NumericConverter<T, S>;
94
95 template <typename PackedSrc>
96 CUTLASS_DEVICE static auto to_regs(PackedSrc const& src) {
97 if constexpr (sizeof(PackedSrc) == 1) {
98 return Array<uint32_t, 1>{reinterpret_cast<uint8_t const&>(src)};
99 } else if constexpr (sizeof(PackedSrc) == 2) {
100 return Array<uint32_t, 1>{reinterpret_cast<uint16_t const&>(src)};
101 } else if constexpr (sizeof(PackedSrc) == 4) {
102 return Array<uint32_t, 1>{reinterpret_cast<uint32_t const&>(src)};
103 } else {
104 static_assert(sizeof(PackedSrc) == 8);
105 return reinterpret_cast<Array<uint32_t, 2> const&>(src);
106 }
107 }
108
109 // The core converter uses bit tricks to construct a known FP16 number, then
110 // does a subtraction in FP16 for the final result.
111 template <typename PackedResultType, typename PackedSrcType>
112 CUTLASS_DEVICE static PackedResultType packed_convert(
113 PackedSrcType const& source) {
114 static_assert(PackedSrcType::kElements == PackedResultType::kElements);
115 static_assert(PackedResultType::kElements == 2 ||
116 PackedResultType::kElements == 4 ||
117 PackedResultType::kElements == 8,
118 "Invalid PackedResultType must be 2, 4 or 8.");
119 static_assert(std::is_same_v<typename PackedSrcType::Element, S>);
120 static_assert(std::is_same_v<typename PackedResultType::Element, T>);
121
122 return RegConvert32bit::template convert<PackedResultType>(to_regs(source));
123 }
124
125 friend class detail::VectorizedConverter;
126
127 public:
128 CUTLASS_DEVICE static result_type convert(source_type const& source) {
129 result_type result;
130 using ConverterType =
131 ArrayConverterPacked32Bit<RegConvert32bit,
132 typename result_type::Element,
133 typename source_type::Element, N>;
134
135 if constexpr (src_elems_per_32bit_reg >= 8) {
136 detail::VectorizedConverter::convert<
137 ConverterType, result_packed_8_t, src_packed_8_t, result_packed_4_t,
138 src_packed_4_t, result_packed_2_t, src_packed_2_t>(result, source);
139 } else if constexpr (src_elems_per_32bit_reg >= 4) {
140 detail::VectorizedConverter::convert<ConverterType, result_packed_4_t,
141 src_packed_4_t, result_packed_2_t,
142 src_packed_2_t>(result, source);
143 } else {
144 detail::VectorizedConverter::convert<ConverterType, result_packed_2_t,
145 src_packed_2_t>(result, source);
146 }
147
148 return result;
149 }
150 };
151
152 // Convert 8 4bit values packed into a 32bit register to 8 8bit values packed
153 // into 2 32bit register.
154 template <uint8_t LUT0, uint8_t LUT1, uint8_t LUT2, uint8_t LUT3, //
155 uint8_t LUT4, uint8_t LUT5, uint8_t LUT6, uint8_t LUT7, //
156 uint8_t LUT8, uint8_t LUT9, uint8_t LUT10, uint8_t LUT11, //
157 uint8_t LUT12, uint8_t LUT13, uint8_t LUT14, uint8_t LUT15>
158 CUTLASS_DEVICE cutlass::AlignedArray<uint32_t, 2> lut_4bit_to_8bit_convert(
159 uint32_t src) {
160 cutlass::AlignedArray<uint32_t, 2> r;
161 // Determines if the value is in the top half of the LUT if set or
162 // (i.e. LUT[8:15]) in the bottom half (i.e. LUT[0:7]) if not set. Then move
163 // into bit position 0x4 of each nibble so when or'd with final_prmt_base it
164 // selects the correct candidate. When elements in final_prmt_base
165 // are >= 0x4, the high candidate is selected (i.e. LUT[8:15]), when elements
166 // are < 0x4, the low candidate is selected (i.e. LUT[0:7])
167 uint32_t high_bit = (src & 0x88888888) >> 1;
168
169 // `high_bit` is OR'd with 0x31203120 to find the correct value in the LUT
170 // (selects correct high or low candidate)
171 const uint32_t final_prmt_base = 0x32103210;
172
173 // Ignore the high bit when indexing into LUT, for each 4bit value
174 // we index into both the high and low candidates then use
175 // high_bit | final_prmt_base to select the correct candidate
176 uint32_t lut_idx = (src & 0x77777777);
177
178 auto pack = [](uint8_t a, uint8_t b, uint8_t c, uint8_t d) {
179 return uint32_t(a) | (uint32_t(b) << 8) | (uint32_t(c) << 16) |
180 (uint32_t(d) << 24);
181 };
182
183 static constexpr uint32_t LOW_0 = pack(LUT0, LUT1, LUT2, LUT3);
184 static constexpr uint32_t LOW_1 = pack(LUT4, LUT5, LUT6, LUT7);
185 static constexpr uint32_t HIGH_0 = pack(LUT8, LUT9, LUT10, LUT11);
186 static constexpr uint32_t HIGH_1 = pack(LUT12, LUT13, LUT14, LUT15);
187
188 CUTLASS_PRAGMA_UNROLL
189 for (int ii = 0; ii < 2; ++ii, lut_idx >>= 16, high_bit >>= 16) {
190 uint32_t final_prmt_idx = final_prmt_base | high_bit;
191
192 // This uses a look up table to convert packed int4s to packed int8s,
193 // using the int4 value as the index to prmt. It first select both the
194 // high and low candidates, then uses the high bit (i.e. `high_bit`) to
195 // select the correct candidate.
196 asm volatile(
197 "{\n"
198 " .reg .b32 low, high;\n"
199 " prmt.b32 low, %1, %2, %5;\n"
200 " prmt.b32 high, %3, %4, %5;\n"
201 " prmt.b32 %0, low, high, %6;\n"
202 "}\n"
203 : "=r"(r[ii])
204 : "n"(LOW_0), "n"(LOW_1), "n"(HIGH_0), "n"(HIGH_1), "r"(lut_idx),
205 "r"(final_prmt_idx));
206 }
207
208 return r;
209 };
210
211 // for Array<int8_t, N> <= Array<vllm_uint4b8_t, N>
212 template <FloatRoundStyle Round, int N>
213 struct NumericArrayConverter<int8_t, vllm_uint4b8_t, N, Round> {
214 using result_type = Array<int8_t, N>;
215 using source_type = Array<vllm_uint4b8_t, N>;
216
217 static FloatRoundStyle const round_style = Round;
218
219 private:
220 struct RegConvert {
221 template <typename PackedResultType>
222 CUTLASS_DEVICE static PackedResultType convert(Array<uint32_t, 1> src_) {
223 // [-8, -7, -6, -5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5, 6, 7] as int8s
224 auto r = lut_4bit_to_8bit_convert<0xF8, 0xF9, 0xFA, 0xFB, //
225 0xFC, 0xFD, 0xFE, 0xFF, //
226 0x00, 0x01, 0x02, 0x03, //
227 0x04, 0x05, 0x06, 0x07>(src_[0]);
228 return reinterpret_cast<PackedResultType&>(r);
229 };
230 };
231
232 public:
233 CUTLASS_DEVICE
234 static result_type convert(source_type const& source) {
235 return ArrayConverterPacked32Bit<RegConvert, typename result_type::Element,
236 typename source_type::Element,
237 N>::convert(source);
238 }
239
240 CUTLASS_DEVICE
241 result_type operator()(source_type const& s) const { return convert(s); }
242 };
243
244 // for Array<cutlass::float_e4m3_t, N> <= Array<vllm_uint4b8_t, N>
245 template <FloatRoundStyle Round, int N>
246 struct NumericArrayConverter<cutlass::float_e4m3_t, vllm_uint4b8_t, N, Round> {
247 using result_type = Array<cutlass::float_e4m3_t, N>;
248 using source_type = Array<vllm_uint4b8_t, N>;
249
250 static FloatRoundStyle const round_style = Round;
251
252 private:
253 struct RegConvert {
254 template <typename PackedResultType>
255 CUTLASS_DEVICE static PackedResultType convert(Array<uint32_t, 1> src_) {
256 // [-8, -7, -6, -5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5, 6, 7] as fp8s
257 auto r = lut_4bit_to_8bit_convert<0xD0, 0xCE, 0xCC, 0xCA, //
258 0xC8, 0xC4, 0xC0, 0xB8, //
259 0x00, 0x38, 0x40, 0x44, //
260 0x48, 0x4A, 0x4C, 0x4E>(src_[0]);
261 return reinterpret_cast<PackedResultType&>(r);
262 };
263 };
264
265 public:
266 CUTLASS_DEVICE
267 static result_type convert(source_type const& source) {
268 return ArrayConverterPacked32Bit<RegConvert, typename result_type::Element,
269 typename source_type::Element,
270 N>::convert(source);
271 }
272
273 CUTLASS_DEVICE
274 result_type operator()(source_type const& s) const { return convert(s); }
275 };
276
277 // for Array<cutlass::half_t, N> <= Array<vllm_uint4b8_t, N>
278 template <FloatRoundStyle Round, int N>
279 struct NumericArrayConverter<cutlass::half_t, vllm_uint4b8_t, N, Round> {
280 using result_type = Array<cutlass::half_t, N>;
281 using source_type = Array<vllm_uint4b8_t, N>;
282
283 struct RegConvert {
284 template <typename PackedResultType>
285 CUTLASS_DEVICE static PackedResultType convert(Array<uint32_t, 1> src_) {
286 uint32_t src = src_[0];
287 using RegArray =
288 cutlass::AlignedArray<uint32_t, PackedResultType::kElements / 2,
289 sizeof(PackedResultType)>;
290 RegArray r;
291
292 // Below constructs the following temporary:
293 // fp16s_01 = {0x00, i4_01, 0x00, i4_01}
294 // fp16s_23 = {0x00, i4_23, 0x00, i4_23}
295 // fp16s_45 = {0x00, i4_45, 0x00, i4_45}
296 // fp16s_67 = {0x00, i4_67, 0x00, i4_67}
297 // We use inline asm instead of __byte_perm intrinsic since we don't want
298 // the documented (& 0x7) on the index. NVCC might be able to optimize it
299 // out since the index is a constexpr, but we choose to be safe about it
300 // here.
301 uint32_t prmt_indices[4] = {0x4040, 0x4141, 0x4242, 0x4343};
302 static_assert(RegArray::kElements <= 4,
303 "Too many inputs for F16 -> I4 vector converter");
304 CUTLASS_PRAGMA_UNROLL
305 for (int ii = 0; ii < RegArray::kElements; ++ii) {
306 asm volatile(
307 "{\n"
308 " prmt.b32 %0, %1, %2, %3;\n"
309 "}\n"
310 : "=r"(r[ii])
311 : "r"(src), "n"(0), "r"(prmt_indices[ii]));
312 }
313
314 // Since the stored 4bit values are biased by 8 we get stored_val = (x+8)
315 // we are trying to construct x and a fp16 value
316 // The below XOR does the following:
317 // 1) Sets the exponent bits of the FP16 to the correct value for the
318 // FP16 magic_num. We will be constructing {1024+16*(x1+8), 1024+(x0+8)},
319 // where x1 in the high nibble and x0 is the low nibble then using hfma
320 // to subtract 1032 from that
321 // The AND does the following:
322 // 1) Clear the set bits for the int4 we will ignore.
323 // We use lop3 so that we can use 1 instruction for AND and XOR.
324 static constexpr uint32_t xor_mask = 0x64006400;
325 static constexpr uint32_t and_mask = 0xFFF0FF0F;
326 static constexpr uint32_t immLut = (0xf0 & 0xcc) ^ 0xaa;
327
328 // For each operand, computes:
329 // r[i] = (r[i] & and_mask) ^ xor_mask
330 CUTLASS_PRAGMA_UNROLL
331 for (int ii = 0; ii < RegArray::kElements; ++ii) {
332 asm volatile(
333 "{\n"
334 " lop3.b32 %0, %0, %1, %2, %3;\n"
335 "}\n"
336 : "+r"(r[ii])
337 : "n"(and_mask), "n"(xor_mask), "n"(immLut));
338 }
339
340 // We will issue 2 hfmas that do the following:
341 // {x1, x0} = {1024+16*(x1+8), 1024+(x0+8)} * {1/16, 1} - {72, 1032}
342 // = {x1 + 1152, x0 + 1032} * {1/16, 1} - {72, 1032}
343 static constexpr uint32_t hfma_bias_rep = 0xD480E408; // {72, 1032}
344 static constexpr uint32_t hfma_scale_rep = 0x2C003C00; // {1 / 16, 1}
345
346 const half2& hfma_bias = reinterpret_cast<const half2&>(hfma_bias_rep);
347 const half2& hfma_scale = reinterpret_cast<const half2&>(hfma_scale_rep);
348 CUTLASS_PRAGMA_UNROLL
349 for (int ii = 0; ii < RegArray::kElements; ++ii) {
350 half2& fp16x2_val = reinterpret_cast<__half2&>(r[ii]);
351 fp16x2_val = __hfma2(hfma_scale, fp16x2_val, hfma_bias);
352 }
353
354 return reinterpret_cast<PackedResultType&>(r);
355 };
356 };
357
358 public:
359 CUTLASS_DEVICE
360 static result_type convert(source_type const& source) {
361 return ArrayConverterPacked32Bit<RegConvert, typename result_type::Element,
362 typename source_type::Element,
363 N>::convert(source);
364 }
365
366 CUTLASS_DEVICE
367 result_type operator()(source_type const& s) const { return convert(s); }
368 };
369
370 // for Array<cutlass::half_t, N> <= Array<vllm_uint4b8_t, N>
371 // for IlvdLayout: (2, 4):(4, 1)
372 template <FloatRoundStyle Round, int N>
373 struct InterleavedNumericArrayConverter<Layout<Shape<_2, _4>, Stride<_4, _1>>,
374 cutlass::half_t, vllm_uint4b8_t, N,
375 Round, void> {
376 using IlvdLayout = Layout<Shape<_2, _4>, Stride<_4, _1>>;
377 static_assert(N % size(IlvdLayout{}) == 0);
378
379 using result_type = Array<cutlass::half_t, N>;
380 using source_type = Array<vllm_uint4b8_t, N>;
381
382 static FloatRoundStyle const round_style = Round;
383
384 private:
385 struct RegConvert {
386 template <typename PackedResultType>
387 CUTLASS_DEVICE static PackedResultType convert(Array<uint32_t, 1> src_) {
388 uint32_t src = src_[0];
389 using RegArray =
390 cutlass::AlignedArray<uint32_t, PackedResultType::kElements / 2,
391 sizeof(PackedResultType)>;
392 RegArray r;
393
394 static_assert(PackedResultType::kElements <= size(IlvdLayout{}));
395 static constexpr uint32_t xor_mask = 0x64006400;
396
397 for (int ii = 0; ii < RegArray::kElements; ii += 2) {
398 auto src_ = src >> (4 * (ii));
399 r[ii + 0] = src_;
400 r[ii + 1] = src_;
401
402 static constexpr uint32_t and_xor_imm_lut = (0xf0 & 0xcc) ^ 0xaa;
403
404 static constexpr uint32_t low_nib_mask = 0x000F000F;
405 static constexpr uint32_t high_nib_mask = 0x00F000F0;
406
407 asm volatile(
408 "{\n"
409 " lop3.b32 %0, %0, %1, %2, %3;\n"
410 "}\n"
411 : "+r"(r[ii + 0])
412 : "n"(low_nib_mask), "n"(xor_mask), "n"(and_xor_imm_lut));
413
414 asm volatile(
415 "{\n"
416 " lop3.b32 %0, %0, %1, %2, %3;\n"
417 "}\n"
418 : "+r"(r[ii + 1])
419 : "n"(high_nib_mask), "n"(xor_mask), "n"(and_xor_imm_lut));
420
421 // For low nibble:
422 // {x1, x0} = {1024+(x1+8), 1024+(x0+8)} * {1, 1} - {1032, 1032}
423 // For high nibble:
424 // {x1, x0} = {1024+16*(x1+8), 1024+16*(x0+8)} * {1/16, 1/16}
425 // - {72, 72}
426 static constexpr uint32_t low_nib_bias = 0x64086408; // {1032, 1032}
427 static constexpr uint32_t high_nib_scale = 0x2C002C00; // {1/16, 1/16}
428 static constexpr uint32_t high_nib_bias = 0xD480D480; // {-72, -72}
429
430 {
431 half2& fp16x2_val = reinterpret_cast<__half2&>(r[ii + 0]);
432 fp16x2_val =
433 __hsub2(fp16x2_val, reinterpret_cast<const half2&>(low_nib_bias));
434 }
435
436 {
437 half2& fp16x2_val = reinterpret_cast<__half2&>(r[ii + 1]);
438 fp16x2_val = __hfma2(fp16x2_val,
439 reinterpret_cast<const half2&>(high_nib_scale),
440 reinterpret_cast<const half2&>(high_nib_bias));
441 }
442 }
443
444 return reinterpret_cast<PackedResultType&>(r);
445 };
446 };
447
448 public:
449 CUTLASS_DEVICE
450 static result_type convert(source_type const& source) {
451 return ArrayConverterPacked32Bit<RegConvert, typename result_type::Element,
452 typename source_type::Element,
453 N>::convert(source);
454 }
455
456 CUTLASS_DEVICE
457 result_type operator()(source_type const& s) const { return convert(s); }
458 };
459
460 // for Array<cutlass::half_t, N> <= Array<uint4_t, N>
461 // for IlvdLayout: (2, 4):(4, 1)
462 template <FloatRoundStyle Round, int N>
463 struct InterleavedNumericArrayConverter<Layout<Shape<_2, _4>, Stride<_4, _1>>,
464 cutlass::half_t, uint4_t, N, Round,
465 void> {
466 using IlvdLayout = Layout<Shape<_2, _4>, Stride<_4, _1>>;
467 static_assert(N % size(IlvdLayout{}) == 0);
468
469 using result_type = Array<cutlass::half_t, N>;
470 using source_type = Array<uint4_t, N>;
471
472 static FloatRoundStyle const round_style = Round;
473
474 private:
475 struct RegConvert {
476 template <typename PackedResultType>
477 CUTLASS_DEVICE static PackedResultType convert(Array<uint32_t, 1> src_) {
478 uint32_t src = src_[0];
479 using RegArray =
480 cutlass::AlignedArray<uint32_t, PackedResultType::kElements / 2,
481 sizeof(PackedResultType)>;
482 RegArray r;
483
484 static_assert(PackedResultType::kElements <= size(IlvdLayout{}));
485 static constexpr uint32_t xor_mask = 0x64006400;
486
487 for (int ii = 0; ii < RegArray::kElements; ii += 2) {
488 auto src_ = src >> (4 * (ii));
489 r[ii + 0] = src_;
490 r[ii + 1] = src_;
491
492 static constexpr uint32_t and_xor_imm_lut = (0xf0 & 0xcc) ^ 0xaa;
493
494 static constexpr uint32_t low_nib_mask = 0x000F000F;
495 static constexpr uint32_t high_nib_mask = 0x00F000F0;
496
497 asm volatile(
498 "{\n"
499 " lop3.b32 %0, %0, %1, %2, %3;\n"
500 "}\n"
501 : "+r"(r[ii + 0])
502 : "n"(low_nib_mask), "n"(xor_mask), "n"(and_xor_imm_lut));
503
504 asm volatile(
505 "{\n"
506 " lop3.b32 %0, %0, %1, %2, %3;\n"
507 "}\n"
508 : "+r"(r[ii + 1])
509 : "n"(high_nib_mask), "n"(xor_mask), "n"(and_xor_imm_lut));
510
511 // For low nibble:
512 // {x1, x0} = {1024+x1, 1024+x0} - {1024, 1024}
513 // For high nibble:
514 // {x1, x0} = {1024+16*x1, 1024+16*x0} * {1/16, 1/16} - {64, 64}
515 static constexpr uint32_t low_nib_bias = 0x64006400; // {1024, 1024}
516 static constexpr uint32_t high_nib_scale = 0x2C002C00; // {1/16, 1/16}
517 static constexpr uint32_t high_nib_bias = 0xD400D400; // {-64, -64}
518
519 {
520 half2& fp16x2_val = reinterpret_cast<__half2&>(r[ii + 0]);
521 fp16x2_val =
522 __hsub2(fp16x2_val, reinterpret_cast<const half2&>(low_nib_bias));
523 }
524
525 {
526 half2& fp16x2_val = reinterpret_cast<__half2&>(r[ii + 1]);
527 fp16x2_val = __hfma2(fp16x2_val,
528 reinterpret_cast<const half2&>(high_nib_scale),
529 reinterpret_cast<const half2&>(high_nib_bias));
530 }
531 }
532
533 return reinterpret_cast<PackedResultType&>(r);
534 };
535 };
536
537 public:
538 CUTLASS_DEVICE
539 static result_type convert(source_type const& source) {
540 return ArrayConverterPacked32Bit<RegConvert, typename result_type::Element,
541 typename source_type::Element,
542 N>::convert(source);
543 }
544
545 CUTLASS_DEVICE
546 result_type operator()(source_type const& s) const { return convert(s); }
547 };
548
549 // for Array<cutlass::half_t, N> <= Array<vllm_uint8b128_t, N>
550 template <FloatRoundStyle Round, int N>
551 struct NumericArrayConverter<cutlass::half_t, vllm_uint8b128_t, N, Round> {
552 using result_type = Array<cutlass::half_t, N>;
553 using source_type = Array<vllm_uint8b128_t, N>;
554
555 struct RegConvert {
556 template <typename PackedResultType>
557 CUTLASS_DEVICE static PackedResultType convert(Array<uint32_t, 1> src_) {
558 uint32_t src = src_[0];
559 // Hold output FP16s in reg. We need 1 reg for every 2 elements
560 using RegArray =
561 cutlass::AlignedArray<uint32_t, PackedResultType::kElements / 2,
562 sizeof(PackedResultType)>;
563 RegArray r;
564
565 uint32_t const prmt_indices[2] = {0x5150, 0x5352};
566 static constexpr uint32_t start_byte_for_fp16 = 0x64646464;
567
568 for (int ii = 0; ii < RegArray::kElements; ++ii) {
569 asm volatile("prmt.b32 %0,%1,%2,%3;\n"
570 : "=r"(r[ii])
571 : "r"(src), "n"(start_byte_for_fp16),
572 "r"(prmt_indices[ii]));
573 }
574
575 // -128 is folded into bias subtraction, i.e. the 0x80 in the low bytes
576 static constexpr uint32_t bias_rep = 0x64806480;
577 const half2& bias = reinterpret_cast<const half2&>(bias_rep);
578 CUTLASS_PRAGMA_UNROLL
579 for (int ii = 0; ii < RegArray::kElements; ++ii) {
580 half2& fp16x2_val = reinterpret_cast<__half2&>(r[ii]);
581 fp16x2_val = __hsub2(fp16x2_val, bias);
582 }
583
584 return reinterpret_cast<PackedResultType&>(r);
585 };
586 };
587
588 public:
589 CUTLASS_DEVICE
590 static result_type convert(source_type const& source) {
591 return ArrayConverterPacked32Bit<RegConvert, typename result_type::Element,
592 typename source_type::Element,
593 N>::convert(source);
594 }
595
596 CUTLASS_DEVICE
597 result_type operator()(source_type const& s) const { return convert(s); }
598 };
599
600 // for Array<cutlass::float, N> <= Array<vllm_uint8b128_t, N>
601 template <FloatRoundStyle Round, int N>
602 struct NumericArrayConverter<float, vllm_uint8b128_t, N, Round> {
603 using result_type = Array<float, N>;
604 using source_type = Array<vllm_uint8b128_t, N>;
605 static FloatRoundStyle const round_style = Round;
606
607 private:
608 struct RegConvert {
609 template <typename PackedResultType>
610 CUTLASS_DEVICE static PackedResultType convert(Array<uint32_t, 1> src_) {
611 uint32_t src = src_[0];
612 PackedResultType r;
613
614 // __byte_perm simulates the add.u32 0x4B000000 to every u8 element of
615 // u8x4 source and stores the result in r (without introducing extra
616 // cvt.u32.u8 instruction)
617 uint32_t const prmt_indices[4] = {0x7650, 0x7651, 0x7652, 0x7653};
618 uint32_t* result_as_int = reinterpret_cast<uint32_t*>(&r);
619 for (int ii = 0; ii < PackedResultType::kElements; ++ii) {
620 result_as_int[ii] = __byte_perm(src, 0x4B000000, prmt_indices[ii]);
621 // Subtract the magic number 0x4B000000 from tmp in floating-point
622 // arithmetic to obtain final result
623 r[ii] -= (8388608.f + 128.f); // fold in -128 bias
624 }
625
626 return r;
627 };
628 };
629
630 public:
631 CUTLASS_DEVICE
632 static result_type convert(source_type const& source) {
633 return ArrayConverterPacked32Bit<RegConvert, typename result_type::Element,
634 typename source_type::Element,
635 N>::convert(source);
636 }
637
638 CUTLASS_DEVICE
639 result_type operator()(source_type const& s) const { return convert(s); }
640 };
641
642 #if defined(__CUDA_ARCH__) && (__CUDA_ARCH__ >= 800)
643
644 // for Array<cutlass::bfloat16_t, N> <= Array<vllm_uint4b8_t, N>
645 template <FloatRoundStyle Round, int N>
646 struct NumericArrayConverter<cutlass::bfloat16_t, vllm_uint4b8_t, N, Round> {
647 using result_type = Array<cutlass::bfloat16_t, N>;
648 using source_type = Array<vllm_uint4b8_t, N>;
649
650 static FloatRoundStyle const round_style = Round;
651
652 private:
653 struct RegConvert {
654 template <typename PackedResultType>
655 CUTLASS_DEVICE static PackedResultType convert(Array<uint32_t, 1> src_) {
656 uint32_t src_reg = src_[0];
657 // Hold output BF16s in reg. We need 1 reg for every 2 elements
658 using RegArray =
659 cutlass::AlignedArray<uint32_t, PackedResultType::kElements / 2,
660 sizeof(PackedResultType)>;
661 RegArray r;
662 uint32_t src_reg_shifted = src_reg >> 4;
663
664 // Below constructs the following temporary:
665 uint32_t const prmt_indices[4] = {0xF4F0, 0xF5F1, 0xF6F2, 0xF7F3};
666 static_assert(RegArray::kElements <= 4,
667 "Too many inputs for uint4b8_t -> BF16 vector converter");
668 CUTLASS_PRAGMA_UNROLL
669 for (int ii = 0; ii < RegArray::kElements; ++ii) {
670 asm volatile(
671 "{\n"
672 " prmt.b32 %0, %1, %2, %3;\n"
673 "}\n"
674 : "=r"(r[ii])
675 : "r"(src_reg), "r"(src_reg_shifted), "r"(prmt_indices[ii]));
676 }
677
678 // Since the stored 4bit values are biased by 8 we get stored_val = (x+8)
679 // we are trying to construct x and a BF16 value
680 // The below XOR does the following:
681 // 1) Sets the exponent bits of the BF16 to the correct value for the
682 // BF16 magic_num. We will be constructing {128 + (x1+8), 128 + (x0+8)}
683 // and subtracting 136 to get {x1, x0}
684 static constexpr uint32_t xor_mask = 0x43004300;
685 static constexpr uint32_t and_mask = 0x000F000F;
686 static constexpr uint32_t immLut = (0xf0 & 0xcc) ^ 0xaa;
687
688 // For each operand, computes:
689 // r[i] = (r[i] & and_mask) ^ xor_mask
690 CUTLASS_PRAGMA_UNROLL
691 for (int ii = 0; ii < RegArray::kElements; ++ii) {
692 asm volatile(
693 "{\n"
694 " lop3.b32 %0, %0, %1, %2, %3;\n"
695 "}\n"
696 : "+r"(r[ii])
697 : "n"(and_mask), "n"(xor_mask), "n"(immLut));
698 }
699
700 // We will issue 2 bfmas that do the following:
701 // high BF16:
702 // hi_bf16 - 136, lo_bf16 - 136
703
704 // This is the BF16 {136, 136} represented as an integer.
705 static constexpr uint32_t bias_rep = 0x43084308;
706 const __nv_bfloat162& bias =
707 reinterpret_cast<const __nv_bfloat162&>(bias_rep);
708
709 CUTLASS_PRAGMA_UNROLL
710 for (int ii = 0; ii < RegArray::kElements; ++ii) {
711 __nv_bfloat162& bf16x2_val = reinterpret_cast<__nv_bfloat162&>(r[ii]);
712 bf16x2_val = __hsub2(bf16x2_val, bias);
713 }
714
715 return reinterpret_cast<PackedResultType&>(r);
716 }
717 };
718
719 public:
720 CUTLASS_DEVICE
721 static result_type convert(source_type const& source) {
722 return ArrayConverterPacked32Bit<RegConvert, typename result_type::Element,
723 typename source_type::Element,
724 N>::convert(source);
725 }
726
727 CUTLASS_DEVICE
728 result_type operator()(source_type const& s) const { return convert(s); }
729 };
730
731 // for Array<cutlass::bfloat16_t, N> <= Array<vllm_uint4b8_t, N>
732 // for IlvdLayout: (2, 4):(4, 1)
733 template <FloatRoundStyle Round, int N>
734 struct InterleavedNumericArrayConverter<Layout<Shape<_2, _4>, Stride<_4, _1>>,
735 cutlass::bfloat16_t, vllm_uint4b8_t, N,
736 Round, void> {
737 using IlvdLayout = Layout<Shape<_2, _4>, Stride<_4, _1>>;
738 static_assert(N % size(IlvdLayout{}) == 0);
739
740 using result_type = Array<cutlass::bfloat16_t, N>;
741 using source_type = Array<vllm_uint4b8_t, N>;
742
743 private:
744 struct RegConvert {
745 template <typename PackedResultType>
746 CUTLASS_DEVICE static PackedResultType convert(Array<uint32_t, 1> src_) {
747 uint32_t src = src_[0];
748 using RegArray =
749 cutlass::AlignedArray<uint32_t, PackedResultType::kElements / 2,
750 sizeof(PackedResultType)>;
751 RegArray r;
752
753 static_assert(PackedResultType::kElements <= size(IlvdLayout{}));
754 static constexpr uint32_t or_mask = 0x43004300;
755
756 // Unlike float16 where the mantissa is large enough to contain 2
757 // nibbles, bfloat16 can only fit one, so we can only convert one
758 // nibble at a time
759 for (int ii = 0; ii < RegArray::kElements; ++ii) {
760 r[ii] = src >> (4 * ii);
761
762 static constexpr uint32_t and_or_imm_lut = (0xf0 & 0xcc) | 0xaa;
763 static constexpr uint32_t low_nib_mask = 0x000F000F;
764
765 asm volatile(
766 "{\n"
767 " lop3.b32 %0, %0, %1, %2, %3;\n"
768 "}\n"
769 : "+r"(r[ii + 0])
770 : "n"(low_nib_mask), "n"(or_mask), "n"(and_or_imm_lut));
771
772 // For low nibble:
773 // {x1, x0} = {128+(x1+8), 128+(x0+8)} * {1, 1} - {136, 136}
774 static constexpr uint32_t low_nib_bias = 0x43084308; // {136, 136}
775
776 {
777 __nv_bfloat162& fp16x2_val = reinterpret_cast<__nv_bfloat162&>(r[ii]);
778 fp16x2_val =
779 __hsub2(fp16x2_val,
780 reinterpret_cast<const __nv_bfloat162&>(low_nib_bias));
781 }
782 }
783
784 return reinterpret_cast<PackedResultType&>(r);
785 };
786 };
787
788 public:
789 CUTLASS_DEVICE
790 static result_type convert(source_type const& source) {
791 return ArrayConverterPacked32Bit<RegConvert, typename result_type::Element,
792 typename source_type::Element,
793 N>::convert(source);
794 }
795
796 CUTLASS_DEVICE
797 result_type operator()(source_type const& s) const { return convert(s); }
798 };
799
800 // for Array<cutlass::bfloat16_t, N> <= Array<uint4_t, N>
801 // for IlvdLayout: (2, 4):(4, 1)
802 template <FloatRoundStyle Round, int N>
803 struct InterleavedNumericArrayConverter<Layout<Shape<_2, _4>, Stride<_4, _1>>,
804 cutlass::bfloat16_t, uint4_t, N, Round,
805 void> {
806 using IlvdLayout = Layout<Shape<_2, _4>, Stride<_4, _1>>;
807 static_assert(N % size(IlvdLayout{}) == 0);
808
809 using result_type = Array<cutlass::bfloat16_t, N>;
810 using source_type = Array<uint4_t, N>;
811
812 private:
813 struct RegConvert {
814 template <typename PackedResultType>
815 CUTLASS_DEVICE static PackedResultType convert(Array<uint32_t, 1> src_) {
816 uint32_t src = src_[0];
817 using RegArray =
818 cutlass::AlignedArray<uint32_t, PackedResultType::kElements / 2,
819 sizeof(PackedResultType)>;
820 RegArray r;
821
822 static_assert(PackedResultType::kElements <= size(IlvdLayout{}));
823 static constexpr uint32_t or_mask = 0x43004300;
824
825 // Unlike float16 where the mantissa is large enough to contain 2
826 // nibbles, bfloat16 can only fit one, so we can only convert one
827 // nibble at a time
828 for (int ii = 0; ii < RegArray::kElements; ++ii) {
829 r[ii] = src >> (4 * ii);
830
831 static constexpr uint32_t and_or_imm_lut = (0xf0 & 0xcc) | 0xaa;
832 static constexpr uint32_t low_nib_mask = 0x000F000F;
833
834 asm volatile(
835 "{\n"
836 " lop3.b32 %0, %0, %1, %2, %3;\n"
837 "}\n"
838 : "+r"(r[ii])
839 : "n"(low_nib_mask), "n"(or_mask), "n"(and_or_imm_lut));
840
841 // For low nibble:
842 // {x1, x0} = {128 + x1, 128 + x0} * {1, 1} - {128, 128}
843 static constexpr uint32_t low_nib_bias = 0x43004300; // {128, 128}
844
845 {
846 __nv_bfloat162& fp16x2_val = reinterpret_cast<__nv_bfloat162&>(r[ii]);
847 fp16x2_val =
848 __hsub2(fp16x2_val,
849 reinterpret_cast<const __nv_bfloat162&>(low_nib_bias));
850 }
851 }
852
853 return reinterpret_cast<PackedResultType&>(r);
854 };
855 };
856
857 public:
858 CUTLASS_DEVICE
859 static result_type convert(source_type const& source) {
860 return ArrayConverterPacked32Bit<RegConvert, typename result_type::Element,
861 typename source_type::Element,
862 N>::convert(source);
863 }
864
865 CUTLASS_DEVICE
866 result_type operator()(source_type const& s) const { return convert(s); }
867 };
868
869 // for Array<cutlass::bfloat16_t, N> <= Array<vllm_uint8b128_t, N>
870 template <FloatRoundStyle Round, int N>
871 struct NumericArrayConverter<cutlass::bfloat16_t, vllm_uint8b128_t, N, Round> {
872 using result_type = Array<cutlass::bfloat16_t, N>;
873 using source_type = Array<vllm_uint8b128_t, N>;
874 static FloatRoundStyle const round_style = Round;
875
876 private:
877 using result_packed_4_t = Array<cutlass::bfloat16_t, 4>;
878 using result_packed_2_t = Array<cutlass::bfloat16_t, 2>;
879 using src_packed_4_t = Array<vllm_uint8b128_t, 4>;
880 using src_packed_2_t = Array<vllm_uint8b128_t, 2>;
881
882 // Not Valid, not supported, only here to satisfy the interface and to avoid
883 // a compile error. ScalarConverter will not actually work until
884 // NumericConverter<cutlass::bfloat16_t, vllm_uint8b128_t, Round> is
885 // implemented
886 using ScalarConverter =
887 NumericConverter<cutlass::bfloat16_t, vllm_uint8b128_t, Round>;
888
889 template <typename PackedResultType, typename PackedSrcType>
890 CUTLASS_DEVICE static PackedResultType packed_convert(
891 PackedSrcType const& source) {
892 static_assert(
893 (platform::is_same<PackedSrcType, src_packed_2_t>::value &&
894 platform::is_same<PackedResultType, result_packed_2_t>::value) ||
895 (platform::is_same<PackedSrcType, src_packed_4_t>::value &&
896 platform::is_same<PackedResultType, result_packed_4_t>::value),
897 "Invalid PackedSrcType/PackedResultType must be 2 or 4 to use private "
898 "convert dispatch.");
899
900 NumericArrayConverter<float, vllm_uint8b128_t, PackedResultType::kElements,
901 Round>
902 convert_uint8_to_f32;
903 Array<float, PackedResultType::kElements> tmp =
904 convert_uint8_to_f32(source);
905 NumericArrayConverter<cutlass::bfloat16_t, float,
906 PackedResultType::kElements, Round>
907 convert_f32_to_bf16_;
908 return convert_f32_to_bf16_(tmp);
909 }
910
911 friend class detail::VectorizedConverter;
912
913 public:
914 CUTLASS_DEVICE
915 static result_type convert(source_type const& source) {
916 result_type result;
917 using ConverterType =
918 NumericArrayConverter<typename result_type::Element,
919 typename source_type::Element, N, Round>;
920 detail::VectorizedConverter::convert<ConverterType, result_packed_4_t,
921 src_packed_4_t, result_packed_2_t,
922 src_packed_2_t>(result, source);
923
924 return result;
925 }
926
927 CUTLASS_DEVICE
928 result_type operator()(source_type const& s) const { return convert(s); }
929 };
930
931 #endif
932
933 // for Array<int8_t, N> <= Array<cutlass::half_t, N>
934 // FastFP16toINT8 from https://arxiv.org/pdf/2406.09904
935 template <FloatRoundStyle Round, int N>
936 struct NumericArrayConverter<int8_t, cutlass::half_t, N, Round> {
937 using result_type = Array<int8_t, N>;
938 using source_type = Array<cutlass::half_t, N>;
939
940 struct RegConvert {
941 // FastFP16toINT8 from https://arxiv.org/pdf/2406.09904
942 template <typename PackedResultType, int src_regs>
943 CUTLASS_DEVICE static PackedResultType convert(
944 Array<uint32_t, src_regs> src) {
945 // Hold output int8s in reg. We need 1 reg for every 4 elements
946 using RegArray = cutlass::AlignedArray<
947 uint32_t, std::max(PackedResultType::kElements / 4, size_t(1))>;
948 RegArray r;
949
950 static constexpr uint32_t MAGIC_BIAS_ = 0x64806480;
951 auto MAGIC_BIAS = *reinterpret_cast<const half2*>(&MAGIC_BIAS_);
952
953 *reinterpret_cast<half2*>(&src[0]) =
954 __hadd2(*reinterpret_cast<half2*>(&src[0]), MAGIC_BIAS);
955
956 if constexpr (src_regs > 1) {
957 *reinterpret_cast<half2*>(&src[1]) =
958 __hadd2(*reinterpret_cast<half2*>(&src[1]), MAGIC_BIAS);
959 }
960
961 static_assert(PackedResultType::kElements <= 4);
962 uint32_t uint8s;
963 static constexpr uint32_t MASK_0246 = 0x6420;
964 static constexpr uint32_t UINT8s_TO_INT8s_MASK = 0x80808080;
965 asm volatile("prmt.b32 %0,%1,%2,%3;\n"
966 : "=r"(uint8s)
967 : "r"(src[0]), "r"((src_regs > 1) ? src[1] : src[0]),
968 "n"(MASK_0246));
969
970 uint32_t int8s = (uint8s ^ UINT8s_TO_INT8s_MASK);
971
972 return reinterpret_cast<PackedResultType&>(int8s);
973 };
974 };
975
976 public:
977 CUTLASS_DEVICE
978 static result_type convert(source_type const& source) {
979 return ArrayConverterPacked32Bit<RegConvert, typename result_type::Element,
980 typename source_type::Element,
981 N>::convert(source);
982 }
983
984 CUTLASS_DEVICE
985 result_type operator()(source_type const& s) const { return convert(s); }
986 };
987
988 /////////////////////////////////////////////////////////////////////////////////////////////////
989
990 } // namespace cutlass
991
992 /////////////////////////////////////////////////////////////////////////////////////////////////