Operations
Most scalar operations listed here work on both scalars and tiles. Use Julia's broadcast syntax (the . operator) to apply any scalar function element-wise over tiles: sqrt.(tile), max.(a, b), cld.(tile, 4), and so on.
Operations prefixed with ct. are cuTile intrinsics without a direct Julia equivalent; everything else is standard Julia syntax overlaid on Base. Operations that need a particular Tile IR version or GPU architecture are noted inline and collected in Compatibility.
Arithmetic
| Operation | Description |
|---|---|
+, - | Element-wise (same shape only) |
tile * scalar, tile / scalar | Scalar multiply/divide |
.+, .-, .*, ./, .^ | Broadcasting element-wise |
mod.(x, y) | Floored remainder with Julia sign semantics |
ct.divmod(x, y) | Floored quotient and remainder |
Note that + and - apply directly to tiles of matching shape, and * and / to a tile and a scalar, but combining tiles of different shapes requires broadcast syntax. a * b between two tiles is matrix multiplication, not an element-wise product.
Construction
| Operation | Description |
|---|---|
zeros(T, dims...) | Zero-filled tile |
ones(T, dims...) | One-filled tile |
fill(value, dims...) | Constant-filled tile |
ct.arange(n; dtype, start, step) | Configurable arithmetic sequence (defaults to 1:n) |
Shape
| Operation | Description |
|---|---|
reshape(tile, shape) | Reshape (same element count) |
transpose(tile) | Transpose 2D tile |
permutedims(tile, perm) | Permute dimensions |
repeat(tile, counts...) repeat(tile; inner, outer) | Repeat values along dimensions |
ct.extract(tile, index, shape) | Extract sub-tile |
ct.insert(tile, index, value) | Replace a non-overlapping sub-tile (Tile IR v13.4+) |
ct.cat((a, b), axis) | Concatenate tiles |
ct.broadcast_to(tile, shape) | Broadcast to target shape |
dropdims(tile; dims) | Remove singleton dimensions |
Matrix multiplication
| Operation | Description |
|---|---|
a * b | Matrix multiplication: a @ b |
muladd(a, b, acc; fast_acc=false) | Matrix multiply-accumulate: a * b + acc |
ct.muladd_scaled(a, a_scale, b, b_scale, acc) | Block-scaled multiply-accumulate |
Each operation follows Base.:* / Base.muladd's shape rules, with the addition of allowing trailing batch dimensions.
fast_acc=true enables fast accumulation for FP8 inputs, and has an effect only on Hopper (sm_90; silently ignored on other architectures), and requires Tile IR v13.3+.
ct.muladd_scaled multiplies each operand by a low-precision block scale before the matmul: each scale element covers a contiguous block of B = K ÷ K_s elements along the K dimension. Requires Blackwell. The supported operand/scale/accumulator dtypes and block sizes are:
Input (a/b) | Scale | Acc/Output | B |
|---|---|---|---|
Float8_E4M3FN, Float8_E5M2 | Float8_E8M0FNU | Float32 | 32 |
Float4_E2M1FN | Float8_E8M0FNU | Float32 | 16, 32 |
Float4_E2M1FN | Float8_E4M3FN | Float32 | 16 |
Reductions and scans
| Operation | Description |
|---|---|
sum(tile; dims) | Sum along axis |
prod(tile; dims) | Product along axis |
maximum(tile; dims, propagate_nan) | Maximum along axis |
minimum(tile; dims, propagate_nan) | Minimum along axis |
any(tile; dims) | Logical OR along axis |
all(tile; dims) | Logical AND along axis |
count(tile; dims) | Count true elements along axis |
argmax(tile; dims, propagate_nan) | 1-based index of maximum along axis |
argmin(tile; dims, propagate_nan) | 1-based index of minimum along axis |
cumsum(tile; dims, rev) | Cumulative sum |
cumprod(tile; dims, rev) | Cumulative product |
Following Base semantics, reductions keep the reduced dimension as size 1. Use dropdims to remove it.
Higher-order functions
| Operation | Description |
|---|---|
map(f, tiles...) | Apply function element-wise (same shape) |
f.(tiles...), broadcast(f, tiles...) | Apply function with shape broadcasting |
reduce(f, tile; dims, init) | Reduction with arbitrary function |
mapreduce(f, op, tile; dims, init) | Map then reduce |
accumulate(f, tile; dims, init, rev) | Scan/prefix-sum with arbitrary function |
Any function that works on scalars "just works" when broadcast over tiles, so these cover element-wise operations that have no dedicated entry in the tables above.
Broadcasting shape alignment
cuTile.jl uses Julia's standard left-aligned broadcast shape rules: dimensions are matched starting from the first (leftmost) dimension. A 1D (N,) tile therefore cannot broadcast with a 2D (M, N) tile, because dimension 1 has size N vs M. Use reshape to align dimensions, just as with regular Julia arrays:
a = ct.load(...) # (N,)
b = ct.load(...) # (M, N)
result = reshape(a, (1, N)) .+ b # (1, N) .+ (M, N) → (M, N)This differs from NumPy's (and cuTile Python's) right-aligned rules; see Comparison with cuTile Python.
Math
| Operation | Description |
|---|---|
sqrt(x) | Square root |
rsqrt(x) | Reciprocal square root |
exp(x), exp2(x) | Exponential |
log(x), log2(x) | Logarithm |
sin(x), cos(x), tan(x) | Trigonometric functions |
sinh(x), cosh(x), tanh(x) | Hyperbolic functions |
fma(a, b, c) | Fused multiply-add |
abs(x) | Absolute value |
isnan(x) | NaN test |
max(a, b), min(a, b) | Maximum/minimum |
ceil(x), floor(x) | Rounding |
ct.@fpmode rounding_mode=ct.Rounding.Approx flush_to_zero=true begin ... end | Scoped FP rounding mode and flush-to-zero |
Comparison
| Operation | Description |
|---|---|
.<, .>, .<=, .>= | Element-wise comparison (returns a Bool tile) |
.==, .!= | Element-wise equality |
ifelse.(cond, x, y) | Element-wise conditional selection |
Integer and bitwise
| Operation | Description |
|---|---|
cld(a, b) | Ceiling division |
fld(a, b) | Floor division |
div(a, b) | Truncating division |
mul_hi(a, b) | High bits of integer multiply (Base.mul_hi on Julia 1.13+) |
.~x | Element-wise bitwise NOT |
.&, .|, xor. | Element-wise bitwise AND, OR, XOR |
Type conversion
Element-type conversion and reinterpretation are covered in Element Types.
Reference
The entries below are the operations with cuTile-specific behaviour or no Base counterpart. Everything else in this chapter is a Base function that cuTile overlays, documented by Base itself.
cuTile.arange — Function
arange(shape; dtype=Int32, start=1, step=1) -> Tile{dtype, shape}
arange(n; dtype=Int32, start=1, step=1) -> Tile{dtype, Tuple{n}}Create a 1D tile with values [1, 2, 3, ..., n] (1-indexed).
Example
indices = ct.arange(16) # Int32 [1, 2, ..., 16]
indices = ct.arange(16; dtype=Int64) # Int64 [1, 2, ..., 16]
odd = ct.arange(16; start=1, step=2) # Int32 [1, 3, ..., 31]cuTile.cat — Function
cat(tiles::Tuple{Tile, Tile}, axis::Int) -> TileConcatenate two tiles along the specified axis (1-indexed). Supports negative axis (e.g., -1 for last dimension).
Example
tile_a = ct.load(arr_a, (1,), (4, 8)) # Shape (4, 8)
tile_b = ct.load(arr_b, (1,), (4, 8)) # Shape (4, 8)
# Concatenate along axis 1: (4, 8) + (4, 8) -> (8, 8)
combined = ct.cat((tile_a, tile_b), 1)
# Concatenate along axis -1 (last): (4, 8) + (4, 8) -> (4, 16)
combined_last = ct.cat((tile_a, tile_b), -1)cuTile.broadcast_to — Function
broadcast_to(tile::Tile{T, S}, shape::NTuple{N, Int}) -> Tile{T, shape}Explicitly broadcast a tile to a target shape.
Example
row = ct.load(arr, (1, 1), (1, 128)) # Shape (1, 128)
expanded = ct.broadcast_to(row, (64, 128)) # Shape (64, 128)cuTile.extract — Function
extract(tile::Tile{T, S}, index::NTuple{N, Int}, shape::NTuple{N, Int}) -> Tile{T, shape}Extract a sub-tile from a tile at the given slice indices.
IMPORTANT: The index parameter specifies SLICE INDICES, not element offsets!
For each dimension, the source tile is divided into S[i] ÷ shape[i] non-overlapping slices. The index[i] selects which slice to extract (1-indexed).
Example: Extracting quadrants from an 8×8 tile
tile = ct.load(arr, (1, 1), (8, 8))
# 8÷4 = 2 slices per dimension, so valid indices are {1, 2} × {1, 2}
tl = ct.extract(tile, (1, 1), (4, 4)) # Top-left (rows 1-4, cols 1-4)
bl = ct.extract(tile, (2, 1), (4, 4)) # Bottom-left (rows 5-8, cols 1-4)
tr = ct.extract(tile, (1, 2), (4, 4)) # Top-right (rows 1-4, cols 5-8)
br = ct.extract(tile, (2, 2), (4, 4)) # Bottom-right (rows 5-8, cols 5-8)cuTile.insert — Function
insert(tile::Tile, index, value::Tile) -> TileReturn a copy of tile with the non-overlapping subtile at the 1-indexed slice index replaced by value. This is the inverse of extract and requires Tile IR v13.4 or newer.
Base.reinterpret — Method
Base.reinterpret(::Type{T}, x::Tile) -> Tile{T}Reinterpret the whole tile x as a tile of element type T, like reinterpret(T, ::AbstractArray): the underlying bits are viewed as a contiguous (column-major) block and the leading dimension is rescaled by the ratio of element widths. Lowers to cuda_tile.bitcast for equal widths and to cuda_tile.pack/unpack (via reshape to rank-1) when widths differ.
This is how sub-byte formats move through global memory: a Tile{UInt8,Tuple{N}} reinterprets to a Tile{Float4_E2M1FN,Tuple{2N}} and back, so FP4 data can be stored in a UInt8 array. The total bit-width is preserved, so it must divide evenly.
Note reinterpret.(T, x) (with a dot) is the unrelated element-wise broadcast, which keeps the shape and requires T to be the same width as eltype(x).
bytes = ct.load(a, pid, (8,)) # Tile{UInt8,Tuple{8}}
fp4 = reinterpret(Float4_E2M1FN, bytes) # Tile{Float4_E2M1FN,Tuple{16}}
vals = convert(ct.Tile{Float32}, fp4) # widen for computeBase.reinterpret — Method
Base.reinterpret(reshape, ::Type{T}, x::Tile) -> Tile{T}The reshape-form whole-tile reinterpret, mirroring reinterpret(reshape, T, ::AbstractArray): instead of rescaling the leading dimension it removes it when widening (the leading dim must equal bitwidth(T) ÷ bitwidth(eltype(x))) and prepends one when narrowing.
cuTile.divmod — Function
divmod(x, y) -> (q, r)Floored quotient and remainder, i.e. (fld(x, y), mod(x, y)). Accepts integer scalars or integer tiles of matching shape, and follows Julia's sign conventions: the remainder takes the sign of the divisor.
cuTile.rsqrt — Function
rsqrt(x)Reciprocal square root, 1 / sqrt(x), computed as a single Tile IR operation rather than a division followed by a square root. Broadcast it (rsqrt.(tile)) to apply it element-wise over a tile.
Base.muladd — Method
muladd(a::Tile, b::Tile, acc::Tile; fast_acc::Bool=false) -> TileMatrix multiply-accumulate a * b + acc over tiles, lowering to cuda_tile.mmaf (float) or cuda_tile.mmai (i8 × i8 → i32).
a/b are 2-D matrices (M, K) × (K, N) → (M, N); a 1-D operand is promoted (vec-mat / mat-vec) and any trailing dimensions (≥3-D) are treated as broadcast batch dims, lifting Base.muladd's shape rules to tiles. acc carries the result dtype, which must be one tileiras allows for the input dtype (f16/f32 for f16 and f8; f32 for bf16/tf32; f64 for f64; i32 for i8).
fast_acc enables fast accumulation (lower accumulator precision for throughput); it is valid only for FP8 inputs and requires Tile IR v13.3+.
cuTile.muladd_scaled — Function
muladd_scaled(a, a_scale, b, b_scale, acc) -> TileBlock-scaled matrix multiply-accumulate (a ⊙ a_scale) * (b ⊙ b_scale) + acc, lowering to cuda_tile.mmaf_scaled (Tile IR v13.3+, Blackwell). Each scale element multiplies a contiguous block of B = K ÷ K_s elements along the K dimension of its operand, so a_scale/b_scale match a/b in every dimension except K, where they have K_s ≤ K entries.
a/b are low-precision floats (f8e4m3fn, f8e5m2, or f4e2m1fn), a_scale/b_scale are f8e8m0fnu or f8e4m3fn, and acc is f32. Shapes follow muladd: 2-D (M, K) × (K, N), mat-vec, and trailing batch dims; vec-mat is unsupported (it would collapse K, leaving nothing to scale).