← CuTe 笔记 / cute_mma_ldmatrix_ab_retile_trace.cu · 下载原文件
1// Companion to cute_mma_ldmatrix_ab_retile_walkthrough_zh.md.
2// Host build validates layouts and models the hardware route; it executes no GPU instruction.
3// g++ -x c++ -std=c++17 -Iinclude -I/usr/local/cuda/include \
4// -I/usr/local/cuda/include/cccl cute_mma_ldmatrix_ab_retile_trace.cu \
5// -o /tmp/cute_mma_ldmatrix_ab_retile_trace
6// Compile the three one-warp GPU kernels to PTX without launching them:
7// nvcc -std=c++17 -Iinclude -arch=sm_80 --ptx \
8// cute_mma_ldmatrix_ab_retile_trace.cu -o /tmp/cute_mma_ldmatrix_ab_retile_trace.ptx
9
10#include <cute/tensor.hpp>
11#include <cute/atom/copy_traits_sm75.hpp>
12#include <cute/atom/mma_traits_sm80.hpp>
13#include <cute/algorithm/gemm.hpp>
14#include <cstdio>
15#include <cstdlib>
16
17using namespace cute;
18using MmaOp = SM80_16x8x16_F16F16F16F16_TN;
19
20void require(bool condition, char const* message) {
21 if (!condition) {
22 std::fprintf(stderr, "FAILED: %s\n", message);
23 std::exit(1);
24 }
25}
26
27template<class T>
28void show(char const* name, T const& value) {
29 std::printf("%s = ", name);
30 print(value);
31 print("\n");
32}
33
34template<bool IsA, class ThrMma, class Tensor>
35auto partition_operand(ThrMma const& thr, Tensor const& tensor) {
36 if constexpr (IsA) return thr.partition_A(tensor);
37 else return thr.partition_B(tensor);
38}
39
40template<bool IsA, class ThrMma, class Tensor>
41auto make_operand_fragment(ThrMma const& thr, Tensor const& tensor) {
42 if constexpr (IsA) return thr.partition_fragment_A(tensor);
43 else return thr.partition_fragment_B(tensor);
44}
45
46template<class Copy, class Fragment>
47void show_retile_steps(Fragment const& fragment) {
48 constexpr int Rank = Fragment::rank;
49 using NumThreads = typename Copy::TiledNumThr;
50 auto V = size<0>(fragment);
51 auto inverse = right_inverse(typename Copy::TiledLayout_TV{})
52 .with_shape(shape(typename Copy::Tiler_MN{}));
53 auto fmn = upcast<NumThreads{} * V>(inverse);
54 auto product = logical_product(make_layout(V), right_inverse(fmn));
55 auto fv = zipped_divide(product, make_layout(typename Copy::AtomNumVal{}));
56 auto tiler = prepend(product_each(shape(fmn)), V);
57 auto tt = zipped_divide(fragment.layout(), tiler);
58 auto vt = tt.compose(fv, _);
59 show("frg_layout_mn", fmn);
60 show("gather extents", product_each(shape(fmn)));
61 show("logical_product", product);
62 show("frg_layout_v", fv);
63 show("retile tiler", tiler);
64 show("t_tensor", tt);
65 show("v_tensor", vt);
66 show("retile result", vt(_, append<Rank>(Int<0>{}, _)));
67}
68
69template<bool IsA, class Load, class Tensor, class Mma>
70void verify(char const* label, Tensor const& source, Mma const& mma,
71 bool print_steps = false) {
72 auto tiled_copy = [&] {
73 if constexpr (IsA) return make_tiled_copy_A(Copy_Atom<Load, half_t>{}, mma);
74 else return make_tiled_copy_B(Copy_Atom<Load, half_t>{}, mma);
75 }();
76 using Copy = decltype(tiled_copy);
77 using Src = typename Copy::AtomLayoutSrc;
78 using Dst = typename Copy::AtomLayoutDst;
79 constexpr int values = IsA ? 8 : 4;
80 constexpr int atom_values = int(typename Copy::AtomNumVal{});
81 constexpr int matrices = atom_values / 2;
82 constexpr int groups = values / atom_values;
83 constexpr int address_lanes = 8 * matrices;
84 auto coords = make_identity_tensor(shape(source));
85 int coverage[32][values] = {};
86 int source_checks = 0;
87 int slot_checks = 0;
88 int packed_checks = 0;
89
90 std::printf("\n[%s] atom half values=%d; instruction groups=%d\n",
91 label, atom_values, groups);
92 show("AtomLayoutSrc", Src{});
93 show("AtomLayoutDst", Dst{});
94
95 // Independent non-transposed ldmatrix route:
96 // source row address lane = 8*local_matrix + row
97 // destination lane = 4*row + col/2
98 // destination atom half = 2*local_matrix + col%2
99 // Upper lanes have repeated source views for x1/x2; only the first
100 // address_lanes lanes provide the distinct rows consumed by the instruction.
101 for (int g = 0; g < groups; ++g) {
102 for (int ts = 0; ts < 32; ++ts) {
103 auto source_coords = tiled_copy.get_slice(ts).partition_S(coords);
104 int local_matrix = (ts % address_lanes) / 8;
105 int row = ts % 8;
106 int global_matrix = local_matrix + matrices * g;
107 auto first = source_coords(make_coord(0,g),0,0);
108 int first_offset = int(source.layout()(first));
109 require((2 * first_offset) % 16 == 0, "Every source row must be 16-byte aligned");
110 for (int col = 0; col < 8; ++col) {
111 int td = 4*row + col/2;
112 int av = 2*local_matrix + col%2;
113 int v = av + atom_values*g;
114 auto src_coord = source_coords(make_coord(col,g),0,0);
115 int mn = IsA ? row + 8*(global_matrix%2) : row;
116 int k = col + 8*(IsA ? global_matrix/2 : global_matrix);
117 require(src_coord == make_coord(mn,k), "The inferred source quadrant must match");
118 require(int(source.layout()(src_coord)) == first_offset+col,
119 "A source row must contain eight contiguous half values");
120 auto expected = partition_operand<IsA>(mma.get_slice(td),coords)(v,0,0);
121 require(src_coord == expected, "Hardware route must reach the required MMA coordinate");
122 require(int(Src{}(ts,col)) == int(Dst{}(td,av)),
123 "Copy traits must agree with the independent hardware route");
124 if (ts < address_lanes) ++coverage[td][v];
125 ++source_checks;
126 }
127 }
128 for (int ts = 0; ts < address_lanes; ts += 8) {
129 auto sc = tiled_copy.get_slice(ts).partition_S(coords);
130 std::printf("group %d address lane %2d starts at ",g,ts);
131 print(sc(make_coord(0,g),0,0)); print("\n");
132 }
133 }
134
135 for (int lane = 0; lane < 32; ++lane) {
136 auto thr_mma = mma.get_slice(lane);
137 auto fragment = make_operand_fragment<IsA>(thr_mma,source);
138 auto thr_copy = tiled_copy.get_slice(lane);
139 auto retiled = thr_copy.retile_D(fragment);
140 auto expected = partition_operand<IsA>(thr_mma,coords);
141 auto destination_coords = thr_copy.partition_D(coords);
142 require(retiled.data() == fragment.data(), "retile_D must preserve the storage iterator");
143 for (int g = 0; g < groups; ++g) {
144 for (int av = 0; av < atom_values; ++av) {
145 int v = av + atom_values*g;
146 int row = lane/4;
147 int col = 2*(lane%4) + av%2;
148 int ts = 8*(av/2) + row;
149 auto src = tiled_copy.get_slice(ts).partition_S(source);
150 require(coverage[lane][v] == 1, "Each destination half must be reached exactly once");
151 require(destination_coords(make_coord(av,g),0,0) == expected(v,0,0),
152 "Composed Copy destination must equal the MMA coordinate");
153 require(&retiled(make_coord(av,g),0,0) == &fragment(v,0,0),
154 "Copy groups must bind the expected fragment half slots");
155 // Host model of the fixed hardware route; no GPU instruction is executed.
156 retiled(make_coord(av,g),0,0) = src(make_coord(col,g),0,0);
157 require(fragment(v,0,0).raw() == source(expected(v,0,0)).raw(),
158 "The actual source half bits must reach the required fragment slot");
159 ++slot_checks;
160 }
161 auto copy_regs = recast<uint32_t>(retiled(make_coord(_,g),0,0));
162 auto mma_regs = recast<uint32_t>(fragment(_,0,0));
163 require(int(size(copy_regs)) == matrices, "Each instruction must expose its declared outputs");
164 for (int j = 0; j < matrices; ++j) {
165 int output = j + matrices*g;
166 require(&copy_regs(j) == &mma_regs(output),
167 "A Copy output and the corresponding MMA input must share packed storage");
168 uint32_t bits = uint32_t(fragment(2*output,0,0).raw()) |
169 (uint32_t(fragment(2*output+1,0,0).raw()) << 16);
170 require(copy_regs(j) == bits, "Packed low/high half bits must be preserved");
171 ++packed_checks;
172 }
173 }
174 if (lane == 0) {
175 show("partition source-backed layout", partition_operand<IsA>(thr_mma,source).layout());
176 show("MMA fragment", fragment.layout());
177 show("retile_D view", retiled.layout());
178 if (print_steps) show_retile_steps<Copy>(fragment);
179 for (int v = 0; v < values; ++v) {
180 std::printf("lane0 slot%d = %.0f from ", v, float(fragment(v,0,0)));
181 print(expected(v,0,0)); print("\n");
182 }
183 int target_v = IsA ? 5 : 3;
184 int g = target_v/atom_values;
185 int av = target_v%atom_values;
186 int ts = 8*(av/2);
187 int e = int(Dst{}(0,av));
188 auto f = typename Copy::TiledLayout_TV{};
189 int q = int(f(0,target_v));
190 std::printf("target: group%d source(%d,1) -> e%d -> q%d -> lane0 slot%d = %.0f\n",
191 g,ts,e,q,target_v,float(fragment(target_v,0,0)));
192 }
193 }
194 std::printf("PASS %s: %d source-view entries, %d fragment slots, %d packed bindings.\n",
195 label,source_checks,slot_checks,packed_checks);
196}
197
198int main() {
199 alignas(16) half_t storage_a[256];
200 alignas(16) half_t storage_b[128];
201 auto sA = make_tensor(make_smem_ptr(storage_a),
202 Layout<Shape<_16,_16>,Stride<_16,_1>>{});
203 auto sB = make_tensor(make_smem_ptr(storage_b),
204 Layout<Shape<_8,_16>,Stride<_16,_1>>{});
205 for (int m=0; m<16; ++m)
206 for (int k=0; k<16; ++k) sA(m,k)=half_t(16*m+k);
207 for (int n=0; n<8; ++n)
208 for (int k=0; k<16; ++k) sB(n,k)=half_t(16*n+k);
209 sA(0,9)=half_t(37);
210 sB(0,9)=half_t(53);
211 auto mma = make_tiled_mma(MmaOp{});
212 show("MMA ALayout", typename decltype(mma)::AtomLayoutA_TV{});
213 show("MMA BLayout", typename decltype(mma)::AtomLayoutB_TV{});
214 verify<true,SM75_U32x4_LDSM_N>("A/x4",sA,mma);
215 verify<true,SM75_U32x2_LDSM_N>("A/x2",sA,mma,true);
216 verify<false,SM75_U32x2_LDSM_N>("B/x2",sB,mma);
217 verify<false,SM75_U32x1_LDSM_N>("B/x1",sB,mma,true);
218 std::puts("PASS: 4 configurations, 768 fragment slots and 384 packed bindings; host only.");
219}
220
221#if defined(__CUDACC__)
222// Compile-only kernels. If launched, each requires exactly one warp per block.
223// Inputs use the same physical layouts as the article; output is row-major 16x8.
224template<class LoadA, class LoadB>
225__device__ __forceinline__ void ab_body(
226 half_t const* a, half_t const* b, half_t* output,
227 half_t* shared_a, half_t* shared_b) {
228 int lane = int(threadIdx.x);
229 for (int i=lane; i<256; i+=32) shared_a[i]=a[i];
230 for (int i=lane; i<128; i+=32) shared_b[i]=b[i];
231 __syncthreads();
232 auto sA = make_tensor(make_smem_ptr(shared_a),
233 Layout<Shape<_16,_16>,Stride<_16,_1>>{});
234 auto sB = make_tensor(make_smem_ptr(shared_b),
235 Layout<Shape<_8,_16>,Stride<_16,_1>>{});
236 auto gC = make_tensor(make_gmem_ptr(output),
237 Layout<Shape<_16,_8>,Stride<_8,_1>>{});
238 auto mma = make_tiled_mma(MmaOp{});
239 auto copy_a = make_tiled_copy_A(Copy_Atom<LoadA,half_t>{},mma);
240 auto copy_b = make_tiled_copy_B(Copy_Atom<LoadB,half_t>{},mma);
241 auto thr_mma = mma.get_slice(lane);
242 auto thr_a = copy_a.get_slice(lane);
243 auto thr_b = copy_b.get_slice(lane);
244 auto tCrA = thr_mma.partition_fragment_A(sA);
245 auto tCrB = thr_mma.partition_fragment_B(sB);
246 auto tCrC = thr_mma.partition_fragment_C(gC);
247 auto tXsA = thr_a.partition_S(sA);
248 auto tXsB = thr_b.partition_S(sB);
249 auto tXrA = thr_a.retile_D(tCrA);
250 auto tXrB = thr_b.retile_D(tCrB);
251 copy(copy_a,tXsA,tXrA);
252 copy(copy_b,tXsB,tXrB);
253 clear(tCrC);
254 gemm(mma,tCrA,tCrB,tCrC);
255 copy(tCrC,thr_mma.partition_C(gC));
256}
257
258extern "C" __global__ void ab_x4_x2(half_t const* a, half_t const* b, half_t* output) {
259 __shared__ __align__(16) half_t shared_a[256];
260 __shared__ __align__(16) half_t shared_b[128];
261 ab_body<SM75_U32x4_LDSM_N,SM75_U32x2_LDSM_N>(a,b,output,shared_a,shared_b);
262}
263
264extern "C" __global__ void ab_x2_x2(half_t const* a, half_t const* b, half_t* output) {
265 __shared__ __align__(16) half_t shared_a[256];
266 __shared__ __align__(16) half_t shared_b[128];
267 ab_body<SM75_U32x2_LDSM_N,SM75_U32x2_LDSM_N>(a,b,output,shared_a,shared_b);
268}
269
270extern "C" __global__ void ab_x2_x1(half_t const* a, half_t const* b, half_t* output) {
271 __shared__ __align__(16) half_t shared_a[256];
272 __shared__ __align__(16) half_t shared_b[128];
273 ab_body<SM75_U32x2_LDSM_N,SM75_U32x1_LDSM_N>(a,b,output,shared_a,shared_b);
274}
275#endif
276