cp.async#
cp.async copies a short contiguous chunk from global to shared memory
without passing the payload through registers. Each issuing thread owns its
async-copy group. Select the cache/prefetch qualifiers in the opcode and the
copy width with chunk_bytes:
T.cuda.tile.apply(
T.ptx["cp.async.cg.shared.global"], dst, src,
chunk_bytes=16, scope="cta",
)
Operand contract#
The operand order is dst_shared, src_global. At thread scope each region
has shape (*repeat,element). The final axis occupies exactly
chunk_bytes bytes and is contiguous and naturally aligned on both sides.
For float16 with chunk_bytes=16, that axis has eight elements. An explicit
b32 view of the same bytes has four elements.
Field |
Meaning |
|---|---|
|
Select in the opcode. |
|
Required constant 4, 8 or 16; no width is inferred from the layout. |
|
Optional prefetch qualifier in the opcode, not a larger copy size. |
|
Runtime operand for an opcode containing |
|
Thread by default. CTA scope adds explicit |
The same byte count is copied when source and destination use different storage element widths, provided each side’s atom contains that many whole contiguous bytes. This is a bit copy, not a conversion.
Example: four 16-byte copies per thread#
This complete kernel uses (128,32) float16 global and
shared tensors. It explicitly assigns four eight-element chunks to each of
128 threads:
from tvm.script import tirx as T
@T.function
def copy_async(A: T.Tensor((128, 32), "float16"), B: T.Tensor((128, 32), "float16")):
T.device_entry(launch=T.cuda.LaunchConfig(grid=1, block=128))
tid = T.cuda.thread_idx("x")
shared = T.alloc_shared((128, 32), "float16", layout="default")
src = A.rearrange("(w t) (r v) -> w t r v", w=4, t=32, v=8)
dst = shared.rearrange("(w t) (r v) -> w t r v", w=4, t=32, v=8)
T.cuda.tile.apply(
T.ptx["cp.async.cg.shared.global"],
dst,
src,
chunk_bytes=16,
scope="cta",
)
T.ptx.cp.async_.commit_group()
T.ptx.cp.async_.wait_group(0)
T.cuda.cta_sync()
for j in range(32):
B[tid, j] = shared[tid, j]
The views have shape (4,32,4,8). Coordinates w,t bind the warp and
lane, r repeats the instruction four times, and v selects eight
float16 elements. For thread tid=32*w+t, issue r accesses
A[tid,8*r:8*r+8] and the matching shared row. There is no data rearrangement.
Compile and check it with an Ampere-or-later CUDA device:
import numpy as np
import tvm
compiled = tvm.tirx.compile(copy_async, target="cuda", pipeline="tirx")
x = (np.arange(4096) % 97).astype("float16").reshape(128, 32)
a = tvm.runtime.tensor(x, tvm.cuda())
b = tvm.runtime.tensor(np.zeros_like(x), tvm.cuda())
compiled(a, b)
np.testing.assert_array_equal(b.numpy(), x)
The transfer part emits four instances of this instruction per thread, followed by the caller’s commit and wait. Pointer calculations and inline helper names are omitted here:
cp.async.cg.shared.global [shared_address], [global_address], 16;
// repeated for r = 0, 1, 2, 3
cp.async.commit_group;
cp.async.wait_group 0;
Predication is different from zero fill#
pred=False suppresses the instruction; it does not write zeros to shared
memory. To copy a partial chunk and zero the remaining bytes, supply
src_size in the range 0..chunk_bytes. To zero the entire chunk, use
src_size=0 or ignore_src=True. src_size and ignore_src are
alternative operand forms and cannot both be supplied.
For example, inside a kernel where tid selects one 16-byte row:
T.cuda.tile.apply(
T.ptx["cp.async.cg.shared.global.L2::128B"],
shared.sub[tid, :], A.sub[tid, :], chunk_bytes=16,
src_size=T.if_then_else(tid < valid_rows, T.uint32(16), T.uint32(0)),
)
Here both tensors have uint8 rows of length 16. The final allocated row is still part of the declared source view: a zero-fill operand does not relax the tile pattern’s bounds/shape proof.
Completion and errors#
Each thread commits and waits for its own issues. Use a CTA barrier before
other threads read the resulting shared tile. These calls are not inserted
by apply. A shared-to-global request, a noncontiguous chunk, an unaligned
base, or a missing group axis fails its contract; lowering never substitutes
a synchronous copy or a narrower instruction.
Implementation: python/tvm/backend/cuda/tile/patterns/async_copy.py.
See Explicit CUDA tile patterns for the shared group/repeat rules.