#include <cute/tensor.hpp>
#include <cute/atom/copy_atom.hpp>
#include <cute/atom/copy_traits_sm75.hpp>
#include <cstdio>
#include <cstdlib>
using namespace cute;
CUTE_HOST_DEVICE auto make_example_copy()
{
return make_tiled_copy(
Copy_Atom<UniversalCopy<uint128_t>, float>{},
make_layout(make_shape(_4{}, _8{}), make_stride(_8{}, _1{})),
make_layout(make_shape(_1{}, _4{}), make_stride(_4{}, _1{})));
}
template <class T>
void show(char const* name, T const& value)
std::printf("%s", name);
cute::print(value);
cute::print("\n");
void require(bool condition, char const* message)
if (!condition) {
std::fprintf(stderr, "Verification failed: %s\n", message);
std::exit(1);
// Inspect ldmatrix ownership only. Do not execute a device instruction.
void verify_reference_side()
using Atom = Copy_Atom<SM75_U32x4_LDSM_N, uint32_t>;
auto tc = make_tiled_copy_impl(
Atom{}, Layout<Shape<_32,_4>,Stride<_1,_32>>{}, Shape<_128>{});
using TC = decltype(tc);
auto source = tc.get_layoutS_TV();
auto destination = tc.get_layoutD_TV();
show("ldmatrix/AtomLayoutSrc: ", typename TC::AtomLayoutSrc{});
show("ldmatrix/AtomLayoutDst: ", typename TC::AtomLayoutDst{});
show("ldmatrix/AtomLayoutRef: ", typename TC::AtomLayoutRef{});
show("ldmatrix/TiledLayout_TV: ", typename TC::TiledLayout_TV{});
show("ldmatrix/get_layoutS_TV: ", source);
show("ldmatrix/get_layoutD_TV: ", destination);
for (int t = 0; t < 32; ++t) {
for (int v = 0; v < 4; ++v) {
require(source(make_coord(t,v)) == 4*t+v, "ldmatrix source ownership");
require(destination(make_coord(t,v)) == t+32*v, "ldmatrix destination ownership");
require(source(make_coord(8,0)) == destination(make_coord(0,1)),
"different lanes must refer to the same ldmatrix element");
auto wrong_reference = make_tiled_copy_impl(
Atom{}, Layout<Shape<_32,_4>,Stride<_4,_1>>{}, Shape<_128>{});
auto wrong_source = wrong_reference.get_layoutS_TV();
require(wrong_source(make_coord(8,0)) == 1,
"source TV cannot be passed as destination reference TV unchanged");
std::printf("Verified ldmatrix reference side: 128 source/destination mappings.\n");
// Host-only verification: enumerate coordinates, check complete coverage, and
// actually copy through each thread's fragment. No CUDA device is needed.
template <int M, int N, class TiledCopy>
void verify_case(TiledCopy const& tc, int values_per_thread, char const* name)
alignas(16) float source[M * N];
alignas(16) float destination[M * N];
int visits[M * N]{};
for (int i = 0; i < M * N; ++i) {
source[i] = float(i + 1);
destination[i] = -1.0f;
auto physical = make_layout(make_shape(Int<M>{}, Int<N>{}),
make_stride(Int<N>{}, _1{}));
auto src = make_tensor(source + 0, physical);
auto dst = make_tensor(destination + 0, physical);
auto coordinates = make_identity_tensor(shape(physical));
auto thread = tc.get_slice(t);
auto source_coordinates = thread.partition_S(coordinates);
auto destination_coordinates = thread.partition_D(coordinates);
auto s = thread.partition_S(src);
auto d = thread.partition_D(dst);
auto fragment = make_fragment_like(d);
require(int(size(s)) == values_per_thread * (M / 4) * (N / (8 * values_per_thread)),
"per-thread element count");
for (int rm = 0; rm < M / 4; ++rm) {
for (int rn = 0; rn < N / (8 * values_per_thread); ++rn) {
for (int v = 0; v < values_per_thread; ++v) {
auto coordinate = source_coordinates(v, rm, rn);
int m = t / 8 + 4 * rm;
int n = values_per_thread * (t % 8) + v + 8 * values_per_thread * rn;
require(coordinate == make_coord(m, n), "source coordinate formula");
require(destination_coordinates(v, rm, rn) == coordinate, "source/destination agreement");
require(int(s.layout()(v, rm, rn)) ==
physical(m, n) - physical(t / 8, values_per_thread * (t % 8)),
"physical offset relative to sliced pointer");
require(++visits[m * N + n] == 1, "non-overlapping thread ownership");
copy(tc, s, fragment);
copy(tc, fragment, d);
require(visits[i] == 1, "complete tile coverage");
require(destination[i] == source[i], "copied values");
std::printf("Verified %s: %d coordinates and %d copied floats.\n", name, M * N, M * N);
template <int M, int N>
CUTE_DEVICE void device_copy(float const* source, float* destination)
auto tc = make_example_copy();
auto src = make_tensor(make_gmem_ptr(source), physical);
auto dst = make_tensor(make_gmem_ptr(destination), physical);
auto thread = tc.get_slice(threadIdx.x);
// These kernels are compiled for PTX inspection; main() does not launch them.
// Launch contract: one block of 32 threads; 16-byte aligned device pointers.
extern "C" __global__ void cute_copy_4x32(float const* src, float* dst)
device_copy<4, 32>(src, dst);
extern "C" __global__ void cute_copy_8x128(float const* src, float* dst)
device_copy<8, 128>(src, dst);
int main()
verify_reference_side();
auto threads = make_layout(make_shape(_4{}, _8{}), make_stride(_8{}, _1{}));
auto values = make_layout(make_shape(_1{}, _4{}), make_stride(_4{}, _1{}));
auto logical = logical_product(threads, values);
auto mn = raked_product(threads, values);
auto inv = right_inverse(mn);
auto tv = inv.with_shape(make_shape(size(threads), size(values)));
auto tiler = product_each(shape(mn));
show("thr_layout: ", threads);
show("val_layout: ", values);
show("logical_product: ", logical);
show("layout_mn: ", mn);
show("right_inverse: ", inv);
auto mn_coalesced = coalesce(mn);
auto inverse_preprod = cute::fold(shape(mn_coalesced), cute::tuple<_1>{},
[](auto acc, auto extent) { return append(acc, extent * back(acc)); });
auto inverse_strides = wrap(stride(mn_coalesced));
using InverseSorted = detail::SortByKey<decltype(inverse_strides), tuple_seq<decltype(inverse_strides)>>;
show("right_inverse/coalesced input: ", mn_coalesced);
show("right_inverse/prefix products: ", inverse_preprod);
show("right_inverse/stride-sorted modes: ", typename InverseSorted::val_type{});
show("right_inverse/input strides: ", inverse_strides);
for (int i = 0; i < size(inv); ++i) {
require(mn(inv(i)) == i, "right inverse must recover every TV code");
require(inv(i) == 16 * (i % 8) + i / 8, "right inverse coordinate formula");
show("layout_tv: ", tv);
show("tiler: ", tiler);
show("blocked_product: ", blocked_product(threads, values));
show("AtomLayoutSrc: ", typename TC::AtomLayoutSrc{});
show("AtomLayoutDst: ", typename TC::AtomLayoutDst{});
show("AtomLayoutRef: ", typename TC::AtomLayoutRef{});
show("TiledLayout_TV: ", typename TC::TiledLayout_TV{});
show("get_layoutS_TV: ", tc.get_layoutS_TV());
show("get_layoutD_TV: ", tc.get_layoutD_TV());
auto physical = make_layout(make_shape(_4{}, _32{}), make_stride(_32{}, _1{}));
alignas(16) float storage[128]{};
auto tensor = make_tensor(storage + 0, physical);
auto coordinate = make_identity_tensor(shape(physical));
auto split = zipped_divide(physical, tiler);
auto atom_tv = zipped_divide(tv, make_shape(typename TC::AtomNumThr{}, typename TC::AtomNumVal{}));
auto src_to_ref = right_inverse(typename TC::AtomLayoutRef{}).compose(typename TC::AtomLayoutSrc{});
auto target_tv = atom_tv.compose(src_to_ref, _);
auto thread_value = coalesce(zip(target_tv), Shape<_1, Shape<_1, _1>>{});
auto tv_tensor = split.compose(thread_value, _);
show("zipped_divide(physical,tiler): ", split);
show("atom_layout_TV: ", atom_tv);
show("source_to_reference: ", src_to_ref);
show("trg_layout_TV: ", target_tv);
show("zip(trg_layout_TV): ", zip(target_tv));
show("thrval2mn: ", thread_value);
show("coalesce without profile: ", coalesce(zip(target_tv)));
show("coalesce profile (1,1): ", coalesce(zip(target_tv), Shape<_1,_1>{}));
show("coalesce profile (7,(7,7)): ", coalesce(zip(target_tv), Shape<_7,Shape<_7,_7>>{}));
auto before = target_tv(make_coord(make_coord(_0{}, v), make_coord(t, _0{})));
auto after_zip = zip(target_tv)(make_coord(make_coord(_0{}, t), make_coord(v, _0{})));
auto after_coalesce = thread_value(make_coord(t, make_coord(v, _0{})));
require(before == after_zip && before == after_coalesce,
"zip and profile coalesce must preserve corresponding coordinate mappings");
show("tv_tensor: ", tv_tensor);
show("tidfrg_S: ", TC::tidfrg_S(physical));
show("partition_S: ", tc.get_slice(0).partition_S(tensor).layout());
show("partition_D: ", tc.get_slice(0).partition_D(tensor).layout());
for (int t : {0, 1, 7, 8, 31}) {
auto part = tc.get_slice(t).partition_S(coordinate);
std::printf("thread %d:", t);
cute::print(" ");
cute::print(part(v, 0, 0));
int compact_coordinate = tv(t, v);
require(compact_coordinate == t / 8 + 4 * (4 * (t % 8) + v), "compact logical coordinate");
require(int(mn(compact_coordinate)) == t + 32 * v, "MN-to-TV encoding");
auto large_physical = make_layout(make_shape(_8{}, _128{}), make_stride(_128{}, _1{}));
alignas(16) float large_storage[1024]{};
auto large_tensor = make_tensor(large_storage + 0, large_physical);
show("large partition_S: ", tc.get_slice(0).partition_S(large_tensor).layout());
auto eight_values = make_tiled_copy(
Copy_Atom<UniversalCopy<uint128_t>, float>{}, threads,
make_layout(make_shape(_1{}, _8{}), make_stride(_8{}, _1{})));
using TC8 = decltype(eight_values);
show("eight-value TiledLayout_TV: ", typename TC8::TiledLayout_TV{});
show("eight-value tiler: ", typename TC8::Tiler_MN{});
alignas(16) float eight_storage[256]{};
auto eight_tensor = make_tensor(eight_storage + 0,
make_layout(make_shape(_4{}, _64{}), make_stride(_64{}, _1{})));
show("eight-value partition_S: ", eight_values.get_slice(0).partition_S(eight_tensor).layout());
// Changing the physical layout preserves logical ownership while changing
// offsets. A strided column-major partition cannot use this 128-bit Atom.
auto column_major = make_tensor(storage + 0, make_layout(make_shape(_4{}, _32{})));
show("column-major partition_S: ", tc.get_slice(0).partition_S(column_major).layout());
auto part = tc.get_slice(t).partition_S(column_major);
require(int(part.layout()(v, 0, 0)) == 4 * v, "column-major value stride");
verify_case<4, 32>(tc, 4, "base tile");
verify_case<8, 128>(tc, 4, "repeated tile");
verify_case<4, 64>(eight_values, 8, "two Atoms per thread");
return 0;