← CuTe 笔记 / cute_mma_ldmatrix_registers_trace.cu · 下载原文件
1// Host-only layout inspection. No ldmatrix or mma.sync is executed.
2// Compile from the CUTLASS root:
3// g++ -x c++ -std=c++17 -Iinclude -I/usr/local/cuda/include \
4// -I/usr/local/cuda/include/cccl cute_mma_ldmatrix_registers_trace.cu \
5// -o /tmp/cute_mma_ldmatrix_registers_trace
6#include <cute/tensor.hpp>
7#include <cute/atom/copy_traits_sm75.hpp>
8#include <cute/atom/mma_traits_sm80.hpp>
9#include <cstdio>
10#include <cstdlib>
11
12using namespace cute;
13
14void require(bool condition, char const* message) {
15 if (!condition) {
16 std::fprintf(stderr, "FAILED: %s\n", message);
17 std::exit(1);
18 }
19}
20
21template<class T>
22void show(char const* name, T const& value) {
23 std::printf("%s = ", name);
24 print(value);
25 print("\n");
26}
27
28template<class Layout>
29void show_fragment_steps(Layout const& layout) {
30 constexpr int R = Layout::rank;
31 auto block = make_layout(get<0>(layout.shape()),
32 compact_major<LayoutLeft>(filter_zeros(get<0>(layout.stride()), get<0>(layout.shape()))));
33 auto rest = make_ordered_layout(take<1,R>(layout.shape()), take<1,R>(layout.stride()));
34 show("block", block);
35 show("rest", rest);
36 show("fragment layout", tiled_product(block, rest));
37}
38
39void inspect_fragment_zero_strides() {
40 std::puts("=== Why the original Atom stride is inspected ===");
41 auto atom_shape = Shape<_2,_2,_2>{};
42 auto original_stride = Stride<_1,_128,_8>{};
43 auto original_compact_shape = filter_zeros(original_stride, atom_shape);
44 auto original_block = make_layout(atom_shape,
45 compact_major<LayoutLeft>(original_compact_shape));
46 show("nonzero strides: compact shape", original_compact_shape);
47 show("nonzero strides: block", original_block);
48
49 auto broadcast_stride = Stride<_1,_0,_8>{};
50 auto broadcast_compact_shape = filter_zeros(broadcast_stride, atom_shape);
51 auto broadcast_block = make_layout(atom_shape,
52 compact_major<LayoutLeft>(broadcast_compact_shape));
53 show("static zero: compact shape", broadcast_compact_shape);
54 show("static zero: block", broadcast_block);
55
56 auto unfiltered_block = make_layout(atom_shape, compact_major<LayoutLeft>(atom_shape));
57 auto dynamic_zero_stride = make_stride(_1{}, int(0), _8{});
58 auto dynamic_compact_shape = filter_zeros(dynamic_zero_stride, atom_shape);
59 auto dynamic_block = make_layout(atom_shape,
60 compact_major<LayoutLeft>(dynamic_compact_shape));
61 show("dynamic zero: compact shape", dynamic_compact_shape);
62 show("dynamic zero: block", dynamic_block);
63
64 require(broadcast_block(0,0,0) == broadcast_block(0,1,0),
65 "Static broadcast coordinates retain their alias");
66 require(int(size(broadcast_block)) == 8 && int(cosize(broadcast_block)) == 4,
67 "Eight logical positions use four half slots");
68 require(unfiltered_block(0,0,0) != unfiltered_block(0,1,0),
69 "Using only the original shape loses the broadcast alias");
70 require(dynamic_block(0,0,0) != dynamic_block(0,1,0),
71 "A runtime zero is not a static broadcast marker");
72 auto input = make_layout(make_shape(atom_shape, _1{}, _1{}),
73 make_stride(broadcast_stride, _0{}, _0{}));
74 auto fragment = make_fragment_like(input);
75 for (int v = 0; v < 8; ++v) {
76 require(fragment(v,0,0) == broadcast_block(v),
77 "The broadcast block agrees with make_fragment_like");
78 }
79}
80
81template<class Copy>
82void inspect_copy_atom_cut(char const* label, Copy const&) {
83 std::printf("\n--- %s: split the TV input domain ---\n", label);
84 using TV = typename Copy::TiledLayout_TV;
85 using Ref = typename Copy::AtomLayoutRef;
86 using Dst = typename Copy::AtomLayoutDst;
87 constexpr int atom_threads = int(typename Copy::AtomNumThr{});
88 constexpr int atom_values = int(typename Copy::AtomNumVal{});
89 constexpr int tiled_threads = int(typename Copy::TiledNumThr{});
90 constexpr int tiled_values = int(typename Copy::TiledNumVal{});
91 auto cut = zipped_divide(TV{}, make_shape(
92 typename Copy::AtomNumThr{}, typename Copy::AtomNumVal{}));
93 auto dst_to_ref = right_inverse(Ref{}).compose(Dst{});
94 auto target = cut.compose(dst_to_ref, _);
95 std::printf("Tiled TV size = (%d,%d), Copy Atom size = (%d,%d)\n",
96 tiled_threads, tiled_values, atom_threads, atom_values);
97 show("Copy Atom reference layout (half units)", Ref{});
98 show("Copy Atom destination layout (half units)", Dst{});
99 show("MMA TV divided into Copy-sized groups", cut);
100 show("destination TV -> reference TV", dst_to_ref);
101 for (int group_t = 0; group_t < tiled_threads/atom_threads; ++group_t) {
102 for (int group_v = 0; group_v < tiled_values/atom_values; ++group_v) {
103 for (int t = 0; t < atom_threads; ++t) {
104 for (int v = 0; v < atom_values; ++v) {
105 auto rest = make_coord(group_t,group_v);
106 int tv = int(dst_to_ref(t,v));
107 int ref_t = tv % atom_threads;
108 int ref_v = tv / atom_threads;
109 require(tv == t + atom_threads*v, "ldmatrix reference equals destination");
110 require(cut(make_coord(t,v),rest) ==
111 TV{}(t + atom_threads*group_t, v + atom_values*group_v),
112 "Copy-sized cuts preserve the original MMA TV mapping");
113 require(target(make_coord(t,v),rest) ==
114 TV{}(ref_t + atom_threads*group_t, ref_v + atom_values*group_v),
115 "The local Copy conversion composes into the MMA TV domain");
116 }
117 }
118 }
119 }
120 if constexpr (atom_values == 8) {
121 std::printf("(t,v)=(0,5): Copy element id=%d, reference TV index=%d, MMA matrix index=%d\n",
122 int(Dst{}(0,5)), int(dst_to_ref(0,5)), int(TV{}(0,5)));
123 }
124}
125
126void inspect_one_warp() {
127 std::puts("=== One warp: A(16,16), B(8,16), no swizzle ===");
128 alignas(16) cutlass::half_t storage_a[16*16];
129 alignas(16) cutlass::half_t storage_b[8*16];
130 auto sA = make_tensor(make_smem_ptr(storage_a), Layout<Shape<_16,_16>,Stride<_16,_1>>{});
131 auto sB = make_tensor(make_smem_ptr(storage_b), Layout<Shape<_8,_16>,Stride<_16,_1>>{});
132 auto mma = make_tiled_mma(SM80_16x8x16_F16F16F16F16_TN{});
133 auto load_a = Copy_Atom<SM75_U32x4_LDSM_N, cutlass::half_t>{};
134 auto load_b = Copy_Atom<SM75_U32x2_LDSM_N, cutlass::half_t>{};
135 auto copy_a = make_tiled_copy_A(load_a, mma);
136 auto copy_a_x1 = make_tiled_copy_A(
137 Copy_Atom<SM75_U32x1_LDSM_N, cutlass::half_t>{}, mma);
138 auto copy_a_x2 = make_tiled_copy_A(
139 Copy_Atom<SM75_U32x2_LDSM_N, cutlass::half_t>{}, mma);
140 auto copy_b = make_tiled_copy_B(load_b, mma);
141 auto coords_a = make_identity_tensor(shape(sA));
142 auto coords_b = make_identity_tensor(shape(sB));
143 using CopyA = decltype(copy_a);
144 using SrcA = typename CopyA::AtomLayoutSrc;
145 using DstA = typename CopyA::AtomLayoutDst;
146 using RefA = typename CopyA::AtomLayoutRef;
147 auto src_to_ref_a = right_inverse(RefA{}).compose(SrcA{});
148 auto src_to_dst_a = right_inverse(DstA{}).compose(SrcA{});
149 auto dst_to_src_a = right_inverse(SrcA{}).compose(DstA{});
150 for (int m = 0; m < 16; ++m) {
151 for (int k = 0; k < 16; ++k) {
152 storage_a[16*m+k] = cutlass::half_t(16*m+k);
153 }
154 }
155
156 inspect_copy_atom_cut("A + ldmatrix.x4", copy_a);
157 inspect_copy_atom_cut("A + ldmatrix.x2", copy_a_x2);
158 inspect_copy_atom_cut("A + ldmatrix.x1", copy_a_x1);
159 require(int(size<1>(mma.get_layoutB_TV())) == 4,
160 "A single MMA B TV has four values per lane");
161 require(int(size<1>(mma.get_layoutB_TV())) % int(decltype(load_a)::NumValDst) != 0,
162 "A single MMA B TV cannot be cut into x4 groups of eight values");
163
164 for (int lane = 0; lane < 32; ++lane) {
165 auto thr_mma = mma.get_slice(lane);
166 auto tCrA = thr_mma.partition_fragment_A(sA);
167 auto tCrB = thr_mma.partition_fragment_B(sB);
168 auto thr_a = copy_a.get_slice(lane);
169 auto thr_b = copy_b.get_slice(lane);
170 auto tXrA = thr_a.retile_D(tCrA);
171 auto tXrA_x1 = copy_a_x1.get_slice(lane).retile_D(tCrA);
172 auto tXrB = thr_b.retile_D(tCrB);
173 auto mma_a = thr_mma.partition_A(coords_a);
174 auto mma_b = thr_mma.partition_B(coords_b);
175 auto copy_dst_a = thr_a.partition_D(coords_a);
176 auto copy_dst_b = thr_b.partition_D(coords_b);
177 auto source_a = thr_a.partition_S(coords_a);
178 auto source_values_a = thr_a.partition_S(sA);
179 auto first_source_a = source_a(0,0,0);
180 require((2*int(sA.layout()(first_source_a))) % 16 == 0,
181 "Each x4 source row begins at a 16-byte-aligned address");
182 for (int p = 0; p < 8; ++p) {
183 auto source_coord = source_a(p,0,0);
184 require(get<0>(source_coord) == get<0>(first_source_a) &&
185 get<1>(source_coord) == get<1>(first_source_a) + p,
186 "Each x4 source address describes eight contiguous half values in K");
187 int element = int(SrcA{}(lane,p));
188 int ref_tv = int(src_to_ref_a(lane,p));
189 int dst_tv = int(src_to_dst_a(lane,p));
190 int dst_lane = dst_tv % 32;
191 int dst_value = dst_tv / 32;
192 auto dst_coord = mma.get_slice(dst_lane).partition_A(coords_a)(dst_value,0,0);
193 require(int(DstA{}(dst_lane,dst_value)) == element,
194 "Copy source and destination identify the same internal element");
195 require(int(CopyA::TiledLayout_TV{}(ref_tv%32,ref_tv/32)) ==
196 int(get<0>(source_coord)) + 16*int(get<1>(source_coord)),
197 "Source TV reaches the matrix coordinate through reference TV");
198 require(source_coord == dst_coord,
199 "Equal Copy element ids reach equal source and MMA matrix coordinates");
200 require(float(source_values_a(p,0,0)) ==
201 16*int(get<0>(dst_coord)) + int(get<1>(dst_coord)),
202 "The source address holds the value needed by the destination MMA slot");
203 }
204
205 require(tXrA.data() == tCrA.data(), "A view retains the fragment data iterator");
206 auto mma_coord = make_coord(1,0,1);
207 auto copy_coord = make_coord(5,0);
208 require(&tCrA(mma_coord,0,0) == &tXrA(copy_coord,0,0),
209 "Different nested coordinates select the same half slot");
210 tXrA(copy_coord,0,0) = cutlass::half_t(7);
211 require(float(tCrA(mma_coord,0,0)) == 7.0f,
212 "Writing the Copy view changes the value read through the MMA view");
213
214 for (int v = 0; v < 8; ++v) {
215 int m = lane/4 + 8*((v/2)%2);
216 int k = 2*(lane%4) + v%2 + 8*(v/4);
217 require(mma_a(v,0,0) == make_coord(m,k), "MMA A coordinates");
218 require(copy_dst_a(v,0,0) == mma_a(v,0,0), "Copy and MMA A coordinates agree");
219 require(&tCrA(v,0,0) - &tCrA(0,0,0) == v, "A values are compact");
220 require(&tXrA(v,0,0) == &tCrA(v,0,0), "A retile aliases the original fragment");
221 require(&tXrA_x1(make_coord(v%2,v/2),0,0) == &tCrA(v,0,0),
222 "x1 groups the same A storage into four pairs");
223 require(4*(m%8) + (k%8)/2 == lane &&
224 k%2 + 2*(m/8) + 4*(k/8) == v,
225 "A matrix coordinates recover the hardware lane and half position");
226 int src_tv = int(dst_to_src_a(lane,v));
227 auto src_values = copy_a.get_slice(src_tv%32).partition_S(sA);
228 // Model the Copy_Traits route on the host; no GPU instruction is executed.
229 tXrA(v,0,0) = src_values(src_tv/32,0,0);
230 require(float(tCrA(v,0,0)) == 16*m+k,
231 "Following the Copy route fills the intended MMA fragment slot");
232 }
233 for (int v = 0; v < 4; ++v) {
234 int n = lane/4;
235 int k = 2*(lane%4) + v%2 + 8*(v/2);
236 require(mma_b(v,0,0) == make_coord(n,k), "MMA B coordinates");
237 require(copy_dst_b(v,0,0) == mma_b(v,0,0), "Copy and MMA B coordinates agree");
238 require(&tXrB(v,0,0) == &tCrB(v,0,0), "B retile aliases the original fragment");
239 require(4*n + (k%8)/2 == lane && k%2 + 2*(k/8) == v,
240 "B matrix coordinates recover the hardware lane and half position");
241 tCrB(v,0,0) = cutlass::half_t(16*n + k);
242 }
243 auto registers_a = recast<uint32_t>(tCrA(_,0,0));
244 auto registers_b = recast<uint32_t>(tCrB(_,0,0));
245 require(int(size(registers_a)) == 4 && int(size(registers_b)) == 2,
246 "mma_unpack exposes four A and two B 32-bit operands");
247 for (int j = 0; j < 4; ++j) {
248 uint32_t expected = uint32_t(tCrA(2*j,0,0).raw()) |
249 (uint32_t(tCrA(2*j+1,0,0).raw()) << 16);
250 require(registers_a(j) == expected, "A operand contains the correct low and high half fields");
251 }
252 for (int j = 0; j < 2; ++j) {
253 uint32_t expected = uint32_t(tCrB(2*j,0,0).raw()) |
254 (uint32_t(tCrB(2*j+1,0,0).raw()) << 16);
255 require(registers_b(j) == expected, "B operand contains the correct low and high half fields");
256 }
257 if (lane == 0) {
258 show("Copy Atom source layout (half units)", SrcA{});
259 int example_e = int(SrcA{}(16,1));
260 int example_tv = int(src_to_ref_a(16,1));
261 int example_q = int(CopyA::TiledLayout_TV{}(example_tv%32,example_tv/32));
262 require(example_e == 129 && example_tv == 160 && example_q == 144,
263 "The A(0,9) source-to-reference-to-MMA example");
264 require(copy_a.get_slice(16).partition_S(coords_a)(1,0,0) == make_coord(0,9),
265 "Source lane 16 half 1 reads A(0,9)");
266 std::printf("source (16,1) -> Copy e=%d -> reference tv=%d -> MMA q=%d -> destination (0,5)\n",
267 example_e, example_tv, example_q);
268 std::puts("A(0,9): source row base=8 halves, row offset=1, memory offset=9; destination a2 high half.");
269 show("MMA A TV", mma.get_layoutA_TV());
270 show("Copy reference TV", decltype(copy_a)::TiledLayout_TV{});
271 show("Copy destination TV", copy_a.get_layoutD_TV());
272 auto part_a = thr_mma.partition_A(sA);
273 show("partition_A layout", part_a.layout());
274 show_fragment_steps(part_a.layout());
275 show("tCrA layout", tCrA.layout());
276 show("tCrB layout", tCrB.layout());
277 show("tXrA layout", tXrA.layout());
278 show("tXrA x1 layout", tXrA_x1.layout());
279 show("tXrB layout", tXrB.layout());
280 std::puts("A nested aliases: MMA (1,0,1) = x4 Copy (5,0) = x1 Copy (1,2) = slot 5");
281 std::puts("Copy-view write/MMA-view readback verified before filling matrix labels.");
282 std::puts("Matrix labels below use A(m,k)=16*m+k and B(n,k)=16*n+k.");
283 for (int j = 0; j < 4; ++j) {
284 std::printf("lane 0 a%d: low half=%.0f, high half=%.0f\n", j,
285 float(tCrA(2*j,0,0)), float(tCrA(2*j+1,0,0)));
286 }
287 for (int j = 0; j < 2; ++j) {
288 std::printf("lane 0 b%d: low half=%.0f, high half=%.0f\n", j,
289 float(tCrB(2*j,0,0)), float(tCrB(2*j+1,0,0)));
290 }
291 print("lane 0 A source coordinates = ");
292 for (int v = 0; v < 8; ++v) { print(source_a(v,0,0)); print(" "); }
293 print("\n");
294 print("lane 0 A(v=5) = "); print(mma_a(5,0,0)); print("\n");
295 }
296 }
297}
298
299void inspect_official_cta() {
300 std::puts("\n=== Official CTA: 128x128x64, 2x2 warps ===");
301 auto atom = composition(Swizzle<3,3,3>{},
302 Layout<Shape<_8,Shape<_8,_8>>,Stride<_8,Stride<_1,_64>>>{});
303 auto shared_layout = tile_to_shape(atom, Shape<_128,_64,_3>{});
304 alignas(16) cutlass::half_t shared_stub[128*64*3];
305 auto s = make_tensor(make_smem_ptr(shared_stub), shared_layout);
306 auto mma = make_tiled_mma(SM80_16x8x16_F16F16F16F16_TN{},
307 Layout<Shape<_2,_2>>{}, Tile<_32,_32,_16>{});
308 auto thr_mma = mma.get_slice(0);
309 auto tCrA = thr_mma.partition_fragment_A(s(_,_,0));
310 auto tCrB = thr_mma.partition_fragment_B(s(_,_,0));
311 auto copy_b = make_tiled_copy_B(Copy_Atom<SM75_U32x4_LDSM_N,cutlass::half_t>{}, mma);
312 auto tXrB = copy_b.get_slice(0).retile_D(tCrB);
313 auto part_a = thr_mma.partition_A(s(_,_,0));
314 show("partition_A layout", part_a.layout());
315 show_fragment_steps(part_a.layout());
316 show("tCrA layout", tCrA.layout());
317 show("tCrB layout", tCrB.layout());
318 show("tXrB layout", tXrB.layout());
319 for (int k = 0; k < 4; ++k) {
320 for (int j = 0; j < 4; ++j) {
321 for (int v = 0; v < 8; ++v) {
322 require(&tXrB(v,j,k) == &tCrB(v%4,2*j+v/4,k), "B Copy group joins two MMA fragments");
323 }
324 }
325 }
326 require(int(size(tCrA)) == 128 && int(size(tCrB)) == 128, "Official A/B fragment sizes");
327}
328
329int main() {
330 inspect_fragment_zero_strides();
331 inspect_one_warp();
332 inspect_official_cta();
333 std::puts("PASS: layouts, coordinates, and aliases verified; no GPU instruction executed.");
334}
335