tcgen05.cp#
tcgen05.cp copies a descriptor-defined shared image into TMEM. It is
issued by a single thread, not collectively by the warps whose TMEM lanes
receive the data. Select shape, CTA group, multicast and optional
decompression directly in the opcode.
TMEM shape and replicas#
Operands are dst_tmem, src_shared, both with suffix (row,b32_word)
and 32-bit storage elements. An earlier axis is an explicit repeat.
Shape / multicast |
Atom extents |
TMEM lanes |
|---|---|---|
|
|
Consecutive 128 lanes |
|
|
Consecutive 128 lanes |
|
|
0, 32, 64, 96 relative to the origin |
|
|
64 consecutive lanes, replicated 64 lanes apart |
|
|
Logical row r maps to |
|
|
32 consecutive lanes, four replicas 32 lanes apart |
The destination layout must state these replicas explicitly; the pattern does not infer multicast. Columns are consecutive b32 cells with a 128-bit aligned origin. The whole footprint, including replicas, must stay inside the 128-lane, 512-column TMEM address space. A CTA-group-2 opcode makes both CTAs copy from their own corresponding shared storage into their own TMEM. The launch, allocation and commit must use the matching CTA-group protocol.
Example: one 128x128b copy and readback#
from tvm.script import tirx as T
from tvm.backend.cuda.axis import TLane, TCol
from tvm.tirx.layout import S, TileLayout
tmem_layout = TileLayout(S[(128, 4) : (1 @ TLane, 1 @ TCol)])
@T.function
def copy_to_tmem(A: T.Tensor((128, 4), "uint32"), Out: T.Tensor((128, 4), "uint32")):
T.device_entry(launch=T.cuda.LaunchConfig(grid=1, block=128))
tid = T.cuda.thread_idx("x")
warp = T.cuda.warp_id()
shared = T.alloc_shared((128, 4), "uint32", layout="default", align=16)
address = T.alloc_shared((1,), "uint32", layout="default")
mbar = T.alloc_shared((1,), "uint64", layout="default")
if warp == 0:
T.ptx.tcgen05.alloc.cta_group__1.sync.aligned.shared__cta.b32(
T.address_of(address), T.uint32(32)
)
if tid == 0:
T.ptx.mbarrier.init.shared.b64(mbar.ptr_to([0]), T.uint32(1))
for j in range(4):
shared[tid, j] = A[tid, j]
T.cuda.cta_sync()
T.ptx.fence.proxy.async_.shared__cta()
mem = T.cuda.decl_tmem(
address[0], ty_args=[T.Tensor((128, 4), "uint32", scope="tmem", layout=tmem_layout)]
)
if tid == 0:
T.cuda.tile.apply(
T.ptx["tcgen05.cp.cta_group::1.128x128b"],
mem,
shared,
ldo_bytes=16,
sdo_bytes=128,
swizzle=0,
)
T.ptx.tcgen05.commit.cta_group__1.mbarrier__arrive__one.shared__cluster.b64(
mbar.ptr_to([0])
)
T.cuda.mbarrier_wait(mbar.ptr_to([0]), 0)
T.ptx.tcgen05.fence__after_thread_sync()
word = T.alloc_local((1,), "uint32", layout="default")
for j in range(4):
T.ptx["tcgen05.ld.sync.aligned.32x32b.x1.b32"](word[0], address[0] + T.uint32(j))
T.ptx.tcgen05.wait__ld.sync.aligned()
Out[tid, j] = word[0]
T.cuda.cta_sync()
if warp == 0:
T.ptx.tcgen05.relinquish_alloc_permit.cta_group__1.sync.aligned()
T.ptx.tcgen05.dealloc.cta_group__1.sync.aligned.b32(address[0], T.uint32(32))
One cp instruction copies 2048 bytes. Its commit triggers one barrier arrival, rather than a TMA-style expected-byte transaction. The scalar x1 readback issues are caller code and independently expose each TMEM word.
import numpy as np
import tvm
x = np.arange(512, dtype="uint32").reshape(128, 4)
compiled = tvm.tirx.compile(copy_to_tmem, target="cuda", pipeline="tirx")
out = tvm.runtime.tensor(np.zeros_like(x), tvm.cuda())
compiled(tvm.runtime.tensor(x, tvm.cuda()), out)
np.testing.assert_array_equal(out.numpy(), x)
Native FP4/FP6 decompression#
Append .b8x16.b4x16_p64 or .b8x16.b6x16_p32 to the opcode. The b32
footprint above stays unchanged: each 16-byte shared block contains sixteen
packed values followed by eight bytes (FP4) or four bytes (FP6) of padding.
The destination contains sixteen expanded bytes. Four-bit values gain two
low zero bits; six-bit values gain two high zero bits. A 256-bit row contains
two such blocks. The pattern emits one native decompression instruction per
repeat, with no software unpacking loop.
For instance, the example’s opcode can be explicitly replaced with
tcgen05.cp.cta_group::1.128x128b.b8x16.b4x16_p64 when the source data
has been prepared in that packed format. The reference then compares the
expanded values, not the original words or padding. A dtype name alone
neither prepares the payload nor chooses this qualifier.
Allocation, shared proxy fencing, election, commit, wait and deallocation
are caller code. pred gates the cp but does not adjust a surrounding
completion protocol. Implementation: patterns/tmem_copy.py and
patterns/descriptors.py under python/tvm/backend/cuda/tile.