← CuTe 笔记 / cute_layout_exercises.cu · 下载原文件
1// Host-only CuTe Layout exercises. No CUDA kernel is launched.
2// Starter:
3// nvcc -std=c++17 -I include cute_layout_exercises.cu -o /tmp/cute_layout_exercises
4// Reference answers:
5// nvcc -std=c++17 -I include -DCUTE_LAYOUT_EXERCISES_REFERENCE \
6// cute_layout_exercises.cu -o /tmp/cute_layout_exercises_reference
7// Run all exercises, or select one: /tmp/cute_layout_exercises 4
8// Edit the six TODO branches below; the default branches intentionally fail checks.
9
10#include <cute/tensor.hpp>
11
12#include <array>
13#include <cstdio>
14#include <cstdlib>
15
16using namespace cute;
17
18// -------------------- Fill in these six functions --------------------
19
20auto exercise1_layout()
21{
22#ifdef CUTE_LAYOUT_EXERCISES_REFERENCE
23 return make_layout(Shape<_3,_4>{}, Stride<_8,_1>{});
24#else
25 // TODO(1): Keep a 3x4 logical matrix; start each row eight elements apart.
26 return make_layout(Shape<_3,_4>{}, Stride<_4,_1>{});
27#endif
28}
29
30template <class Src, class Dst>
31void exercise2_copy(Src const& src, Dst const& dst)
32{
33#ifdef CUTE_LAYOUT_EXERCISES_REFERENCE
34 // The tensors have the same shape, so the same integer coordinate denotes
35 // the same (m,n), even when their physical offsets differ.
36 for (int i = 0; i < int(size(src)); ++i) {
37 dst(i) = src(i);
38 }
39#else
40 // TODO(2): Preserve matrix coordinates when copying row-major to column-major.
41 for (int i = 0; i < int(size(src)); ++i) {
42 dst.data()[i] = src.data()[i];
43 }
44#endif
45}
46
47auto exercise3_layout()
48{
49#ifdef CUTE_LAYOUT_EXERCISES_REFERENCE
50 return make_layout(Shape<_2,Shape<_2,_3>>{},
51 Stride<_6,Stride<_3,_1>>{});
52#else
53 // TODO(3): Make h(m,(a,b)) = 6*m + 3*a + b, with a<2 and b<3.
54 return make_layout(Shape<_2,Shape<_2,_3>>{},
55 Stride<_6,Stride<_1,_2>>{});
56#endif
57}
58
59template <class Tensor>
60auto exercise4_transpose(Tensor const& a)
61{
62 auto transposed_shape = make_shape(size<1>(a), size<0>(a));
63#ifdef CUTE_LAYOUT_EXERCISES_REFERENCE
64 auto transposed_stride = make_stride(stride<1>(a), stride<0>(a));
65#else
66 // TODO(4): A transpose view swaps both shape modes and stride modes.
67 auto transposed_stride = make_stride(_3{}, _1{});
68#endif
69 return make_tensor(a.data(), make_layout(transposed_shape, transposed_stride));
70}
71
72auto exercise5_tv()
73{
74#ifdef CUTE_LAYOUT_EXERCISES_REFERENCE
75 return make_layout(Shape<_8,_2>{}, Stride<_1,_8>{});
76#else
77 // TODO(5): Eight logical threads, two values each; q = t + 8*v.
78 return make_layout(Shape<_8,_2>{}, Stride<_2,_1>{});
79#endif
80}
81
82auto exercise6_mma_coord(int lane, int v)
83{
84#ifdef CUTE_LAYOUT_EXERCISES_REFERENCE
85 int a = v % 2;
86 int b = (v / 2) % 2;
87 int c = v / 4;
88 return make_coord(lane / 4 + 8*b, 2*(lane % 4) + a + 8*c);
89#else
90 // TODO(6), optional: Decode all eight values of each SM80 MMA A lane.
91 return make_coord(lane / 4, 2*(lane % 4));
92#endif
93}
94
95// -------------------- Acceptance checks: leave these unchanged --------------------
96
97namespace {
98
99struct CheckFailure {};
100
101void expect_equal(char const* label, int actual, int expected)
102{
103 if (actual != expected) {
104 std::printf(" FAIL: %s; got %d, expected %d\n", label, actual, expected);
105 throw CheckFailure{};
106 }
107}
108
109void expect_true(char const* label, bool condition)
110{
111 expect_equal(label, condition ? 1 : 0, 1);
112}
113
114template <class Layout>
115void print_offsets(Layout const& layout)
116{
117 for (int m = 0; m < int(size<0>(layout)); ++m) {
118 for (int n = 0; n < int(size<1>(layout)); ++n) {
119 std::printf("%4d", int(layout(m,n)));
120 }
121 std::printf("\n");
122 }
123}
124
125void check1()
126{
127 auto padded = exercise1_layout();
128 print_offsets(padded);
129 expect_equal("logical size", int(size(padded)), 12);
130 expect_equal("cosize (no trailing padding required)", int(cosize(padded)), 20);
131 for (int m = 0; m < 3; ++m) {
132 for (int n = 0; n < 4; ++n) {
133 expect_equal("padded(m,n)", int(padded(m,n)), 8*m+n);
134 }
135 }
136}
137
138void check2()
139{
140 std::array<int,12> source{};
141 std::array<int,12> destination{};
142 auto src = make_tensor(source.data(), make_layout(Shape<_3,_4>{}, Stride<_4,_1>{}));
143 auto dst = make_tensor(destination.data(), make_layout(Shape<_3,_4>{}, Stride<_1,_3>{}));
144 for (int m = 0; m < 3; ++m) {
145 for (int n = 0; n < 4; ++n) {
146 src(m,n) = 10*m+n;
147 }
148 }
149 exercise2_copy(src, dst);
150 std::printf("destination storage:");
151 for (int value : destination) {
152 std::printf(" %d", value);
153 }
154 std::printf("\n");
155 expect_equal("src.layout()(5)", int(src.layout()(5)), 9);
156 expect_equal("dst.layout()(5)", int(dst.layout()(5)), 5);
157 for (int m = 0; m < 3; ++m) {
158 for (int n = 0; n < 4; ++n) {
159 expect_equal("dst(m,n) preserves matrix value", dst(m,n), 10*m+n);
160 }
161 }
162}
163
164void check3()
165{
166 auto h = exercise3_layout();
167 print_offsets(h);
168 expect_equal("rank", int(rank(h)), 2);
169 expect_equal("depth", int(depth(h)), 2);
170 expect_equal("size", int(size(h)), 12);
171 auto flat = flatten(h);
172 expect_true("group restores hierarchy", group<1,3>(flat) == h);
173 for (int m = 0; m < 2; ++m) {
174 for (int j = 0; j < 6; ++j) {
175 int a = j % 2;
176 int b = j / 2;
177 int expected = 6*m+3*a+b;
178 expect_equal("h(m,j)", int(h(m,j)), expected);
179 expect_equal("h(m,(a,b))", int(h(make_coord(m,make_coord(a,b)))), expected);
180 expect_equal("h(i)", int(h(m+2*j)), expected);
181 expect_equal("flatten(h)(i)", int(flat(m+2*j)), expected);
182 }
183 }
184}
185
186void check4()
187{
188 std::array<int,20> storage;
189 storage.fill(-99);
190 auto a = make_tensor(storage.data(), make_layout(Shape<_3,_4>{}, Stride<_8,_1>{}));
191 for (int m = 0; m < 3; ++m) {
192 for (int n = 0; n < 4; ++n) {
193 a(m,n) = 10*m+n;
194 }
195 }
196 auto b = exercise4_transpose(a);
197 expect_equal("transpose rows", int(size<0>(b)), 4);
198 expect_equal("transpose columns", int(size<1>(b)), 3);
199 expect_true("transpose shares the data pointer", b.data() == a.data());
200 // Check every computed offset before dereferencing the view.
201 for (int n = 0; n < 4; ++n) {
202 for (int m = 0; m < 3; ++m) {
203 expect_equal("transpose offset", int(b.layout()(n,m)), 8*m+n);
204 }
205 }
206 for (int n = 0; n < 4; ++n) {
207 for (int m = 0; m < 3; ++m) {
208 expect_equal("b(n,m) == a(m,n)", b(n,m), a(m,n));
209 }
210 }
211 auto row = b(2,_);
212 expect_true("b(2,_) starts at storage+2", row.data() == storage.data()+2);
213 expect_equal("b(2,_) stride", int(stride<0>(row)), 8);
214 row(1) = 777;
215 expect_equal("write through transpose row updates a(1,2)", a(1,2), 777);
216 for (int offset = 0; offset < 20; ++offset) {
217 int expected = offset % 8 < 4 ? 10*(offset/8)+offset%8 : -99;
218 if (offset == 10) {
219 expected = 777;
220 }
221 expect_equal("only the selected element changed; padding preserved", storage[offset], expected);
222 }
223 std::printf("b(2,1) = %d; a(1,2) = %d; storage[10] = %d\n",
224 b(2,1), a(1,2), storage[10]);
225}
226
227void check5()
228{
229 auto data_layout = make_layout(Shape<_4,_4>{}, Stride<_6,_1>{});
230 auto tv = exercise5_tv();
231 auto address = composition(data_layout, tv);
232 std::array<int,22> storage;
233 std::array<int,16> visits{};
234 storage.fill(-99);
235 auto output = make_tensor(storage.data(), data_layout);
236 for (int t = 0; t < 8; ++t) {
237 for (int v = 0; v < 2; ++v) {
238 int q = int(tv(t,v));
239 expect_equal("tv(t,v) is a logical position", q, t+8*v);
240 int m = q % 4;
241 int n = q / 4;
242 int offset = int(address(t,v));
243 expect_equal("composition yields the physical offset", offset, 6*m+n);
244 expect_equal("logical position is written exactly once", ++visits[q], 1);
245 storage[offset] = 100*t+v;
246 }
247 }
248 for (int q = 0; q < 16; ++q) {
249 expect_equal("complete tile coverage", visits[q], 1);
250 expect_equal("matrix value identifies its owner", output(q), 100*(q%8)+q/8);
251 }
252 for (int offset = 0; offset < 22; ++offset) {
253 if (offset % 6 >= 4) {
254 expect_equal("padding not written", storage[offset], -99);
255 }
256 }
257 std::printf("owner values (100*t+v):\n");
258 for (int m = 0; m < 4; ++m) {
259 for (int n = 0; n < 4; ++n) {
260 std::printf("%5d", output(m,n));
261 }
262 std::printf("\n");
263 }
264}
265
266void check6()
267{
268 auto mma = make_tiled_mma(SM80_16x8x16_F32F16F16F32_TN{});
269 auto tv = mma.get_layoutA_TV();
270 auto data_layout = make_layout(Shape<_16,_16>{}, Stride<_16,_1>{});
271 std::array<int,256> visits{};
272 for (int lane = 0; lane < 32; ++lane) {
273 for (int v = 0; v < 8; ++v) {
274 auto coord = exercise6_mma_coord(lane,v);
275 int m = int(get<0>(coord));
276 int k = int(get<1>(coord));
277 expect_true("MMA coordinates in [0,16)", m >= 0 && m < 16 && k >= 0 && k < 16);
278 int q = m+16*k;
279 expect_equal("coordinate agrees with actual MMA A TV", q, int(tv(lane,v)));
280 expect_equal("each A element has one owner", ++visits[q], 1);
281 if (lane == 4) {
282 std::printf("lane=4 v=%d -> A(%d,%d) q=%d physical_offset=%d\n",
283 v,m,k,q,int(data_layout(m,k)));
284 }
285 }
286 }
287 for (int count : visits) {
288 expect_equal("all 256 A elements covered", count, 1);
289 }
290}
291
292struct Exercise {
293 char const* name;
294 void (*run)();
295};
296
297} // namespace
298
299int main(int argc, char** argv)
300{
301 int selected = 0;
302 if (argc > 2) {
303 std::fprintf(stderr, "Usage: %s [1-6]\n", argv[0]);
304 return 2;
305 }
306 if (argc == 2) {
307 char* end = nullptr;
308 long parsed = std::strtol(argv[1], &end, 10);
309 if (end == argv[1] || *end != '\0' || parsed < 1 || parsed > 6) {
310 std::fprintf(stderr, "Choose an exercise from 1 to 6.\n");
311 return 2;
312 }
313 selected = int(parsed);
314 }
315 Exercise exercises[] = {
316 {"padded matrix", check1},
317 {"copy between storage layouts", check2},
318 {"hierarchical coordinates", check3},
319 {"transpose view and slicing", check4},
320 {"thread/value ownership", check5},
321 {"SM80 MMA A coordinates (optional)", check6},
322 };
323#ifdef CUTE_LAYOUT_EXERCISES_REFERENCE
324 std::printf("Reference answers; host execution only.\n");
325#else
326 std::printf("Starter; fill TODO(1)-TODO(6). Initial failures are expected.\n");
327#endif
328 int attempted = 0;
329 int passed = 0;
330 for (int i = 0; i < 6; ++i) {
331 if (selected != 0 && selected != i+1) {
332 continue;
333 }
334 ++attempted;
335 std::printf("\nExercise %d: %s\n", i+1, exercises[i].name);
336 try {
337 exercises[i].run();
338 ++passed;
339 std::printf(" PASS\n");
340 } catch (CheckFailure const&) {
341 // Continue so each exercise has its own feedback.
342 }
343 }
344 std::printf("\nPassed %d/%d exercises.\n", passed, attempted);
345 return passed == attempted ? 0 : 1;
346}
347