GizStack
View .mdOpen in ChatGPTOpen in Claude

gizml:tile

GPU kernels as ordinary Gizlang over tiles, on every device.

A tile program says what one block of the output is; this module decides everything else: threads, which thread holds which element, workgroup memory, barriers, bounds masks, matrix-core fragments, layout conversions and the device. The same program runs on host tensors (the CPU, one program per grid point in turn) and on a GPU's tensors (NVIDIA PTX, AMD LLVM through comgr, both from gizhw:kernel). A tile program is ordinary Gizlang, and it reads as math: a * b + c is a.mul(b).add(c).

Tile.run([Tile.cdiv(m, 128), Tile.cdiv(n, 128)], (p: Tile.Program): void => { const i = p.id(0); const j = p.id(1); const A = p.read(a, [128, 32]); const B = p.read(b, [32, 128]); const acc = p.fold(p.int(0), A.blocks(1), p.zeros([128, 128]), (k: Tile.Index, acc: Tile.Tile): Tile.Tile => acc.add(A.at([i, k]).dot(B.at([k, j])))); p.write(c, [128, 128]).put(acc.relu(), [i, j]); });

The body runs once per launch and records the tile IR (below). Tiles are immutable values of a static shape whose sizes are powers of two; a 1-d tile of C elements is the row [1, C]. Method names are the operators' (add, sub, mul, div, lt, ...) and k lifts a literal, so operator syntax can lower onto them.

gizlang
import * as Tile from "gizml:tile";

Types

Program

gizlang
type Program

One grid point's program while it is traced.

Opaque: create and use it through this module's functions.

Tile

gizlang
type Tile

A block of values: shape is [rows, columns], dtype the storage type its values came from (F32 after math).

  • shape: Vec<i64>
  • dtype: i64

Index

gizlang
type Index

A uniform integer: a program id, a size, a loop counter, or math on them.

Opaque: create and use it through this module's functions.

In

gizlang
type In

A tensor read in blocks.

Opaque: create and use it through this module's functions.

Out

gizlang
type Out

A tensor written in blocks.

Opaque: create and use it through this module's functions.

Functions

cdiv

gizlang
function cdiv(a: i64, b: i64): i64

ceil(a / b) for a launch's grid.

Effects: fail.

run

gizlang
function run(grid: Vec<i64>, body: (p: Program) => void): Vec<Tensor>

Runs body once for each point of grid (1 to 3 sizes, axis 0 fastest) where its tensors are: on their GPU, or on the CPU for host tensors. Returns the tensors the body wrote, in the order of its write calls.

Effects: fail, read, write, console, io.

tune

gizlang
function tune<C>(configs: Vec<C>, grid: (c: C) => Vec<i64>, body: (p: Program, c: C) => void): Vec<Tensor>

As run, for each configuration in configs (block sizes, warps: any record), keeping the fastest on this GPU for these tensor shapes (measured once, then cached); on the CPU the first. Every configuration must compute the same result, and the body must not read what it writes (each runs several times while measured).

ir

gizlang
function ir(grid: Vec<i64>, storage: Vec<i64>, kind: string, arch: string, warps: i64, body: (p: Program) => void): string

The tile IR of body as text, one node a line: its index, kind, shape, storage type, operands and the layout the lowering for kind and arch gives it (free: made where used; uniform; blocked; mma WMxWN warps; slice of a layout along an axis). What to read first when a tile program is slower than expected: every "slice"/"blocked" next to an "mma" is data moved through workgroup memory.

Effects: fail, read, write, console, io.

source

gizlang
function source(grid: Vec<i64>, storage: Vec<i64>, kind: string, arch: string, warps: i64, body: (p: Program) => void): string

The GPU code a launch of body runs on a GPU of kind ("cuda": PTX, "hip": AMDGPU LLVM, "metal": MSL) and arch ("sm_120", "gfx1151", "apple9"), with no GPU needed: to read what the lowering made. storage replaces the tensors' storage types, one per read or write in order (empty: theirs), so host tensors can stand for BF16 ones. warps 0 lets it choose.

Effects: fail, read, write, console, io.

Methods

Program.id

gizlang
function Program.id(self: Program, axis: i64): Index

This program's position along grid axis axis (0 is the fastest).

Effects: fail, read, write, console.

Program.count

gizlang
function Program.count(self: Program, axis: i64): Index

The grid's size along axis.

Effects: fail, read, write, console.

Program.int

gizlang
function Program.int(self: Program, value: i64): Index

An integer constant as an index.

Effects: fail, write.

Program.k

gizlang
function Program.k(self: Program, value: f64): Tile

A constant as a [1, 1] tile, which broadcasts.

Effects: fail, write, console.

Program.full

gizlang
function Program.full(self: Program, shape: Vec<i64>, value: f64): Tile

A tile of SHAPE with every value VALUE.

Effects: fail, write, console.

Program.zeros

gizlang
function Program.zeros(self: Program, shape: Vec<i64>): Tile

A tile of SHAPE with every value 0.

Effects: fail, write, console.

Program.iota

gizlang
function Program.iota(self: Program, shape: Vec<i64>, axis: i64): Tile

Each element's row number (axis 0) or column number (axis 1) within the tile, as F32.

Effects: fail, write, console.

Program.warps

gizlang
function Program.warps(self: Program, count: i64): void

The number of warps (32 threads each) of a GPU workgroup: 1, 2, 4, 8 or 16. Without it the lowering picks.

Effects: fail, write, console.

Program.stages

gizlang
function Program.stages(self: Program, count: i64): void

How many blocks of a loop's matrix-core operands are in flight: 2 (the default) reads the next turn's blocks while this turn's product runs, more hide longer memory latency at the cost of workgroup memory, 1 reads each turn's in turn.

Effects: fail, write, console.

Program.read

gizlang
function Program.read(self: Program, t: Tensor, block: Vec<i64>): In

t read in blocks of block (its last 1 or 2 dimensions); the dimensions before are addressed by element.

Effects: fail, write, console.

Program.write

gizlang
function Program.write(self: Program, t: Tensor, block: Vec<i64>): Out

t written in blocks of block: Tile.run returns it with what the programs stored; the rest keeps its values.

Effects: fail, write, console.

Program.loop

gizlang
function Program.loop(self: Program, from: Index, to: Index, inits: Vec<Tile>, body: (k: Index, carried: Vec<Tile>) => Vec<Tile>): Vec<Tile>

body(k, carried) for k from from to to - 1, each time with the values the turn before returned (inits the first time); the values of the last turn (or inits when there is none). The carried tiles keep their shapes.

Effects: fail, read, write, console, io.

Program.fold

gizlang
function Program.fold(self: Program, from: Index, to: Index, init: Tile, body: (k: Index, acc: Tile) => Tile): Tile

loop with one carried tile.

Effects: fail, read, write, console, io.

Index.k

gizlang
function Index.k(self: Index, value: i64): Index

A constant in this index's program.

Effects: fail, write.

Index.add

gizlang
function Index.add(self: Index, other: Index): Index

SELF + OTHER.

Effects: fail, write, console.

Index.sub

gizlang
function Index.sub(self: Index, other: Index): Index

SELF - OTHER.

Effects: fail, write, console.

Index.mul

gizlang
function Index.mul(self: Index, other: Index): Index

SELF * OTHER.

Effects: fail, write, console.

Index.div

gizlang
function Index.div(self: Index, other: Index): Index

Division and remainder truncate toward zero, as i64's.

Effects: fail, write, console.

Index.rem

gizlang
function Index.rem(self: Index, other: Index): Index

The remainder of SELF / OTHER, with the sign of SELF.

Effects: fail, write, console.

Index.minimum

gizlang
function Index.minimum(self: Index, other: Index): Index

The smaller of SELF and OTHER.

Effects: fail, write, console.

Index.maximum

gizlang
function Index.maximum(self: Index, other: Index): Index

The larger of SELF and OTHER.

Effects: fail, write, console.

Index.value

gizlang
function Index.value(self: Index): Tile

The index as a [1, 1] F32 tile (exact below 2^24).

Effects: fail, write.

In.at

gizlang
function In.at(self: In, coords: Vec<Index>): Tile

The block at block coordinates coords (one per tensor dimension; leading unblocked dimensions by element). Elements past the tensor's edge read 0.

Effects: fail, read, write, console.

In.window

gizlang
function In.window(self: In, offsets: Vec<Index>): Tile

The block whose first element is at element offsets offsets (any alignment; outside the tensor reads 0).

Effects: fail, read, write, console.

In.dim

gizlang
function In.dim(self: In, axis: i64): Index

The tensor's size along axis.

Effects: fail, read, write, console.

Out.dim

gizlang
function Out.dim(self: Out, axis: i64): Index

The output's size along AXIS.

Effects: fail, read, write, console.

In.blocks

gizlang
function In.blocks(self: In, axis: i64): Index

The number of blocks along axis (its size for a leading dimension).

Effects: fail, read, write, console.

Out.blocks

gizlang
function Out.blocks(self: Out, axis: i64): Index

The number of blocks of the output along AXIS (its size for a leading dimension).

Effects: fail, read, write, console.

Out.put

gizlang
function Out.put(self: Out, t: Tile, coords: Vec<Index>): void

Stores t (the block's shape) as the block at coords; elements past the tensor's edge are not written.

Effects: fail, read, write, console.

Out.putWindow

gizlang
function Out.putWindow(self: Out, t: Tile, offsets: Vec<Index>): void

Stores t with its first element at element offsets offsets.

Effects: fail, read, write, console.

Tile.k

gizlang
function Tile.k(self: Tile, value: f64): Tile

A constant [1, 1] tile in this tile's program.

Effects: fail, write.

Tile.add

gizlang
function Tile.add(self: Tile, other: Tile): Tile

SELF + OTHER, element by element, with broadcasting.

Effects: fail, write, console.

Tile.sub

gizlang
function Tile.sub(self: Tile, other: Tile): Tile

SELF - OTHER, element by element, with broadcasting.

Effects: fail, write, console.

Tile.mul

gizlang
function Tile.mul(self: Tile, other: Tile): Tile

SELF * OTHER, element by element, with broadcasting.

Effects: fail, write, console.

Tile.div

gizlang
function Tile.div(self: Tile, other: Tile): Tile

SELF / OTHER, element by element, with broadcasting.

Effects: fail, write, console.

Tile.maximum

gizlang
function Tile.maximum(self: Tile, other: Tile): Tile

Elementwise larger and smaller value; with a NaN, the other value.

Effects: fail, write, console.

Tile.minimum

gizlang
function Tile.minimum(self: Tile, other: Tile): Tile

The smaller of SELF and OTHER, element by element; with a NaN, the other value.

Effects: fail, write, console.

Tile.lt

gizlang
function Tile.lt(self: Tile, other: Tile): Tile

Comparisons give masks; a NaN compares false (ne: true).

Effects: fail, write, console.

Tile.le

gizlang
function Tile.le(self: Tile, other: Tile): Tile

The mask of SELF <= OTHER; a NaN compares false.

Effects: fail, write, console.

Tile.gt

gizlang
function Tile.gt(self: Tile, other: Tile): Tile

The mask of SELF > OTHER; a NaN compares false.

Effects: fail, write, console.

Tile.ge

gizlang
function Tile.ge(self: Tile, other: Tile): Tile

The mask of SELF >= OTHER; a NaN compares false.

Effects: fail, write, console.

Tile.eq

gizlang
function Tile.eq(self: Tile, other: Tile): Tile

The mask of SELF == OTHER; a NaN compares false.

Effects: fail, write, console.

Tile.ne

gizlang
function Tile.ne(self: Tile, other: Tile): Tile

The mask of SELF != OTHER; a NaN compares true.

Effects: fail, write, console.

Tile.and

gizlang
function Tile.and(self: Tile, other: Tile): Tile

The mask that is true where both masks are true.

Effects: fail, write, console.

Tile.or

gizlang
function Tile.or(self: Tile, other: Tile): Tile

The mask that is true where either mask is true.

Effects: fail, write, console.

Tile.not

gizlang
function Tile.not(self: Tile): Tile

The mask that is true where SELF is false.

Effects: fail, write, console.

Tile.select

gizlang
function Tile.select(self: Tile, yes: Tile, no: Tile): Tile

For a mask: yes where it is true, else no (all three broadcast).

Effects: fail, write, console.

Tile.neg

gizlang
function Tile.neg(self: Tile): Tile

-SELF, element by element.

Effects: fail, write, console.

Tile.abs

gizlang
function Tile.abs(self: Tile): Tile

The absolute value of SELF, element by element.

Effects: fail, write, console.

Tile.exp

gizlang
function Tile.exp(self: Tile): Tile

e^x, ln x, tanh, sigmoid and GELU within 2-3 ulp: gizml:tensor's polynomials on every device.

Effects: fail, write, console.

Tile.log

gizlang
function Tile.log(self: Tile): Tile

The natural logarithm of SELF, element by element, within 2-3 ulp.

Effects: fail, write, console.

Tile.sqrt

gizlang
function Tile.sqrt(self: Tile): Tile

The square root of SELF, element by element.

Effects: fail, write, console.

Tile.rsqrt

gizlang
function Tile.rsqrt(self: Tile): Tile

1 / sqrt(x), as a division of the rounded root.

Effects: fail, write, console.

Tile.sigmoid

gizlang
function Tile.sigmoid(self: Tile): Tile

The sigmoid 1 / (1 + e^-x) of SELF, element by element, within 2-3 ulp.

Effects: fail, write, console.

Tile.tanh

gizlang
function Tile.tanh(self: Tile): Tile

The hyperbolic tangent of SELF, element by element, within 2-3 ulp.

Effects: fail, write, console.

Tile.gelu

gizlang
function Tile.gelu(self: Tile): Tile

GELU, the tanh form (gelu) and the erf form (geluErf).

Effects: fail, write, console.

Tile.geluErf

gizlang
function Tile.geluErf(self: Tile): Tile

GELU of SELF in the erf form, element by element.

Effects: fail, write, console.

Tile.relu

gizlang
function Tile.relu(self: Tile): Tile

max(SELF, 0), element by element.

Effects: fail, write, console.

Tile.floor

gizlang
function Tile.floor(self: Tile): Tile

SELF rounded down to an integer, element by element.

Effects: fail, write, console.

Tile.to

gizlang
function Tile.to(self: Tile, dtype: i64): Tile

The values rounded to storage type dtype (F32, F16, BF16 or FP8), as a store and a load of it give them.

Effects: fail, write, console.

Tile.sum

gizlang
function Tile.sum(self: Tile, axis: i64): Tile

Sum, largest and smallest along axis, keeping it as size 1: [R, C].sum(1) is [R, 1].

Effects: fail, write, console.

Tile.max

gizlang
function Tile.max(self: Tile, axis: i64): Tile

The largest value along AXIS, which stays as size 1.

Effects: fail, write, console.

Tile.min

gizlang
function Tile.min(self: Tile, axis: i64): Tile

The smallest value along AXIS, which stays as size 1.

Effects: fail, write, console.

Tile.dot

gizlang
function Tile.dot(self: Tile, other: Tile): Tile

The matrix product [M, K] [K, N] -> [M, N] in F32. With F16 or BF16 operands of one type it runs on matrix cores where the GPU has them; acc.add(a.dot(b)) accumulates into acc there.

Effects: fail, write, console.

Tile.transpose

gizlang
function Tile.transpose(self: Tile): Tile

Rows become columns.

Effects: fail, write.

Constants

F32

gizlang
const F32 = 0

The storage type of 32-bit floats.

F16

gizlang
const F16 = 4

The storage type of IEEE half-precision floats.

BF16

gizlang
const BF16 = 5

The storage type of bfloat16 floats.

FP8

gizlang
const FP8 = 9

The storage type of FP8 E4M3 floats.

MASK

gizlang
const MASK = -1

The type of a comparison's result: a mask, for select, and, or and not.