main
cuh 241 lines 10 KB
Raw
1 #pragma once
2
3 // clang-format off
4 // The cutlass include order matters (annoyingly)
5
6 #include "cutlass/cutlass.h"
7
8 #include "cute/tensor.hpp"
9 #include "cutlass/tensor_ref.h"
10 #include "cutlass/epilogue/collective/default_epilogue.hpp"
11 #include "cutlass/epilogue/thread/linear_combination.h"
12 #include "cutlass/gemm/dispatch_policy.hpp"
13 #include "cutlass/gemm/collective/collective_builder.hpp"
14 #include "cutlass/epilogue/collective/collective_builder.hpp"
15 #include "cutlass/gemm/device/gemm_universal_adapter.h"
16 #include "cutlass/gemm/kernel/gemm_universal.hpp"
17 // clang-format on
18
19 #include "cutlass_extensions/cute_utils.cuh"
20 #include "machete_collective_builder.cuh"
21 #include "machete_interleaving_utils.cuh"
22
23 namespace machete {
24
25 using namespace cute;
26
27 struct IlvBlkLayoutAuto {};
28
29 // This defines a prepacked layout for the B matrix, where the matrix is broken
30 // up into PPBlockShape_NK blocks. The data within each block is then compactly
31 // stored in memory such that when performing a TiledMMA operation with the same
32 // shape as prepacked block, all the data for a given thread is contiguous in
33 // memory. This allows us to use wider shared memory loads when loading B from
34 // shared memory. The values within a thread are also potentially interlaeved
35 // inorder to allow for more efficient upconverting.
36 //
37 // The contract here is that the `TiledMma` determined below matches the one
38 // ultimately used in the kernel. (this is also why the other element types are
39 // required along with the kernel schedule)
40 template <typename ElementA_, typename ElementB_, typename ElementConvert_,
41 typename AccumulatorT, class LayoutB, class KernelSchedule,
42 typename IlvBlkLayout_ = IlvBlkLayoutAuto>
43 // clang-format on
44 struct PrepackedLayoutBTemplate {
45 using MmaType = ElementA_;
46 using ElementA = ElementA_;
47 using ElementB = ElementB_;
48 using ElementAccumulator = AccumulatorT;
49 using ElementMma = MmaType;
50
51 // Interleave for 4bit bit types when we are not upconverting to fp8 or int8,
52 // in those cases case we use a LUT using prmt instructions to upconvert and
53 // is more efficient if the data is not interleaved For 8bit+ prmt
54 // instructions makes non-interleaved layouts efficient enough we don't need
55 // iterleaved layouts (and can reuse more of the existing cutlass converts)
56 static constexpr bool should_interleave =
57 sizeof_bits_v<ElementB> <= 4 &&
58 !std::is_same_v<ElementConvert_, cutlass::float_e4m3_t> &&
59 !std::is_same_v<ElementConvert_, int8_t>;
60
61 // Only use interleaved layouts for subbyte weights,
62 using IlvdBlkLayout = std::conditional_t<
63 std::is_same_v<IlvBlkLayout_, IlvBlkLayoutAuto>,
64 std::conditional_t<
65 should_interleave,
66 decltype(get_interleaved_blk_layout<
67 ElementB, sizeof_bits_v<ElementConvert_>, 32>()),
68 void>,
69 IlvBlkLayout_>;
70
71 // TODO (LucasWilkinson): compare the performance for other sizes
72 // Prepacked block shape, smallest layout atom for loading into registers
73 // (can contain multiple wgmma instructions worth of data in one block)
74 // We ideally want this to be configured such that a thread can perform 128bit
75 // loads, i.e. we amount of data associated with each thread within a
76 // prepacked block is a multiple of 128bits, when using a cooperative sechdule
77 // we have 256 threads working a single block at a time, this means each
78 // thread works on `sizeof_bits_v<ElementB> * (128*64) / 256` bits of data,
79 // for a 4bit type this would be 128bits
80 using PPBlockShape_NK = Shape<_128, _64>;
81
82 // Create the shape of the tile anticipated to be used by the GEMM kernel,
83 // when the kernel executes we will compute `Ct = Bt * At` since the
84 // quantized weights (B), must be the lhs operand so the flow through
85 // registers.
86 // The _128 here doesn't actually impact the shape of the stored tile directly
87 // but may impact the op selected by rs_op_selector
88 using GemmTileShape = decltype(make_shape(size<0>(PPBlockShape_NK{}), _128{},
89 size<1>(PPBlockShape_NK{})));
90
91 static constexpr cute::GMMA::Major GmmaMajorB =
92 gmma_rs_tag_to_major_B<LayoutB>();
93
94 // For coop schedules we have two warp groups cooperatively issuing wgmma
95 // instructions so we use 2 atoms along the M dim (one for each warpgroup)
96 using AtomLayoutMNK = cute::conditional_t<
97 cute::is_same_v<KernelSchedule, KernelTmaWarpSpecializedCooperative>,
98 Layout<Shape<_2, _1, _1>>, Layout<Shape<_1, _1, _1>>>;
99
100 using TiledMma = decltype(cute::make_tiled_mma(
101 cute::GMMA::rs_op_selector<ElementMma, ElementMma, ElementAccumulator,
102 GemmTileShape, GMMA::Major::K, GmmaMajorB>(),
103 AtomLayoutMNK{}));
104
105 // Prepacked block, (athrid, val) -> (N,K)
106 // i.e. ((ThrV,(ThrN,ThrK)),(FrgV,(RestN,RestK,...))) -> (N,K)
107 CUTE_HOST_DEVICE static constexpr auto ppblock_TV_to_NK() {
108 return TiledMma{}.thrfrg_A(make_layout(PPBlockShape_NK{}));
109 }
110
111 // Prepacked block, (N,K) -> (athrid, val)
112 // i.e. (N,K) -> ((ThrV,(ThrN,ThrK)),(FrgV,(RestN,RestK,...)))
113 CUTE_HOST_DEVICE static constexpr auto ppblock_NK_to_TV() {
114 return right_inverse(ppblock_TV_to_NK()).with_shape(PPBlockShape_NK{});
115 }
116
117 // Prepacked block, (athrid, val) -> (storage_offset)
118 // i.e. ((ThrV,(ThrN,ThrK)),(FrgV,(RestN,RestK,...))) -> (storage_idx)
119 CUTE_HOST_DEVICE static constexpr auto ppblock_TV_to_offset() {
120 // Return iterleaved layout
121 return make_ordered_layout(shape(ppblock_TV_to_NK()), Step<_1, _0>{});
122 }
123
124 // Prepacked block, (athrid, val) -> (storage_offset)
125 // i.e. ((ThrV,(ThrM,ThrK)),(IlvdFrgV,(RestM,RestK,...))) -> (storage_idx)
126 CUTE_HOST_DEVICE static constexpr auto ppblock_ilvd_TV_to_offset() {
127 auto layout_no_interleave =
128 make_ordered_layout(shape(ppblock_TV_to_NK()), Step<_1, _0>{});
129
130 if constexpr (std::is_same_v<IlvdBlkLayout, void>) {
131 return layout_no_interleave;
132 } else {
133 // interleave by transforming FrgV into interleaved blocks where each
134 // block has the layout IlvdBlkLayout, for example if IlvdBlkLayout is
135 // (2, 2) : (2, 1) then we get: ((2, 2), size(FrgV) / 4) : ((2, 1), 4)
136 // if FrgV is {A, B, C, D, E, F, G, H}
137 // then ((IlvBlk), FrgB) is {A, C, B, D, C, G, D, H}
138 auto frgV = get<1, 0>(layout_no_interleave);
139 auto ilvdBlk = IlvdBlkLayout{};
140 static_assert(size(frgV) % size(ilvdBlk) == 0,
141 "FrgV must be divisible by size(ilvdBlk)");
142 auto ilvd_FrgV = make_layout(
143 make_shape(shape(ilvdBlk), Int<size(frgV) / size(ilvdBlk)>{}),
144 make_stride(stride(ilvdBlk), size(ilvdBlk)));
145
146 // Return iterleaved layout
147 return make_layout(
148 get<0>(layout_no_interleave),
149 make_layout(ilvd_FrgV, get<1, 1>(layout_no_interleave)));
150 }
151 }
152
153 // Prepacked block, (M,K) -> (storage_offset)
154 CUTE_HOST_DEVICE static constexpr auto ppblock_ilvd_NK_to_offset() {
155 // do (M,K) -> (athrid, val) -> (storage_idx)
156 return ppblock_ilvd_TV_to_offset().compose(ppblock_NK_to_TV());
157 }
158
159 // ((athrid, val), (BlocksN, BlocksK), L) -> (storage_idx)
160 template <typename Shape_NKL>
161 CUTE_HOST_DEVICE static constexpr auto TVbNbKL_to_offset(
162 Shape_NKL shape_mkl) {
163 constexpr auto block_layout = ppblock_TV_to_offset();
164
165 // (BlocksN, BlocksK, L)
166 auto blocks_shape =
167 cute::transform(shape_mkl, append(PPBlockShape_NK{}, _1{}),
168 [](auto x, auto y) { return x / y; });
169
170 // ((athrid, val), (BlocksN, BlocksK, L)) -> (storage_idx)
171 auto result = make_layout(
172 block_layout,
173 make_layout(blocks_shape,
174 compact_col_major(blocks_shape, size(block_layout))));
175
176 // ((athrid, val), (BlocksN, BlocksK, L))
177 // => ((athrid, val), (BlocksN, BlocksK), L)
178 return group<1, 3>(result(_, repeat<rank<1>(result)>(_)));
179 }
180
181 // ((athrid_val), (BlocksN, BlocksK, L)) -> (N, K, L)
182 template <typename Shape_NKL>
183 CUTE_HOST_DEVICE static constexpr auto TVbNbKL_to_offset_copy(
184 Shape_NKL shape_mkl) {
185 auto layout = TVbNbKL_to_offset(shape_mkl);
186 // for 4-bit elements, having >= 64 values per column
187 // allows TMA to load full 32-byte sectors
188 auto inner_layout =
189 make_layout(make_shape(_256{}, size<0>(layout) / _256{}));
190
191 return make_layout(inner_layout, get<1>(layout), get<2>(layout));
192 }
193
194 // ((BlockN, BlockK), (BlocksN, BlocksK), L) -> (storage_idx)
195 template <typename Shape_NKL>
196 CUTE_HOST_DEVICE static constexpr auto ilvd_NKbNbKL_to_offset(
197 Shape_NKL shape_mkl) {
198 constexpr auto block_layout = ppblock_ilvd_NK_to_offset();
199
200 // (BlocksN, BlocksK, L)
201 auto blocks_shape =
202 cute::transform(shape_mkl, append(PPBlockShape_NK{}, _1{}),
203 [](auto x, auto y) { return x / y; });
204
205 // ((athrid, val), (BlocksN, BlocksK, L)) -> (storage_idx)
206 auto result = make_layout(
207 block_layout,
208 make_layout(blocks_shape,
209 compact_col_major(blocks_shape, size(block_layout))));
210
211 // ((athrid, val), (BlocksN, BlocksK, L)) => ((athrid, val), (BlocksN,
212 // BlocksK), L)
213 return group<1, 3>(result(_, repeat<rank<1>(result)>(_)));
214 }
215
216 // (BlocksN, BlocksK, L) -> (storage_idx)
217 template <typename Shape_NKL>
218 CUTE_HOST_DEVICE static constexpr auto bNbKL_to_offset(Shape_NKL shape_mkl) {
219 // (BlocksN, BlocksK, L)
220 auto blocks_shape =
221 cute::transform(shape_mkl, append(PPBlockShape_NK{}, _1{}),
222 [](auto x, auto y) { return x / y; });
223 auto stride = size(PPBlockShape_NK{});
224
225 // (BlocksN, BlocksK, L) -> (storage_idx)
226 return make_layout(blocks_shape, compact_col_major(blocks_shape, stride));
227 }
228
229 // ((athrid, val), (BlocksN, BlocksK, L)) -> (N, K, L)
230 template <class Shape_NKL>
231 CUTE_HOST_DEVICE static auto TVbNbK_to_NKL(Shape_NKL shape_mkl) {
232 auto tile = make_tile(make_layout(size<0>(PPBlockShape_NK{})),
233 make_layout(size<1>(PPBlockShape_NK{})));
234
235 // ((BlockN, BlockK), (BlocksN, BlocksK, L)) -> (N, K, L)
236 auto tiled_A = zipped_divide(make_layout(shape_mkl), tile);
237 return tiled_A.compose(ppblock_TV_to_NK(), _);
238 }
239 };
240
241 }; // namespace machete