Memory

Kernels move data between global-memory arrays and tiles with ct.load and ct.store.

OperationDescription
ct.load(arr; index, shape, ...)Load a tile from array
ct.store(arr; index, tile, ...)Store a tile to array
ct.gather(arr, indices; ...)Gather elements by index tile
ct.scatter(arr, indices, tile; ...)Scatter elements by index tile

Their keyword arguments differ: load controls padding and both load and store accept an axis order and TMA hint; gather and scatter accept a per-element mask. All four support bounds checking and a latency hint. See load, store, gather and scatter for the exact signatures.

# Gather with user mask and custom padding for masked-out elements
tile = ct.gather(arr, indices; mask=valid_mask, padding_value=-1.0f0)

# Scatter with mask (only write where mask is true)
ct.scatter(arr, indices, tile; mask=active_mask)

The latency hint, and the allow_tma hint on loads and stores, influence how memory traffic is scheduled; see Performance. Version requirements mentioned on this page are collected in Compatibility.

Bounds checking

ct.load and ct.store are bounds-checked by default: a tile that partially extends past the array is padded on load (per padding_mode) and clipped on store. Set check_bounds=false only when every element of the tile is in the underlying array. That is an unsafe full-tile promise: it drops load padding, emits Tile IR's inbounds attribute, and requires bytecode v13.4 or newer.

Tile memory bounds handling is not Julia's protective bounds checking. @inbounds and --check-bounds=yes|no affect real Julia @boundscheck blocks, but do not change the padding, clipping, masks, or Tile IR attributes of ct.load, ct.store, eachtile, sparse views, gathers, scatters, or atomics. Use the explicit keyword when the stronger full-tile promise holds.

Automatic rank matching

ct.load and ct.store automatically match the tile rank to that of the target:

  • Lower rank: trailing 1s are appended. Loading (M, N) from a 4D array internally uses (M, N, 1, 1). Storing a scalar tile into a 2D array pads to (1, 1).
  • Higher rank: trailing 1s are stripped. Storing (M, 1) into a 1D array reshapes to (M,). Non-trailing singletons (e.g. from sum(tile; dims=1)) require explicit dropdims.

Indexing

OperationDescription
arr[i, j, ...]Load scalar element from TileArray
arr[i, j, ...] = valStore scalar element to TileArray
tile[i, j, ...]Extract scalar from Tile
setindex(tile, val, i, j, ...)Return new Tile with element replaced

Views

OperationDescription
@view arr[r1:r2, :, ...] / view(arr, ...)Sub-range view
permutedims(arr, perm)Permute axes (1-indexed)
transpose(arr)2D transpose (permutedims(arr, (2, 1)))
reshape(arr, dims)Column-major reshape, requires contiguous source

@view and view derive a sub-range TileArray from an existing one. Each index must be :, a UnitRange (e.g. i:j), or a positive StepRange (e.g. i:s:j); scalar Int and CartesianIndex forms are rejected at compile time. A StepRange changes the element stride inside the resulting TileArray. The result can be passed to ct.load/ct.store (or sliced again). Runtime asserts verify that ranges start at ≥ 1 and have a positive step; negative steps cannot be represented.

function rowsum(a, b, r1::Int32, r2::Int32)
    sub = @view a[r1:r2, :]                    # sub-range TileArray
    tile = ct.load(sub, (1, 1), (4, 4))
    ct.store(b, (1, 1), sum(tile; dims=2))
    return
end

Sparse views

For a 2D-or-higher array, one 1D integer Tile index plus integer unit ranges (or :) in every other dimension creates a sparse view consumed only by ct.load and ct.store. Public indices are one-based; the load shape is explicit and static while range starts may be runtime values. A : dense dimension starts at element 1 and takes its extent from the load shape.

rows = ct.arange(4; start=1, step=2)
selected = @view a[rows, col_start:col_start+3]
tile = ct.load(selected, (4, 4); padding_mode=ct.PaddingMode.Zero)
ct.store(selected, tile)

Sparse loads apply the requested padding and stores clip partially out-of-bounds elements. Repeated sparse indices are valid for loads, but conflicting stores are undefined. This requires Tile IR v13.3. Direct bracket access, view atomics, and Python-style advanced-indexing function names are intentionally not provided.

Tile windows

eachtile creates a small, indexable device-side collection of fixed-shape tiles. Its indices are 1-based and step (one entry per tile dimension) controls tile origins, not the element stride inside a tile. size(tiles, d) is the number of tiles along d: on the host it computes cld(size(a, d), step[d]) for launch-grid sizing, while inside a kernel it queries the Tile IR backend for the authoritative index-space count:

adjacent = eachtile(a, (8, 8))              # default: step == (8, 8)
overlap  = eachtile(a, (8, 8); step=(4, 8)) # neighboring windows overlap
gapped   = eachtile(a, (8, 8); step=(16, 8)) # gaps between row windows

tile = overlap[2, 1]
overlap[2, 1] = tile

Equal shape and step work at any supported bytecode version; unequal values require Tile IR bytecode v13.3 or newer. This is distinct from @view a[1:2:end, :], which steps individual elements rather than tile origins. See eachtile for the remaining keyword arguments.