cp.async.bulk: shared to cluster shared#
Call parameters#
T.cuda.tile.apply(T.ptx[opcode], *operands, scope="thread", **parameters)
Operand order: dst_shared, src_shared.
Parameter |
Type / default |
Meaning |
|---|---|---|
|
static str; default “thread” |
Execution scope; group axes follow Explicit CUDA tile patterns. It does not elect an issuing thread. |
|
static positive int; required |
Bytes per atom; divisible by 16. |
|
runtime pointer; required |
Local shared address of the completion barrier in the destination CTA image. |
|
runtime integer; required |
Destination CTA rank within the cluster. |
|
runtime bool; default None |
Execute the instruction when true; None is unconditional. A skipped asynchronous issue performs no completion accounting. |
Only the parameters listed here are instruction parameters. Opcode qualifiers are
written in T.ptx[opcode].
Data representation#
Each operand ends in chunk_bytes*8/storage_bits scalar elements. Source, destination and mbar denote local shared addresses before remote address mapping.
Operand specification#
This pattern issues
cp.async.bulk.shared::cluster.shared::cta.mbarrier::complete_tx::bytes. It copies
one contiguous shared-memory chunk to another CTA in the launch cluster and completes
transaction bytes on a barrier in the destination CTA. For global tensor maps, use
cp.async.bulk.tensor / cp.reduce.async.bulk.tensor instead.
The pointer contract#
apply takes dst_shared, src_shared followed by explicit chunk_bytes,
mbar and remote_cta_id. Source and destination regions use coordinates
(*group,*repeat,element). The final axis covers exactly chunk_bytes, a positive
multiple of 16; its bytes are consecutive and its origin is 16-byte aligned. Both
shared/shared.dyn storage are accepted.
All three addresses supplied by the caller are local shared addresses:
srcnames storage in the issuing CTA.dstnames the local counterpart of the destination allocation. Lowering maps its address toremote_cta_idwithmapafor each repeat.mbarnames the local counterpart of the completion barrier. Lowering maps it to the same remote CTA once before the repeat loop.
Do not pass an already mapped destination or barrier. Both CTAs must have corresponding
live shared allocations at those offsets. remote_cta_id is the rank within the
cluster, not an absolute block index. pred gates the copy instruction and must agree
with the caller’s transaction accounting.
Legality conditions#
Both regions use shared/shared.dyn storage, contain whole elements and contiguous chunks, and have 16-byte-aligned starts. Group/repeat shapes agree. The caller supplies a valid cluster rank and matching launch/completion protocol.
Code generation#
Map mbar to remote_cta_id once per tile call. In each repeat, map the destination atom pointer with mapa and emit one cp.async.bulk using the local source, mapped destination/barrier and chunk_bytes. The core instruction count equals the repeat product; address preparation includes mapa. Allocation, barrier initialization/transaction accounting and cluster synchronization belong to the caller.
Examples#
Example: CTA 0 sends 256 bytes to CTA 1#
The one-thread blocks here keep election and initialization visible. The same operation can be issued by an elected thread in a larger CTA.
from tvm.script import tirx as T
@T.function
def cluster_copy(A: T.Tensor((64,), "uint32"), Out: T.Tensor((64,), "uint32")):
T.device_entry(launch=T.cuda.LaunchConfig(grid=2, block=1, cluster=2))
rank = T.cuda.cluster_cta_id("x")
src = T.alloc_shared((64,), "uint32", layout="default", align=16)
dst = T.alloc_shared((64,), "uint32", layout="default", align=16)
mbar = T.alloc_shared((1,), "uint64", layout="default")
T.ptx.mbarrier.init.shared.b64(mbar.ptr_to([0]), T.uint32(1))
T.ptx.fence.mbarrier_init.release.cluster()
T.cuda.cluster_sync()
if rank == 0:
for i in range(64):
src[i] = A[i]
T.ptx.fence.proxy.async_.shared__cta()
T.cuda.tile.apply(
T.ptx["cp.async.bulk.shared::cluster.shared::cta.mbarrier::complete_tx::bytes"],
dst,
src,
chunk_bytes=256,
mbar=mbar.ptr_to([0]),
remote_cta_id=T.int32(1),
)
else:
T.ptx.mbarrier.arrive.expect_tx.shared.b64(mbar.ptr_to([0]), T.uint32(256))
T.cuda.mbarrier_wait(mbar.ptr_to([0]), 0)
for i in range(64):
Out[i] = dst[i]
T.cuda.cluster_sync()
The sender emits one barrier-address mapa, one destination-address mapa, and one 256-byte bulk copy. The source address stays local. CTA 1 expects 256 transaction bytes and waits for phase zero before consuming the data. The last cluster barrier keeps both CTAs’ shared allocations alive until the communication is finished.
Compile and verify on a device supporting thread-block clusters:
import numpy as np
import tvm
x = np.arange(64, dtype="uint32")
compiled = tvm.tirx.compile(cluster_copy, 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)
Repeated chunks and synchronization#
A float16 view (8,128):(256,1) with chunk_bytes=256 represents eight separate
contiguous chunks with gaps between them. It emits eight bulk copies. Set expected
transaction bytes to 8*256. The source and destination may have different outer
strides, provided each matching 128-element chunk is contiguous and aligned on its own
side.
Do not flatten across a physical gap and request one larger chunk: it would describe
different bytes and is rejected. There is no automatic split or fallback to a scalar
loop. Cluster launch configuration, issuer election, barrier initialization,
expected-byte accounting, proxy fences, phase progression and storage lifetime are
caller responsibilities. Ordinary cp.async.commit_group/wait_group is not this
completion mechanism.
Implementation: python/tvm/backend/cuda/tile/patterns/async_copy.py.