Programming concepts#

Cooperative operations let a group of threads work on data together. This page explains the participating threads, their payloads and layouts, and the rules for results and temporary storage.

Backend registration#

coop.register("numba-cuda-mlir") explicitly selects the Numba-CUDA-MLIR integration; "numba_cuda_mlir" is an accepted spelling. Call it on the host before compiling kernels. This core-only package does not include the adapter yet, so requesting it raises an informative ImportError.

Groups and algorithms#

Use coop.this_block() for a thread block, coop.this_warp() for a physical warp of 32 threads, and coop.this_warp().group_by(width) for a logical warp of 1, 2, 4, 8, 16, or 32 threads.

The factories describe the current kernel launch and take no size arguments. Constructing a group does not synchronize threads. A descriptor’s availability does not imply that an operation supports it: thread, cluster, grid, and mapped groups of physical warps are not Load or Store targets. Runtime rank, size, membership, and synchronization queries are not exposed in this API layer.

group_by counts units in the next inner hierarchy level: threads for a warp parent and physical warps for a block parent. Its count and keyword-only exhaustive arguments are compile-time constants. An exhaustive partition must divide the parent exactly; exhaustive=False permits a remainder. Nested partitions are unsupported. See cuda.coop.ThreadGroup.group_by() for a host-side descriptor example.

Block Load and Store accept direct, striped, vectorize, transpose, warp_transpose, and warp_transpose_timesliced. Physical and logical Warp operations accept direct, striped, vectorize, and transpose. The planner selects the corresponding CUB specialization and records storage and synchronization requirements for the compiler backend.

load(group, source, output) fills a ThreadData object in place and returns None. store(group, destination, value) writes a per-thread value or ThreadData payload. Both accept a nonnegative element offset and a valid_items count within the selected group tile. Load also accepts oob_default with valid_items to fill invalid output slots.

Participation and synchronization#

Every member of a participating group must reach the same cooperative call. A branch around a block operation must be uniform across the block; a branch around a logical-warp operation must be uniform within that logical warp. Complete sibling logical groups may follow different paths. Warp operations require a block size divisible by 32, with no incomplete final physical warp.

Do not put a block Load or Store inside a per-element if index < count condition. Use valid_items to describe the valid prefix while all block threads participate. An early return by some threads also violates participation if the remaining threads later execute a block operation.

A scratch-reuse barrier protects temporary storage. Its presence depends on the algorithm and storage policy; arrange explicit synchronization wherever application-owned shared memory requires it. A barrier does not make divergent participation safe.

Per-thread payloads#

coop.ThreadData(items_per_thread) gives each thread that many slots. With 128 threads, a group tile contains 128 * items_per_thread values. Each thread accesses its own slots with items[i]. Pass the positive compile-time count as a kernel argument using the backend’s supported specialization mechanism. The count is also available as items.items_per_thread.

Leave the element type unspecified for normal use. A supported producer such as Load supplies it; backend-supported typed assignments can also establish it. Explicit type information is useful when the surrounding program cannot identify the intended representation. Conflicting typed values are errors. An explicit alignment requests a minimum storage alignment in bytes and must be a positive compile-time power of two. It does not assert alignment of the arrays passed to Load or Store.

The contents are uninitialized. Write every item before reading it. A full Load initializes the payload; a partial Load leaves invalid slots unspecified unless oob_default is provided, even if initialized before Load. Construction does not synchronize threads.

cuda.coop.ThreadDataLike describes this payload interface for typing. Use cuda.coop.ThreadData() inside a supported kernel to construct one. An arbitrary Python object implementing the protocol does not automatically become a compiler-supported payload.

Data layout#

For four threads holding two items each, a blocked layout assigns consecutive items to each thread: thread 0 holds [0, 1], thread 1 holds [2, 3], and so on. A striped layout assigns consecutive items across threads: thread 0 holds [0, 4], thread 1 holds [1, 5], and so on.

The direct and vectorize algorithms produce blocked per-thread data. striped produces striped per-thread data. Transpose algorithms use shared memory to combine coalesced accesses with blocked per-thread data.

For group size G, items per thread K, thread rank t, and item index i, blocked order uses tile position t * K + i; striped order uses t + i * G. Store interprets its payload according to its algorithm, so pair Load and Store algorithms with matching layouts. The payload carries no runtime layout tag that corrects a mismatched pair.

Temporary storage#

direct, striped, and vectorize require no shared temporary storage. Transpose algorithms require storage and synchronization. The planner lets the backend allocate storage automatically. Block operations can also accept a TempStorage object when a kernel needs to control its allocation or reuse. Warp operations require backend-managed storage with a separate slice for each participating group.

Scratch belongs to one block during its kernel execution. A descriptor does not carry data between blocks or kernel launches.

TempStorage(size_in_bytes=None, *, alignment=None, auto_sync=False, sharing="shared") describes scratch for supported block calls. An omitted capacity lets the compiler size the allocation from its uses. An explicit capacity must be positive and large enough for those uses. alignment sets a positive power-of-two minimum in bytes; the compiler may strengthen it.

sharing="shared" allows call sites using the same descriptor to overlap their scratch slices. sharing="exclusive" gives distinct call sites separate slices, using more shared memory to avoid barriers needed solely for cross-call scratch reuse when auto_sync=False. A loop still reuses each call site’s slice on its next iteration, so that reuse must be synchronized. Application data dependencies may require additional barriers.

TempStorage() defaults to auto_sync=False; passing None also disables automatic reuse barriers. The caller must synchronize before reuse. Use TempStorage(auto_sync=True) to request a trailing barrier after each scratch-using call. Omitting temp_storage lets the compiler choose the layout and insert reuse barriers. It may reuse scratch across compatible calls; omission does not guarantee a separate slice per call site.

cuda.coop.TempStorageLike is the descriptor interface used in type signatures. The scratch contents are opaque; keep application data in ThreadData or application-owned arrays.

The shared API functions are compiler markers; invoking an operation outside a supported kernel compiler raises an error. Group descriptors can be created and inspected from ordinary Python.

See cuda.coop API Reference for the shared API reference.