github ↗
docs menu

Module Tolk.Postrange

Applying kernel optimisations.

A kernel's axes are its ranges (Op.t.Range) with a positive count, except serial loops, listed by argument (Ops.compare_range_arg), the weak and upcast ranges that a reduction closes last. A range's role in a reduction is that a reduction (Op.t.Reduce) closes it: its axis is a reduce axis, and an upcast of it unrolls the reduction into the thread's lanes. An optimisation (Opt.t) names an axis by its position in that list, and rewrites the kernel: a split replaces an axis r of size n by r' of size n / a and a new axis s of size a, and every use of r by r' * a + s, or s * (n / a) + r' from the top; a pad grows an axis to a multiple and makes the steps it adds no-ops, guarding each access whose index reads the axis and each store and loop back edge (Op.t.Backedge) that runs along it; a swap exchanges two global axes' identities; a tensor core replaces a reduction of products by a matrix multiply-accumulate (Op.t.Wmma).

Schedulers

#module Scheduler : sig ... end

Schedulers.

Optimising

#val split_targets : Opt.target -> Ops.Axis_type.t list

split_targets target is the roles of the axes a split can move to target: global, local and weak axes to Opt.target.Upcast, and global and weak axes to Opt.target.Local.

#val threads_fixed : Ops.t -> bool

threads_fixed ast is true iff the builder of the kernel ast, or of a body it calls, fixed its threads: it holds workgroup or register storage (Dtype.addr_space.Local, Dtype.addr_space.Reg), a barrier, a matrix multiply-accumulate or a call of code in the target's language.

#val misses : Ops.t -> Ops.t -> bool

misses r e is true iff the effect e does not depend on the range r: r is none of the ranges (Ops.ranges) of e's index, its validity included, if e is a store, or of e's sources, but a call's body, if e is a call (Ops.call). Such an effect runs once for all of r's steps.

#val apply_opts : ?beam:(Scheduler.t -> Scheduler.t) -> hand_coded:(Scheduler.t -> Scheduler.t) -> Ops.t -> Renderer.t -> Ops.t

apply_opts ~beam ~hand_coded ast ren is the kernel ast optimised for ren (Scheduler.get_optimized_ast), named by its argument unless that is "test". After making weak output axes global (Scheduler.convert_loop_to_global), it applies the optimisations that ast's argument asks for, or else searches with beam if given, or else, unless the setting Setting.noopt is set, ast already had optimisations applied, or it buffers values (Op.t.Stage), applies hand_coded. A kernel one of whose loops, or of the loops of a body it calls, has a bound that loads from memory (Ops.bound_loads) is not searched: its time depends on what memory holds, which a search's samples do not, so it takes hand_coded as without beam. A kernel whose builder fixed its threads is neither searched nor given hand_coded: it, or a body it calls, holds workgroup or register storage (Dtype.addr_space.Local, Dtype.addr_space.Reg), a barrier, a matrix multiply-accumulate or custom code, made for the threads and lanes its builder chose, which splitting its axes would change; the optimisations it asks for still apply. A kernel ast already optimised (optimized) is returned as it is.

Each role an axis takes keeps every effect, a store, a call or code in the target's language, running once for each step of the ranges it depends on (misses). Lanes run an effect that depends on their axis once for each group of lanes: a call or code takes one value for each source, so an axis one reads takes no lanes (Scheduler.read_by_opaque). Threads run every effect in each thread: a store to global memory that misses a thread axis runs where that axis is 0 (Gpudims.group_gpudims), and an axis that a call or code misses takes no threads (Scheduler.unread_by_opaque). A pad makes the steps it adds no-ops: a store and a loop's back edge that run along its axis are guarded, and a call or code cannot be, so an axis one runs along takes no pad. The blocks of a host program each run every effect, so a loop splits into blocks (Ops.split) only when no store to memory, call or code misses it.

Before any optimisation, it refuses a kernel whose store breaks a weak range's promise visibly (Ops.Axis_type.t.Weak): a store to global or workgroup memory that names fewer cells than it has points along one of its weak ranges, or along all of them together, where neither its validity nor its gate reads them, as C[r // 8] += 1 over a weak r < 16 or C[r + s] += 1 over weak r and s. An offset's cells are counted as the multiples of the greatest common divisor of the constant factors of its terms that read the ranges, between those terms' least and greatest sums, over every value of the kernel's variables. Only a range whose bound is a constant or a variable is counted, its points at the bound's greatest value; a store inside a range of no trip is not counted, and a kernel already optimised is not checked.

Raises Invalid_argument if a store breaks a weak range's promise visibly, naming the store and the ranges, or if an optimisation asked for does not apply, with the reason Scheduler.apply_opt gives.