← CuTe 笔记 / cute_partition_s_copy_unpack_trace.cu · 下载原文件
1// Companion to cute_partition_s_copy_unpack_walkthrough_zh.md.
2// Host: g++ -x c++ -std=c++17 -Iinclude -I/usr/local/cuda/include \
3// -I/usr/local/cuda/include/cccl cute_partition_s_copy_unpack_trace.cu \
4// -o /tmp/cute_partition_s_copy_unpack_trace
5// PTX only: nvcc -std=c++17 -Iinclude -arch=sm_80 --ptx \
6// cute_partition_s_copy_unpack_trace.cu -o /tmp/cute_partition_s_copy_unpack_trace.ptx
7
8#include <cute/tensor.hpp>
9#include <cute/atom/copy_traits_sm75.hpp>
10#include <cute/atom/mma_traits_sm80.hpp>
11#include <cstdio>
12#include <cstdlib>
13#include <cstdint>
14#include <cstring>
15
16using namespace cute;
17using MmaOp = SM80_16x8x16_F16F16F16F16_TN;
18
19#if !defined(__CUDACC__)
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
27// A host-only operation with the same argument types as ldmatrix.x2.
28// The actual copy_unpack below expands its tensors into these references.
29// It checks bindings without executing or emulating a GPU instruction.
30struct CaptureLdsmX2 {
31 using SRegisters = uint128_t[1];
32 using DRegisters = uint32_t[2];
33 inline static void const* expected_source = nullptr;
34 inline static void const* expected_outputs[2] = {};
35 inline static int calls = 0;
36
37 static void copy(uint128_t const& src, uint32_t& d0, uint32_t& d1) {
38 require(static_cast<void const*>(&src) == expected_source,
39 "copy_unpack must pass the source row-start reference");
40 require(static_cast<void const*>(&d0) == expected_outputs[0],
41 "copy_unpack dst0 must bind the first half pair of this group");
42 require(static_cast<void const*>(&d1) == expected_outputs[1],
43 "copy_unpack dst1 must bind the second half pair of this group");
44 ++calls;
45 }
46};
47
48namespace cute {
49template <>
50struct Copy_Traits<::CaptureLdsmX2> : Copy_Traits<SM75_U32x2_LDSM_N> {};
51}
52
53int main() {
54 auto mma = make_tiled_mma(MmaOp{}, Layout<Shape<_2,_2,_1>>{});
55 auto tiled_copy = make_tiled_copy_A(
56 Copy_Atom<SM75_U32x2_LDSM_N,half_t>{}, mma);
57 using Copy = decltype(tiled_copy);
58 using S = typename Copy::AtomLayoutSrc;
59 using D = typename Copy::AtomLayoutDst;
60
61 alignas(16) half_t storage[512];
62 auto sA = make_tensor(make_smem_ptr(storage),
63 Layout<Shape<_32,_16>,Stride<_16,_1>>{});
64 for (int m=0; m<32; ++m)
65 for (int k=0; k<16; ++k) sA(m,k) = half_t(16*m+k);
66 sA(8,0) = half_t(23);
67 sA(0,9) = half_t(37);
68 auto coordinates = make_identity_tensor(shape(sA));
69 int rows = 0;
70 int slots = 0;
71 int packed_outputs = 0;
72
73 for (int t=0; t<128; ++t) {
74 int lane = t%32;
75 int warp_base = t-lane;
76 int wm = (t/32)%2;
77 int source_m = 16*wm + lane%16;
78 auto thr_copy = tiled_copy.get_slice(t);
79 auto tXsA = thr_copy.partition_S(sA);
80 auto source_coordinates = thr_copy.partition_S(coordinates);
81 auto fragment = mma.get_slice(t).partition_fragment_A(sA);
82 auto tXrA = thr_copy.retile_D(fragment);
83 auto mma_coordinates = mma.get_slice(t).partition_A(coordinates);
84 auto destination_coordinates = thr_copy.partition_D(coordinates);
85 require(tXrA.data() == fragment.data(), "retile_D must retain fragment storage");
86
87 for (int g=0; g<2; ++g) {
88 auto src_atom = tXsA(make_coord(_,g),0,0);
89 auto dst_atom = tXrA(make_coord(_,g),0,0);
90 require(int(size(src_atom)) == 8 && int(size(dst_atom)) == 4,
91 "One x2 call must expose eight source and four destination halves");
92 for (int p=0; p<8; ++p) {
93 require(source_coordinates(make_coord(p,g),0,0) == make_coord(source_m,8*g+p),
94 "partition_S must select the expected source row coordinates");
95 require(&src_atom(p) == &sA(source_m,8*g+p),
96 "Each source atom must expose eight physically contiguous halves");
97 }
98 require(reinterpret_cast<uintptr_t>(&src_atom(0))%16 == 0,
99 "Each row-start address must be 16-byte aligned");
100 ++rows;
101
102 CaptureLdsmX2::expected_source = &sA(source_m,8*g);
103 CaptureLdsmX2::expected_outputs[0] = &fragment(4*g,0,0);
104 CaptureLdsmX2::expected_outputs[1] = &fragment(4*g+2,0,0);
105 copy_unpack(Copy_Traits<CaptureLdsmX2>{}, src_atom, dst_atom);
106
107 // Independent non-transposed ldmatrix route, expressed for a destination lane:
108 // row=lane/4, col=2*(lane%4)+a%2, matrix=a/2, source lane=8*matrix+row.
109 for (int a=0; a<4; ++a) {
110 int row = lane/4;
111 int col = 2*(lane%4) + a%2;
112 int source_lane = 8*(a/2) + row;
113 int source_thread = warp_base + source_lane;
114 int v = a+4*g;
115 auto routed_source = tiled_copy.get_slice(source_thread).partition_S(sA);
116 auto routed_coordinates = tiled_copy.get_slice(source_thread).partition_S(coordinates);
117 require(S{}(source_lane,col) == D{}(lane,a),
118 "Copy traits must match the independently specified hardware route");
119 require(routed_coordinates(make_coord(col,g),0,0) == mma_coordinates(v,0,0),
120 "The hardware source must match the MMA destination coordinate");
121 require(destination_coordinates(make_coord(a,g),0,0) == mma_coordinates(v,0,0),
122 "Copy partition_D must match the MMA coordinate");
123 require(&dst_atom(a) == &fragment(v,0,0),
124 "retile_D must bind the corresponding original fragment slot");
125 // Host model of the route; no GPU instruction is executed.
126 dst_atom(a) = routed_source(make_coord(col,g),0,0);
127 require(fragment(v,0,0).raw() == sA(mma_coordinates(v,0,0)).raw(),
128 "The expected half bits must reach the original fragment slot");
129 ++slots;
130 }
131 auto packed = recast<uint32_t>(dst_atom);
132 for (int j=0; j<2; ++j) {
133 uint32_t actual = 0;
134 std::memcpy(&actual, &packed(j), sizeof(actual));
135 uint32_t expected = uint32_t(dst_atom(2*j).raw()) |
136 (uint32_t(dst_atom(2*j+1).raw()) << 16);
137 require(actual == expected, "Recast must preserve the packed low/high half bits");
138 ++packed_outputs;
139 }
140 }
141 if (t==0) {
142 std::printf("tXsA = "); print(tXsA.layout()); std::printf("\n");
143 std::printf("tXrA = "); print(tXrA.layout()); std::printf("\n");
144 std::printf("lane0 slot2 = %.0f; slot5 = %.0f\n",
145 float(fragment(2,0,0)),float(fragment(5,0,0)));
146 }
147 if (t==0 || t==1 || t==8 || t==16 || t==32 || t==64)
148 std::printf("thread%d row starts: A(%d,0), A(%d,8)\n",t,source_m,source_m);
149 }
150 std::printf("PASS: %d source rows, %d copy_unpack calls, %d destination half slots, "
151 "%d packed outputs; host only.\n",
152 rows,CaptureLdsmX2::calls,slots,packed_outputs);
153}
154#endif
155
156#if defined(__CUDACC__)
157// Compile-only kernel. If launched, use exactly one block of 128 threads.
158// The caller supplies 512 input halves and storage for 1024 output halves.
159extern "C" __global__ void source_to_fragment_x2(
160 half_t const* input, half_t* output) {
161 __shared__ __align__(16) half_t shared_a[512];
162 int t = int(threadIdx.x);
163 for (int i=t; i<512; i+=128) shared_a[i] = input[i];
164 __syncthreads();
165 auto sA = make_tensor(make_smem_ptr(shared_a),
166 Layout<Shape<_32,_16>,Stride<_16,_1>>{});
167 auto mma = make_tiled_mma(MmaOp{}, Layout<Shape<_2,_2,_1>>{});
168 auto tiled_copy = make_tiled_copy_A(
169 Copy_Atom<SM75_U32x2_LDSM_N,half_t>{},mma);
170 auto thr_copy = tiled_copy.get_slice(t);
171 auto fragment = mma.get_slice(t).partition_fragment_A(sA);
172 auto tXsA = thr_copy.partition_S(sA);
173 auto tXrA = thr_copy.retile_D(fragment);
174 copy(tiled_copy,tXsA,tXrA);
175 for (int v=0; v<8; ++v) output[t*8+v] = fragment(v,0,0);
176}
177#endif
178