Module Tolk.Tc
Tensor cores.
A tensor core computes D = A * B + C on small tiles, with A of M rows and K columns, B of K rows and N columns, and C and D of M rows and N columns. Every dimension is a power of two. A group of threads, a warp, computes one product together: each thread holds some elements of each operand, in registers.
A fragment says which thread holds which element. A thread is numbered by its lane in the group, and its registers by their element index; each bit of the lane number and of the element index is one bit of the tile coordinates. The tensor cores of each target are values of this module.
Fragments
The type for the bits of tile coordinates: M i is bit i of the row, N i bit i of the column, K i bit i of the reduction index.
type fragment = {- #
lanes : bit list;(*The bit of the coordinates each bit of the lane number gives, least significant first.
*) - #
elements : bit list;(*The bit each bit of the element index gives, least significant first.
*)
}The type for fragments: where an operand's elements are held. A lane bit of a dimension the operand does not have (an N bit for A, an M bit for B) broadcasts: lanes that differ only by it hold the same elements.
Tensor cores
type t = private {- #
dtype_in : Dtype.t;(*The type of
*)AandB. - #
dtype_out : Dtype.t;(*The type of
*)CandD. - #
frag_a : fragment;(*
*)A's fragment, ofMandKbits. - #
frag_b : fragment;(*
*)B's fragment, ofKandNbits. - #
frag_c : fragment;(*
*)C's andD's fragment, ofMandNbits. Its lanes are the threads of the warp, and its elements are unrolled in each thread.
}The type for tensor cores.
val v :
dtype_in:Dtype.t ->
dtype_out:Dtype.t ->
frag_a:fragment ->
frag_b:fragment ->
frag_c:fragment ->
tv ~dtype_in ~dtype_out ~frag_a ~frag_b ~frag_c is the tensor core with these types and fragments. The bits of the tile are those axis_coords derives.
Raises Invalid_argument naming the fragment at fault unless:
- the three fragments have as many lanes;
- each fragment's bits are distinct, its elements are bits of its own dimensions, it has every bit of its own dimensions, and its other bits are
MorNbits; AandBhold theKbits in the same order, elements first.
axis_coords tc is every bit of the tile, the N bits, then the M bits, then the K bits, each from bit 0. A dimension has the bits up to the highest that A's or C's fragment names.
base_upcast_axes tc is the bits of C's elements, then the K bits, most significant first.
relabel tc is, for A and for B, each bit of its fragment paired with the bit it takes the place of when its elements are loaded as C's: its lane bits pair with C's lane bits in order, its element bits with the first of base_upcast_axes, from the last.
frag_coords tc is, for A, B and C in turn, the coordinates of the element that each lane holds at each element index: (row, column) for A and C, (k, column) for B.
pp formats a tensor core as its types and fragments: TensorCore(dtype_in=dtypes.f16, dtype_out=dtypes.f32, frag_a=(('k1', 'm0', ...), ('k0',)), frag_b=..., frag_c=...).
Chaining
A product's accumulator can be the next product's operand in registers when each element the operand needs is one its own lane holds, as an attention kernel takes the scores' accumulator as the weights of the values' product. As operand A, the accumulator's rows stay the next product's rows and its columns become K; as B, its columns stay and its rows become K. The dimension that stays is the kept dimension.
type operand = The type for operands of a product.
type chain = {- #
k_bits : int list;(*Bit
*)iof the accumulator's dimension that becomesKis bitList.nth k_bits iof the next product'sK. - #
same_lines : bool;(*Whether each lane of the two products' accumulators holds the same lines of the kept dimension. A value per line computed in the first accumulator's lanes, such as a row's maximum, then scales the second accumulator in place.
*)
}The type for how an accumulator becomes an operand.
chain x y op is how x's accumulator is y's operand op with every element in the lane that holds it, if it can be. y's other operand must read K permuted as k_bits says. The bits of y's K that k_bits does not name are element bits of op numbering accumulators of x; the elements whose bits there read e come from accumulator e. A bit keeps its own index where op's elements leave it free. The elements stay of x's dtype_out, and a program converts them to y's dtype_in.
It is None if x and y differ in threads, their kept dimensions differ in size, x's other dimension is larger than y's K, or some lane of op needs an element another lane of x's accumulator holds.
transpose tc is the core of Dᵀ = Bᵀ Aᵀ + Cᵀ, which a program computes by giving tc its operands in the other order: its A is tc's B and its B is tc's A, each with its M and N bits exchanged, and its C is tc's transposed. transpose (transpose tc) is tc, and chain (transpose x) (transpose y) A is chain x y B: an accumulator that chains only as B chains as A in the transposes.
Targets
The tensor cores of each target, one per tile shape and pair of types.
cuda_sm75 is NVIDIA's tensor cores from compute capability 7.5: 8 x 16 x 8 tiles of Dtype.t.Float16, into Dtype.t.Float32 or Dtype.t.Float16.
cuda_sm80 is those from compute capability 8.0: cuda_sm75, 8 x 16 x 16 tiles of Dtype.t.Float16 and Dtype.t.Bfloat16, and 8 x 16 x 8 tiles of Dtype.t.Float32.
cuda_sm89 is those from compute capability 8.9: cuda_sm80 and 8 x 16 x 32 tiles of the two 8-bit floats.
amd_rdna4 is RDNA 4's 16 x 16 x 16 tensor cores, of the 16-bit floats, of Dtype.fp8_ocp into Dtype.t.Float32 and of Dtype.t.Int8 into Dtype.t.Int32.
amd_cdna3 is CDNA 3's tensor cores, of K 32 for the 8-bit floats without infinities and 16 for the 16-bit floats.
amd_cdna4 is CDNA 4's tensor cores, of K 128 and 32 for the 8-bit floats, 32 and 16 for the 16-bit floats.
val pm_wmma_fp8 : Dtype.t -> ('ctx, Ops.t) Ops.Pattern_matcher.tpm_wmma_fp8 dt makes a tensor core product of 8 lanes of 8-bit floats into Dtype.t.Float32 a product of its operands' bits as dt: the words in which AMD's builtins take them.
metal is Apple's 8 x 8 x 8 tensor cores. Metal leaves which thread holds which element of a simdgroup matrix unspecified: these fragments are those observed on Apple7 and later. A product on fragments that compute it correctly stays correct when its accumulator is chained, so a chain assumes nothing more than a product does.