← CuTe 笔记 / cute_make_tiled_copy_reference_trace.cu · 下载原文件
1// Host-only checks for make_tiled_copy. No GPU instruction is executed.
2/* Compile at the CUTLASS root:
3g++ -x c++ -std=c++17 -Iinclude -I/usr/local/cuda/include \
4 -I/usr/local/cuda/include/cccl cute_make_tiled_copy_reference_trace.cu \
5 -o /tmp/cute_make_tiled_copy_reference_trace
6*/
7#include <cute/tensor.hpp>
8#include <cute/atom/copy_traits_sm75.hpp>
9#include <cute/atom/copy_traits_sm80.hpp>
10#include <cute/atom/mma_traits_sm80.hpp>
11#include <array>
12#include <cstdio>
13#include <cstdlib>
14#include <type_traits>
15
16using namespace cute;
17
18void require(bool condition, char const* message) {
19 if (!condition) {
20 std::fprintf(stderr, "FAILED: %s\n", message);
21 std::exit(1);
22 }
23}
24
25template<class T>
26void show(char const* name, T const& value) {
27 std::printf("%s = ", name);
28 print(value);
29 print("\n");
30}
31
32template<class TC, class Thr, class Val>
33void trace(TC const& tc, Thr thr, Val val) {
34 auto mn = raked_product(thr, val);
35 show("thr_layout", thr);
36 show("val_layout", val);
37 show("logical_product", logical_product(thr, val));
38 show("layout_mn", mn);
39 show("coalesce(layout_mn)", coalesce(mn));
40 show("right_inverse(layout_mn)", right_inverse(mn));
41 show("Tiler_MN", typename TC::Tiler_MN{});
42 show("TiledLayout_TV", typename TC::TiledLayout_TV{});
43 show("AtomLayoutSrc", typename TC::AtomLayoutSrc{});
44 show("AtomLayoutDst", typename TC::AtomLayoutDst{});
45 show("AtomLayoutRef", typename TC::AtomLayoutRef{});
46 show("source_to_reference", right_inverse(typename TC::AtomLayoutRef{}).compose(typename TC::AtomLayoutSrc{}));
47 show("get_layoutS_TV", tc.get_layoutS_TV());
48 show("get_layoutD_TV", tc.get_layoutD_TV());
49}
50
51template<class TC>
52void trace_partition_source() {
53 auto atom_tv = zipped_divide(typename TC::TiledLayout_TV{},
54 make_shape(typename TC::AtomNumThr{}, typename TC::AtomNumVal{}));
55 auto endpoint_to_ref = right_inverse(typename TC::AtomLayoutRef{}).compose(typename TC::AtomLayoutSrc{});
56 auto endpoint_tv = atom_tv.compose(endpoint_to_ref, _);
57 auto zipped = zip(endpoint_tv);
58 show("partition_S/atom_layout_TV", atom_tv);
59 show("partition_S/trg_layout_TV", endpoint_tv);
60 show("partition_S/zip(trg_layout_TV)", zipped);
61 show("partition_S/coalesce with profile", coalesce(zipped, Shape<_1,Shape<_1,_1>>{}));
62 show("partition_S/coalesce without profile", coalesce(zipped));
63}
64
65void verify_small_copy() {
66 std::puts("=== reading example: 32 threads, 4 half per thread, Ref=Src ===");
67 using SmallAtom = Copy_Atom<SM80_CP_ASYNC_CACHEALWAYS<uint64_t>, cutlass::half_t>;
68 auto thr_layout = Layout<Shape<_4,_8>,Stride<_8,_1>>{};
69 auto val_layout = Layout<Shape<_1,_4>>{};
70 auto tc = make_tiled_copy(SmallAtom{}, thr_layout, val_layout);
71 trace(tc, thr_layout, val_layout);
72 trace_partition_source<decltype(tc)>();
73 auto identity = make_identity_tensor(Shape<_4,_32>{});
74 auto mn = raked_product(thr_layout, val_layout);
75 auto tv = typename decltype(tc)::TiledLayout_TV{};
76 auto inv = right_inverse(mn);
77 alignas(16) std::array<cutlass::half_t, 4*40> storage{};
78 auto padded = make_tensor(make_smem_ptr(storage.data()),
79 Layout<Shape<_4,_32>,Stride<Int<40>,_1>>{});
80 std::array<int,128> visits{};
81 for (int t=0; t<32; ++t) {
82 auto ps = tc.get_slice(t).partition_S(identity);
83 auto pd = tc.get_slice(t).partition_D(identity);
84 auto padded_d = tc.get_slice(t).partition_D(padded);
85 for (int v=0; v<4; ++v) {
86 int m = t/8, n = 4*(t%8)+v;
87 int q = m+4*n;
88 require(ps(v) == make_coord(m,n), "reading example source coordinates");
89 require(pd(v) == make_coord(m,n), "reading example destination coordinates");
90 require(int(tv(make_coord(t,v))) == q, "reading example reference TV");
91 require(int(mn(q)) == t+32*v, "reading example TV encoding");
92 require(int(inv(t+32*v)) == q, "reading example inverse mapping");
93 require(&padded_d(v)-storage.data() == 40*m+n, "reading example padded destination");
94 ++visits[32*m+n];
95 }
96 }
97 for (int count : visits) require(count == 1, "reading example exact coverage");
98 std::puts("Verified all 128 reading-example coordinates, inverse mappings, and padded destination offsets.\n");
99}
100
101template<class MMA>
102void trace_mma_a_reference(MMA const& mma) {
103 auto thr_layout = mma.get_thr_layout_vmnk();
104 auto ref_A = make_layout(make_shape(tile_size<0>(mma), tile_size<2>(mma)));
105 auto t_tile = make_tile(mma.template permutation_mnk<0>(),
106 mma.template permutation_mnk<2>());
107 auto t_tensor = logical_divide(ref_A, t_tile);
108 auto zipped_t_tensor = zipped_divide(ref_A, t_tile);
109 auto a_tile = make_tile(make_layout(size<0>(typename MMA::AtomShape_MNK{})),
110 make_layout(size<2>(typename MMA::AtomShape_MNK{})));
111 auto logical_a_tensor = logical_divide(t_tensor, a_tile);
112 auto a_tensor = zipped_divide(t_tensor, a_tile);
113 auto tv_tensor = a_tensor.compose(typename MMA::AtomLayoutA_TV{}, _);
114 auto thr_tile = make_tile(_, make_tile(make_layout(size<1>(thr_layout)),
115 make_layout(size<3>(thr_layout))));
116 auto before_atom_tv = zipped_divide(a_tensor, thr_tile);
117 auto logical_thr_tensor = logical_divide(tv_tensor, thr_tile);
118 auto thr_tensor = zipped_divide(tv_tensor, thr_tile);
119 // The tutorial uses a dimension-only permutation. In this configuration,
120 // omitting the first divide preserves the element mapping.
121 auto direct_a_tensor = zipped_divide(ref_A, a_tile);
122 auto direct_tv_tensor = direct_a_tensor.compose(typename MMA::AtomLayoutA_TV{}, _);
123 auto without_first_divide = zipped_divide(direct_tv_tensor, thr_tile);
124 auto atile = make_tile(_, make_tile(
125 make_layout(make_shape(size<1>(thr_layout), size<2>(thr_layout)),
126 make_stride(Int<1>{}, Int<0>{})), _));
127 auto extended_threads = make_layout(thr_layout, complement(thr_layout));
128 auto inverse_threads = right_inverse(extended_threads);
129 auto thridx_2_thrid = composition(
130 make_layout(make_shape(size(thr_layout), Int<1>{}),
131 make_stride(Int<1>{}, Int<0>{})), inverse_threads);
132 auto with_n_warps = thr_tensor.compose(atile, _);
133 auto result = with_n_warps.compose(thridx_2_thrid, _);
134
135 show("get_layoutA/ref_A", ref_A);
136 show("get_layoutA/t_tile", t_tile);
137 show("get_layoutA/t_tensor", t_tensor);
138 show("get_layoutA/zipped_divide(ref_A,t_tile)", zipped_t_tensor);
139 show("get_layoutA/logical_divide(t_tensor,a_tile)", logical_a_tensor);
140 show("get_layoutA/a_tensor", a_tensor);
141 show("get_layoutA/tv_tensor", tv_tensor);
142 show("get_layoutA/thr_tile", thr_tile);
143 show("get_layoutA/thread_divide_before_atom_TV", before_atom_tv);
144 show("get_layoutA/logical_divide(tv_tensor,thr_tile)", logical_thr_tensor);
145 show("get_layoutA/thrfrg_A(ref_A)", thr_tensor);
146 show("get_layoutA/without_first_divide_dimension_only", without_first_divide);
147 show("get_layoutA/atile", atile);
148 show("get_layoutA/complement(ThrLayoutVMNK)", complement(thr_layout));
149 show("get_layoutA/inverse_extended_threads", inverse_threads);
150 show("get_layoutA/thridx_2_thrid", thridx_2_thrid);
151 show("get_layoutA/with_N_warps", with_n_warps);
152 show("get_layoutA/final_TV", result);
153 require(int(size<0>(thr_tensor)) == 64, "A thread domain before N-warp expansion");
154 require(int(size<0>(with_n_warps)) == 128, "A thread domain after N-warp expansion");
155 auto official = mma.get_layoutA_TV();
156 auto direct = mma.thrfrg_A(ref_A);
157 for (int t=0; t<128; ++t) {
158 int lane = t%32, wm = (t/32)%2;
159 require(int(thridx_2_thrid(t)) == t, "thread-ID adapter is identity in this configuration");
160 for (int v=0; v<8; ++v) {
161 int m = lane/4 + 8*((v/2)%2) + 16*wm;
162 int k = 2*(lane%4) + v%2 + 8*(v/4);
163 auto local_tv = make_coord(lane+32*wm, v);
164 auto full_tv = make_coord(t, v);
165 require(int(thr_tensor(local_tv)) == m+32*k, "thrfrg_A before N-warp expansion");
166 require(int(without_first_divide(local_tv)) == int(thr_tensor(local_tv)),
167 "first divide is elementwise redundant for this dimension-only permutation");
168 require(int(direct(local_tv)) == m+32*k, "manual thrfrg_A matches the source method");
169 require(int(with_n_warps(full_tv)) == m+32*k, "atile broadcasts A across N warps");
170 require(int(result(full_tv)) == int(official(full_tv)), "stepwise get_layoutA_TV composition");
171 }
172 }
173 require(int(result(make_coord(109,6))) == 347, "worked example t=109,v=6");
174 require(int(result(make_coord(45,6))) == 347, "worked example shares A across N warps");
175 std::puts("Verified all 1024 get_layoutA_TV composition paths and the t=109,v=6 example.\n");
176}
177
178void verify_thread_inverse_completion() {
179 auto l = Layout<Shape<_2,_2>, Stride<_1,_4>>{};
180 auto c = complement(l);
181 auto completed = make_layout(l,c);
182 auto inverse = right_inverse(completed);
183 auto project = make_layout(make_shape(size(l),_1{}), make_stride(_1{},_0{}));
184 auto adapter = composition(project,inverse);
185 require(int(size(right_inverse(l))) == 2, "direct inverse stops at the thread-ID gap");
186 require(int(size(inverse)) == 8, "completed inverse covers all eight IDs");
187 for (int t=0; t<8; ++t) {
188 require(int(completed(inverse(t))) == t, "completed thread-ID inverse round trip");
189 require(int(project(inverse(t))) == int(inverse(t))%4,
190 "projection discards the auxiliary coordinate, including nonzero ones");
191 }
192 for (int i=0; i<4; ++i) {
193 require(int(l(i)) == i%2+4*(i/2), "four-thread gapped layout formula");
194 require(int(adapter(l(i))) == i, "all four original coordinates are recovered");
195 }
196 show("thread inverse/gapped L", l);
197 show("thread inverse/complement(L)", c);
198 show("thread inverse/completed layout", completed);
199 show("thread inverse/right_inverse(L)", right_inverse(l));
200 show("thread inverse/right_inverse(completed)", inverse);
201 show("thread inverse/project", project);
202
203 // Exercise the same construction through the actual TiledMMA interface.
204 auto sparse = make_tiled_mma(SM80_16x8x16_F16F16F16F16_TN{},
205 Layout<Shape<_2,_2>,Stride<_1,_4>>{}, Tile<_32,_32,_16>{});
206 auto dense = make_tiled_mma(SM80_16x8x16_F16F16F16F16_TN{},
207 Layout<Shape<_2,_2>>{}, Tile<_32,_32,_16>{});
208 auto sparse_threads = sparse.get_thr_layout_vmnk();
209 auto sparse_completed = make_layout(sparse_threads,complement(sparse_threads));
210 auto sparse_project = make_layout(make_shape(size(sparse_threads),_1{}),
211 make_stride(_1{},_0{}));
212 auto sparse_adapter = composition(sparse_project,right_inverse(sparse_completed));
213 auto sparse_tv = sparse.get_layoutA_TV();
214 auto dense_tv = dense.get_layoutA_TV();
215 require(int(size(sparse_threads)) == 128, "sparse layout has 128 original thread coordinates");
216 require(int(size(right_inverse(sparse_threads))) == 64,
217 "direct sparse MMA inverse stops after the first 64 IDs");
218 require(int(size(right_inverse(sparse_completed))) == 256,
219 "complement completes the 256-ID ambient range");
220 require(int(size(right_inverse(dense.get_thr_layout_vmnk()))) == 128,
221 "official dense thread layout needs no nontrivial completion");
222 for (int i=0; i<128; ++i) {
223 int t = int(sparse_threads(i));
224 require(t == i%64+128*(i/64), "sparse MMA valid thread-ID ranges");
225 require(int(sparse_adapter(t)) == i, "sparse physical ID recovers the compact VMNK index");
226 for (int v=0; v<8; ++v) {
227 require(int(sparse_tv(make_coord(t,v))) == int(dense_tv(make_coord(i,v))),
228 "sparse and dense MMA A coordinates agree at every valid thread value");
229 }
230 }
231 show("thread inverse/sparse MMA L", sparse_threads);
232 show("thread inverse/sparse MMA complement", complement(sparse_threads));
233 std::puts("Verified completed inverses and projection for all 8 ambient IDs; all 128 valid sparse MMA threads preserve 1024 A values.\n");
234}
235
236void verify_mma_a_permutation() {
237 using PermM = Layout<Shape<_2,_16>, Stride<_16,_1>>;
238 auto mma = make_tiled_mma(SM80_16x8x16_F16F16F16F16_TN{},
239 Layout<Shape<_2,_2>>{}, Tile<PermM,_32,_16>{});
240 using MMA = decltype(mma);
241 auto ref_A = make_layout(make_shape(tile_size<0>(mma), tile_size<2>(mma)));
242 auto t_tensor = logical_divide(ref_A,
243 make_tile(mma.template permutation_mnk<0>(), mma.template permutation_mnk<2>()));
244 auto a_tile = make_tile(make_layout(size<0>(typename MMA::AtomShape_MNK{})),
245 make_layout(size<2>(typename MMA::AtomShape_MNK{})));
246 auto direct_tv = zipped_divide(ref_A, a_tile).compose(typename MMA::AtomLayoutA_TV{}, _);
247 auto thr_layout = mma.get_thr_layout_vmnk();
248 auto thr_tile = make_tile(_, make_tile(make_layout(size<1>(thr_layout)),
249 make_layout(size<3>(thr_layout))));
250 auto without_first_divide = zipped_divide(direct_tv, thr_tile);
251 auto with_first_divide = mma.thrfrg_A(ref_A);
252
253 require(int(tile_size<0>(mma)) == 32 && int(tile_size<2>(mma)) == 16,
254 "permutation keeps the reference shape 32 by 16");
255 for (int u=0; u<32; ++u) {
256 int permuted_m = 16*(u%2)+u/2;
257 require(int(PermM{}(u)) == permuted_m, "flattened permutation formula");
258 for (int k=0; k<16; ++k) {
259 require(int(ref_A(make_coord(u,k))) == u+32*k,
260 "size alone does not apply the permutation to ref_A");
261 require(int(t_tensor(make_coord(make_coord(u,0),make_coord(k,0)))) == permuted_m+32*k,
262 "first divide applies the full permutation");
263 }
264 }
265 for (int t=0; t<64; ++t) {
266 int lane = t%32, wm = t/32;
267 for (int v=0; v<8; ++v) {
268 int u = lane/4 + 8*((v/2)%2) + 16*wm;
269 int k = 2*(lane%4) + v%2 + 8*(v/4);
270 auto tv = make_coord(t,v);
271 require(int(with_first_divide(tv)) == 16*(u%2)+u/2+32*k,
272 "permuted MMA-A grouping");
273 require(int(without_first_divide(tv)) == u+32*k,
274 "omitting first divide loses the permutation");
275 }
276 }
277 require(int(with_first_divide(make_coord(0,2))) == 4,
278 "permutation example thread 0 value 2 maps to A(4,0)");
279 require(int(without_first_divide(make_coord(0,2))) == 8,
280 "omitting first divide maps thread 0 value 2 to A(8,0)");
281 show("permuted get_layoutA/ref_A", ref_A);
282 show("permuted get_layoutA/t_tensor", t_tensor);
283 std::puts("Verified all 512 reordered A coordinates and 512 local thread values; omitting the first divide loses PermM.\n");
284}
285
286template<class MMA>
287void verify_mma_b_n_repetition(MMA const& mma) {
288 auto thr_layout = mma.get_thr_layout_vmnk();
289 auto ref_B = make_layout(make_shape(tile_size<1>(mma), tile_size<2>(mma)));
290 auto t_tile = make_tile(mma.template permutation_mnk<1>(),
291 mma.template permutation_mnk<2>());
292 auto t_tensor = logical_divide(ref_B, t_tile);
293 auto b_tile = make_tile(make_layout(size<1>(typename MMA::AtomShape_MNK{})),
294 make_layout(size<2>(typename MMA::AtomShape_MNK{})));
295 auto b_tensor = zipped_divide(t_tensor, b_tile);
296 auto tv_tensor = b_tensor.compose(typename MMA::AtomLayoutB_TV{}, _);
297 auto thr_tile = make_tile(_, make_tile(make_layout(size<2>(thr_layout)),
298 make_layout(size<3>(thr_layout))));
299 auto logical_thr_tensor = logical_divide(tv_tensor, thr_tile);
300 auto thr_tensor = zipped_divide(tv_tensor, thr_tile);
301 auto b_tv = mma.get_layoutB_TV();
302 auto c_tv = mma.get_layoutC_TV();
303 auto identity_b = make_identity_tensor(Shape<_32,_16>{});
304 auto identity_c = make_identity_tensor(Shape<_32,_32>{});
305 using LD = Copy_Atom<SM75_U32x4_LDSM_N, cutlass::half_t>;
306 auto copy_b = make_tiled_copy_impl(LD{}, b_tv, Shape<_32,_16>{});
307 auto full_identity_b = make_identity_tensor(Shape<_128,_64>{});
308 alignas(16) std::array<cutlass::half_t,128*64> b_storage{};
309 auto b_smem = make_tensor(make_smem_ptr(b_storage.data()),
310 Layout<Shape<_128,_64>,Stride<_64,_1>>{});
311
312 show("MMA-B/AtomLayoutB_TV", typename MMA::AtomLayoutB_TV{});
313 show("MMA-B/ref_B", ref_B);
314 show("MMA-B/b_tensor", b_tensor);
315 show("MMA-B/tv_tensor", tv_tensor);
316 show("MMA-B/logical_divide(tv_tensor,thr_tile)", logical_thr_tensor);
317 show("MMA-B/thrfrg_B(ref_B)", thr_tensor);
318 show("MMA-B/get_layoutB_TV", b_tv);
319 show("MMA-B/partition_B shape", shape(mma.get_slice(0).partition_B(identity_b)));
320 show("MMA-C/partition_C shape", shape(mma.get_slice(0).partition_C(identity_c)));
321 require(int(size<1,0>(b_tensor)) == 4, "B has four N-direction Atom blocks");
322 require(int(size<1,1,0>(thr_tensor)) == 2, "two B N repeats remain after assigning two N warps");
323 std::array<int,512> b_visits{};
324 std::array<int,1024> c_visits{};
325 for (int t=0; t<128; ++t) {
326 int lane=t%32, wm=(t/32)%2, wn=t/64;
327 auto pb=mma.get_slice(t).partition_B(identity_b);
328 auto pc=mma.get_slice(t).partition_C(identity_c);
329 auto copy_d=copy_b.get_slice(t).partition_D(identity_b);
330 require(int(size(pb)) == 8, "four B values per MMA times two N repeats");
331 require(int(size(pc)) == 8, "four accumulators per MMA times two N repeats");
332 for (int repeat_n=0; repeat_n<2; ++repeat_n) {
333 for (int atom_v=0; atom_v<4; ++atom_v) {
334 int n=lane/4+8*wn+16*repeat_n;
335 int k=2*(lane%4)+atom_v%2+8*(atom_v/2);
336 int v=atom_v+4*repeat_n;
337 require(pb(atom_v,repeat_n,0) == make_coord(n,k), "B N-warp and N-repeat coordinate formula");
338 require(copy_d(v) == pb(atom_v,repeat_n,0),
339 "one eight-value Copy destination contains two four-value MMA B fragments");
340 require(int(b_tv(make_coord(t,v))) == n+32*k, "B reference TV includes both N expansions");
341 require(int(thr_tensor(make_coord(lane+32*wn,v))) == n+32*k,
342 "B local thread layout ignores the M warp coordinate");
343 ++b_visits[n+32*k];
344 int cm=lane/4+8*(atom_v/2)+16*wm;
345 int cn=2*(lane%4)+atom_v%2+8*wn+16*repeat_n;
346 require(pc(atom_v,0,repeat_n) == make_coord(cm,cn), "C N-warp and N-repeat coordinate formula");
347 require(int(c_tv(make_coord(t,v))) == cm+32*cn, "C reference TV covers the full 32x32 tile");
348 ++c_visits[cm+32*cn];
349 }
350 }
351 auto thr_mma = mma.get_slice(t);
352 auto thr_copy = copy_b.get_slice(t);
353 auto regs = thr_mma.partition_fragment_B(b_smem);
354 auto copy_regs = thr_copy.retile_D(regs);
355 auto mma_coords = thr_mma.partition_B(full_identity_b);
356 auto copy_coords = thr_copy.partition_D(full_identity_b);
357 auto source_coords = thr_copy.partition_S(full_identity_b);
358 auto source = thr_copy.partition_S(b_smem);
359 if (t == 0) {
360 show("MMA-B/register layout", regs.layout());
361 show("MMA-B/retile_D register layout", copy_regs.layout());
362 show("MMA-B/Copy partition_D shape", shape(copy_coords));
363 show("MMA-B/Copy partition_S shape", shape(source));
364 std::puts("MMA-B/lane 0 Copy values -> (Atom value, N repeat) -> B coordinate:");
365 for (int v=0; v<8; ++v) {
366 std::printf(" %d -> (%d,%d) -> ",v,v%4,v/4);
367 print(copy_d(v));
368 print("\n");
369 }
370 }
371 require(int(size<0>(regs)) == 4 && int(size<1>(regs)) == 8,
372 "full B fragment has four Atom values and eight N repeats");
373 require(int(size<0>(copy_regs)) == 8 && int(size<1>(copy_regs)) == 4,
374 "Copy B groups eight values and four N repeats");
375 for (int repeat_n=0; repeat_n<4; ++repeat_n) {
376 for (int repeat_k=0; repeat_k<4; ++repeat_k) {
377 for (int v=0; v<8; ++v) {
378 int atom_v = v%4, mma_n = 2*repeat_n+v/4;
379 require(&copy_regs(v,repeat_n,repeat_k) == &regs(atom_v,mma_n,repeat_k),
380 "B retile_D pairs MMA fragments by aliasing their original registers");
381 require(copy_coords(v,repeat_n,repeat_k) == mma_coords(atom_v,mma_n,repeat_k),
382 "Copy and MMA B coordinates agree after regrouping");
383 int sn = lane%8+8*wn+16*(lane/16)+32*repeat_n;
384 int sk = v+8*((lane/8)%2)+16*repeat_k;
385 require(source_coords(v,repeat_n,repeat_k) == make_coord(sn,sk),
386 "B source rows match the grouped Copy destination");
387 require(&source(v,repeat_n,repeat_k)-b_storage.data() == 64*sn+sk,
388 "B row-major shared-memory source offset");
389 require(&source(v,repeat_n,repeat_k) == &source(0,repeat_n,repeat_k)+v,
390 "B ldmatrix source lane supplies eight contiguous half values");
391 }
392 }
393 }
394 }
395 for (int visits:b_visits) require(visits == 2, "B is shared across two M warps");
396 for (int visits:c_visits) require(visits == 1, "four warps and two N repeats cover C exactly once");
397 std::puts("Verified all 1024 B values with two N repeats and twofold M replication, and exact coverage of all 1024 C elements.\n");
398 std::puts("Verified all 16384 B register aliases, Copy/MMA coordinate correspondences, and row-major source offsets for two-fragment Copy grouping.\n");
399}
400
401void verify_mma_a_copy() {
402 std::puts("=== ldmatrix: explicit reference TV from the actual sgemm_sm80 MMA ===");
403 auto mma = make_tiled_mma(SM80_16x8x16_F16F16F16F16_TN{},
404 Layout<Shape<_2,_2>>{}, Tile<_32,_32,_16>{});
405 trace_mma_a_reference(mma);
406 verify_mma_b_n_repetition(mma);
407 using LD = Copy_Atom<SM75_U32x4_LDSM_N, cutlass::half_t>;
408 auto reference_tv = mma.get_layoutA_TV();
409 auto tc = make_tiled_copy_impl(LD{}, reference_tv, Shape<_32,_16>{});
410 show("MMA/AtomLayoutA_TV", typename decltype(mma)::AtomLayoutA_TV{});
411 show("MMA/ThrLayoutVMNK", mma.get_thr_layout_vmnk());
412 show("MMA/tile_shape", tile_shape(mma));
413 show("MMA/get_layoutA_TV", mma.get_layoutA_TV());
414 show("MMA-A/Tiler_MN", typename decltype(tc)::Tiler_MN{});
415 show("MMA-A/TiledLayout_TV", typename decltype(tc)::TiledLayout_TV{});
416 show("MMA-A/get_layoutS_TV", tc.get_layoutS_TV());
417 show("MMA-A/get_layoutD_TV", tc.get_layoutD_TV());
418 show("MMA-A/AtomLayoutSrc", typename decltype(tc)::AtomLayoutSrc{});
419 show("MMA-A/AtomLayoutDst", typename decltype(tc)::AtomLayoutDst{});
420 show("MMA-A/AtomLayoutRef", typename decltype(tc)::AtomLayoutRef{});
421 trace_partition_source<decltype(tc)>();
422 auto identity = make_identity_tensor(Shape<_32,_16>{});
423 auto tv = typename decltype(tc)::TiledLayout_TV{};
424 auto dst_tv = tc.get_layoutD_TV();
425 std::array<int,512> s_visits{}, d_visits{};
426 for (int t=0; t<128; ++t) {
427 int lane = t%32, warp_m = (t/32)%2;
428 auto ps = tc.get_slice(t).partition_S(identity);
429 auto pd = tc.get_slice(t).partition_D(identity);
430 auto pa = mma.get_slice(t).partition_A(identity);
431 for (int v=0; v<8; ++v) {
432 int dm = lane/4 + 8*((v/2)%2) + 16*warp_m;
433 int dk = 2*(lane%4) + v%2 + 8*(v/4);
434 int sm = lane%16 + 16*warp_m;
435 int sk = v + 8*(lane/16);
436 require(pa(v) == make_coord(dm,dk), "MMA A coordinate formula");
437 require(pd(v) == pa(v), "Copy destination matches MMA A value order");
438 require(ps(v) == make_coord(sm,sk), "MMA A source row formula");
439 require(int(tv(make_coord(t,v))) == dm+32*dk, "MMA A reference TV formula");
440 require(int(dst_tv(make_coord(t,v))) == int(tv(make_coord(t,v))),
441 "ldmatrix Ref=Dst preserves MMA A reference mapping");
442 ++s_visits[sm+32*sk];
443 ++d_visits[dm+32*dk];
444 }
445 }
446 for (int i=0; i<512; ++i) {
447 require(s_visits[i] == 2, "A is loaded twice across the two N warps");
448 require(d_visits[i] == 2, "A is consumed twice across the two N warps");
449 }
450
451 // Keep the tutorial's MMA, Copy Atom, and CTA dimensions. Simplify storage
452 // to a two-dimensional row-major view for this layout-only explanation.
453 auto smem_layout = Layout<Shape<_128,_64>,Stride<_64,_1>>{};
454 alignas(16) std::array<cutlass::half_t,128*64> storage{};
455 auto smem = make_tensor(make_smem_ptr(storage.data()), smem_layout);
456 show("MMA-A/simplified row-major shared layout", smem_layout);
457 auto full_identity = make_identity_tensor(Shape<_128,_64>{});
458 for (int t=0; t<128; ++t) {
459 auto thr_mma = mma.get_slice(t);
460 auto thr_copy = tc.get_slice(t);
461 auto regs = thr_mma.partition_fragment_A(smem);
462 auto copy_regs = thr_copy.retile_D(regs);
463 auto mma_coords = thr_mma.partition_A(full_identity);
464 auto copy_coords = thr_copy.partition_D(full_identity);
465 auto source_coords = thr_copy.partition_S(full_identity);
466 auto source = thr_copy.partition_S(smem);
467 if (t == 0) {
468 show("MMA-A/register layout", regs.layout());
469 show("MMA-A/retile_D register layout", copy_regs.layout());
470 show("MMA-A/partition_S shape", shape(thr_copy.partition_S(smem)));
471 }
472 for (int m=0; m<4; ++m) {
473 for (int k=0; k<4; ++k) {
474 for (int v=0; v<8; ++v) {
475 require(&copy_regs(v,m,k) == &regs(v,m,k), "A retile_D aliases the same register");
476 require(copy_coords(v,m,k) == mma_coords(v,m,k), "A copy and MMA agree across CTA repeats");
477 int sm = t%32%16 + 16*((t/32)%2) + 32*m;
478 int sk = v + 8*((t%32)/16) + 16*k;
479 require(source_coords(v,m,k) == make_coord(sm,sk), "A source coordinates across CTA repeats");
480 require(&source(v,m,k)-storage.data() == 64*sm+sk,
481 "A source address in simplified row-major storage");
482 require(&source(v,m,k) == &source(0,m,k)+v,
483 "Each ldmatrix source lane supplies eight contiguous half values");
484 }
485 }
486 }
487 }
488 std::puts("Verified all 1024 MMA-A source/destination values, twofold N replication, 16384 CTA register aliases, and 16384 row-major source addresses.");
489
490 // Deliberately misuse the source TV as a reference TV, although Ref=Dst.
491 auto wrong = make_tiled_copy_impl(LD{}, tc.get_layoutS_TV(),
492 typename decltype(tc)::Tiler_MN{});
493 auto wrong_source = wrong.get_slice(0).partition_S(identity);
494 std::printf("wrong source lane 0 coordinates:");
495 for (int u=0; u<8; ++u) {
496 require(wrong_source(u) == make_coord(u/2,u%2), "wrong source TV must change the requested A row");
497 print(" ");
498 print(wrong_source(u));
499 }
500 print("\n\n");
501
502 // Introduce the convenience constructor after the explicit layout checks.
503 auto via_helper = make_tiled_copy_A(LD{}, mma);
504 static_assert(std::is_same<decltype(tc),decltype(via_helper)>::value,
505 "make_tiled_copy_A must preserve the explicit reference-TV construction");
506 std::puts("make_tiled_copy_A produces the same TiledCopy type as the explicit reference-TV construction.\n");
507}
508
509int main() {
510 verify_small_copy();
511 verify_mma_a_copy();
512 verify_thread_inverse_completion();
513 verify_mma_a_permutation();
514 std::puts("=== cp.async: actual sgemm_sm80 tutorial layouts, Ref=Src ===");
515 using CP = Copy_Atom<SM80_CP_ASYNC_CACHEALWAYS<uint128_t>, cutlass::half_t>;
516 auto cp_thr = Layout<Shape<_16,_8>,Stride<_8,_1>>{};
517 auto cp_val = Layout<Shape<_1,_8>>{};
518 auto cp = make_tiled_copy(CP{}, cp_thr, cp_val);
519 trace(cp, cp_thr, cp_val);
520 trace_partition_source<decltype(cp)>();
521 auto cp_shape = Shape<_16,_64>{};
522 auto cp_identity = make_identity_tensor(cp_shape);
523 auto cp_s = cp.get_layoutS_TV();
524 auto cp_d = cp.get_layoutD_TV();
525 auto cp_mn = raked_product(cp_thr, cp_val);
526 std::array<int, 1024> cp_visits{};
527 alignas(16) std::array<cutlass::half_t, 16*72> storage{};
528 auto padded = make_tensor(make_smem_ptr(storage.data()),
529 Layout<Shape<_16,_64>,Stride<Int<72>,_1>>{});
530 for (int t = 0; t < 128; ++t) {
531 auto ps = cp.get_slice(t).partition_S(cp_identity);
532 auto pd = cp.get_slice(t).partition_D(cp_identity);
533 auto padded_d = cp.get_slice(t).partition_D(padded);
534 for (int v = 0; v < 8; ++v) {
535 int m = t/8, n = 8*(t%8)+v;
536 int q = m+16*n;
537 require(int(cp_s(make_coord(t,v))) == q, "cp source TV");
538 require(int(cp_d(make_coord(t,v))) == q, "cp destination TV");
539 require(int(cp_mn(q)) == t+128*v, "cp coordinate to TV encoding");
540 require(ps(v) == make_coord(m,n), "cp source identity partition");
541 require(pd(v) == make_coord(m,n), "cp destination identity partition");
542 require(&padded_d(v)-storage.data() == 72*m+n, "cp padded destination physical address");
543 ++cp_visits[m*64+n];
544 }
545 }
546 for (int count : cp_visits) require(count == 1, "cp exact coverage");
547 std::puts("Verified all 1024 cp.async source/destination coordinates and padded destination offsets.");
548
549 std::puts("\n=== right_inverse (8,16):(16,1) subexample ===");
550 auto small_thr = Layout<Shape<_4,_8>,Stride<_8,_1>>{};
551 auto small_val = Layout<Shape<_1,_4>>{};
552 auto small_mn = raked_product(small_thr, small_val);
553 auto small_inv = right_inverse(small_mn);
554 show("layout_mn", small_mn);
555 show("coalesce(layout_mn)", coalesce(small_mn));
556 show("right_inverse(layout_mn)", small_inv);
557 for(int i=0; i<128; ++i) {
558 require(int(small_inv(i)) == 16*(i%8)+i/8, "small inverse formula");
559 require(int(small_mn(small_inv(i))) == i, "small right inverse round trip");
560 }
561 std::puts("PASS: make_tiled_copy layout checks only; no GPU instruction executed.");
562}
563