Module Tolk.Ops
The intermediate representation: nodes, patterns and graph rewriting.
A node (t) is an operation (Op.t) applied to source nodes, with a payload (arg) and a data type derived from the three. Nodes form a directed acyclic graph, and one vocabulary serves every stage of compilation: tensor graphs, kernels, programs and command sequences.
Identity. Nodes are hash-consed: building a node equal to a live one returns that node, so two nodes are structurally equal iff they are physically equal (==). A node lives as long as something references it. Nodes can be built and read from any domain.
Construction. v builds any node; the constructors below build the common ones with the defaults and checks of each operation. Arithmetic promotes its operands to a common data type, and has their broadcast shape (Elementwise).
Shapes. A node's shape is a list of sint: integers, or integer nodes for sizes known only when the program runs. Comparing such sizes needs simplification, so shapes, movements and the functions that read them are Shape's.
Patterns. Upat describes nodes, Pattern_matcher pairs patterns with rules, and graph_rewrite rewrites a graph with them to a fixed point.
Errors. A broken precondition raises Invalid_argument.
type tThe type for nodes.
Axis types
Devices
type device = stringThe type for devices. Devices are named by the caller, such as "CPU" or "AMD:1"; only a name starting with "DISK" has a meaning here, a disk.
pp_device formats a device as its quoted name: 'CPU'.
is_disk_device d is true iff d is a disk: its name, up to its first :, is DISK in any case.
Symbolic integers
The type for symbolic integers: an integer, or an integer node whose value is known when the program runs. Functions that return one return Int whenever the value is known.
Arguments
type param_arg = {- #
slot : int;(*The parameter's position in its function, or
*)-1for a named variable. - #
dtype : Dtype.t;(*The element type.
*) - #
vmin_vmax : (Dtype.value * Dtype.value) option;(*The least and greatest value of a variable.
*) - #
name : string option;(*The name of a variable or a named buffer.
*) - #
addrspace : Dtype.addr_space; - #
device : device option;(*The placement of the storage.
*) - #
volatile : bool;(*Accesses must not be merged or reordered.
*) - #
bind_on_realize : bool;(*The storage is bound by whoever realizes the graph, not by a call.
*) - #
bound : Dtype.value option;(*The value a variable is bound to.
*) - #
phase : int;(*The bytes by which the storage's first element lies past a multiple of
*)align: from0toalign - 1, a multiple of the element's size or ofalign, whichever is less, which a copy that changes the element type must keep. Vector accesses start only where they are aligned to their width. - #
align : int;(*The power of two, from
*)1to16, the width of the widest vector access, modulo which the start of the storage is known (phase). No vector access is wider.
}The type for the arguments of Op.t.Param, Op.t.Buffer and Op.t.Alloc: storage, or a scalar variable. The number of elements is the node's first source (size_src).
val param_arg :
?vmin_vmax:(Dtype.value * Dtype.value) ->
?name:string ->
?addrspace:Dtype.addr_space ->
?device:device ->
?volatile:bool ->
?bind_on_realize:bool ->
?bound:Dtype.value ->
?phase:int ->
?align:int ->
slot:int ->
Dtype.t ->
param_argparam_arg ~slot dtype is the argument with these fields. addrspace defaults to Dtype.addr_space.Global; the flags default to false, phase to 0, align to 16 and the other fields to None.
Raises Invalid_argument if align is not a power of two from 1 to 16, or phase is not below align and a multiple of dtype's size or of align, whichever is less.
type estimates = {- #
ops : sint;(*Arithmetic operations.
*) - #
lds : sint;(*Bytes loaded and stored.
*) - #
mem : sint;(*Bytes of memory touched, each counted once.
*)
}The type for the cost estimates of a kernel.
pp_estimates formats Estimates(ops=0, lds=0, mem=0).
type split = {- #
iterations : sint;(*The loop's iterations,
*)0toiterations - 1. - #
lo : int;(*The slot of the variable that holds a block's first.
*) - #
hi : int;(*The slot of the variable that holds the one after its last.
*)
}The type for the loop a host program's launch splits into blocks that the host's cores run at once. Its program runs the iterations from the value of its variable lo up to that of hi.
type kernel_info = {- #
name : string;(*The kernel's name.
*) - #
applied_opts : Opt.t list;(*The optimisations applied, in order.
*) - #
opts_to_apply : Opt.t list option;(*The optimisations to apply, or
*)Noneto choose them. - #
estimates : estimates option;(*The kernel's cost.
*) - #
beam : int;(*The beam width to search optimisations with, or
*)0. - #
optimized : bool;(*Whether the kernel went through the optimiser, which then leaves it as it is.
*) - #
kernel : bool;(*Whether the runtime launches it;
*)falsefor a body another kernel calls, as a compiled program. - #
split : split option;(*The loop its launch splits, if any.
*)
}The type for the arguments of a kernel's Op.t.Sink.
val kernel_info :
?name:string ->
?applied_opts:Opt.t list ->
?opts_to_apply:Opt.t list ->
?estimates:estimates ->
?beam:int ->
?optimized:bool ->
?kernel:bool ->
?split:split ->
unit ->
kernel_infokernel_info () is the argument with these fields. name defaults to "test", the lists to [] and None, beam to 0, optimized to false, kernel to true and split to None.
val function_name : kernel_info -> stringfunction_name k is k.name as an identifier (Helpers.to_function_name).
type program_info = {- #
global_size : sint list;(*The number of workgroups on each axis.
*) - #
local_size : sint list;(*The threads of a workgroup on each axis.
*) - #
params : t list;(*The parameters, buffers and scalars, one per slot, by slot.
*) - #
globals : int list;(*The slots of the buffers.
*) - #
outs : int list;(*The slots of the buffers written.
*) - #
ins : int list;(*The slots of the buffers read.
*) - #
target : Helpers.Target.t;(*The target compiled for.
*)
}The type for the arguments of Op.t.Program.
type keep = - #
| Removable(*Drop the axes its value does not vary along, and inline it back where that costs little.
*) - #
| Broadcast(*As
*)Removable, for a value read where it is broadcast: it is not inlined back where computing it runs a transcendental function, which its readers would compute again for each element of the ranges it does not vary along. - #
| Whole(*Nothing: a value the user materialises is stored as it is.
*) - #
| Argument(*As
*)Whole, for a value a custom kernel reads: a store through the same view of storage writes that storage, not this buffer.
What later passes may do to a buffer an Op.t.Stage makes.
type bufferize_opts = {- #
device : device option;(*Where the new buffer lives.
*) - #
addrspace : Dtype.addr_space;(*Its address space.
*) - #
keep : keep;(*What later passes may do to it.
*)
}The type for the arguments of an Op.t.Stage that makes a buffer.
type custom_function = {- #
name : string;(*The symbol of the function, or with
*)fmtthe code that the call writes. - #
dtype : Dtype.t; - #
fmt : bool;(*
*)nameis code in the target's language: a format string of the call's arguments, where{}is the next argument,{i}theith from 0, and{{and}}are braces, as Python'sstr.formatreads positional fields; any other field is refused.
}The type for the arguments of Op.t.Custom_function.
type call_info = {- #
name : string option;(*The name of the function called.
*) - #
precompile : bool;(*Compile the body on its own.
*) - #
inline : bool;(*Write the call of code in the target's language into its reader, as an expression.
*) - #
write : (int * int) list;(*The disjoint writes (
*)after_disjoint) this kernel call is a kernel of, each as the write's number in its schedule and the position, among the call's arguments, of the state it writes. A schedule sets it and reads it alone: a program's key and the call's printed form (pp_arg) leave it out.
}The type for the arguments of Op.t.Call.
type wmma_axis = (Axis_type.t * int list) * intThe type for an axis a tensor core folds: its range's role and identity, and its size.
type wmma = {- #
dims : int * int * int;(*The matrix dimensions N, M and K.
*) - #
dtype_in : Dtype.t;(*The type of the multiplied operands.
*) - #
threads : int;(*The threads that cooperate on one product.
*) - #
upcast_axes : (wmma_axis list * wmma_axis list * wmma_axis list) option;(*For each operand, the axes still to fold into it, or
*)Noneonce folded.
}The type for the arguments of Op.t.Wmma.
type arg = - #
| No_arg - #
| Const of Dtype.const - #
| Dtype of Dtype.t - #
| Param of param_arg - #
| Range of {- #
axis_type : Axis_type.t; - #
axis_id : int list;
} - #
- #
| Reduce of {}(*
*)Op.t.Reduce: the operation, and how many leading axes it reduces (0for a kernel's reduction over range sources). - #
| Device of device - #
| String of string - #
| Bytes of string - #
| Code of {} - #
| Bufferize of bufferize_opts - #
| Kernel of kernel_info - #
| Program of program_info - #
| Custom_function of custom_function - #
| Call of call_info - #
| Wmma of wmma - #
| Disjoint
The type for node arguments. Each operation carries one shape of argument, given by v's table; the others carry No_arg.
equal_arg a0 a1 is structural equality, with constants compared by Dtype.equal_const and nodes by ==.
pp_arg formats an argument as the literal that denotes it: None, 42, ConstFloat(1.5), True, Invalid, dtypes.f32, (AxisType.WEAK, 0), (Ops.ADD, 0), 'CPU', b'ab\x00'.
The records print as calls:
Param: the slot, the type and the size, then the fields that differ from their defaults, by name:ParamArg(-1, dtypes.weakint, vmin_vmax=(1, 10), name='i', ...);Kernel:KernelInfo(name='test', applied_opts=(), opts_to_apply=None, estimates=None, beam=0);Program:ProgramInfo(global_size=(1, 1, 1), local_size=(1, 1, 1), params=(), ...);Bufferize:BufferizeOpts(device='CPU', addrspace=AddrSpace.GLOBAL, removable=True, broadcast=False),removableFalseforWholeandArgumentonly andbroadcastTrueforBroadcastonly;Custom_function:CustomFunction(name='f', dtype=dtypes.i32, fmt=False);Call:CallInfo(None, 'f', False, False, False, None).
Raises Invalid_argument on an integer that Bigint.to_string refuses.
Nodes
v ~src ~arg op is the node op of src (default []) with argument arg (default No_arg).
Its data type is derived from op, src and arg (dtype_of). The operations whose type comes from their argument need its shape: Op.t.Const a Const, Op.t.Cast and Op.t.Bitcast a Dtype, Op.t.Param, Op.t.Buffer and Op.t.Alloc a Param, and Op.t.Ins a Code.
An integer literal among src holds its value at the integer type the operation fixes for it, wrapped to that type's width, so every rule and fold reads the machine value of a literal without knowing to wrap it: a comparison at its operands' Dtype.least_upper, another binary or ternary operation at its own type, a cast at its target. On a Dtype.t.Uint8, 300 is 44 and -1 is 255. The node stays a bare weak literal. A shift's count and a power's exponent (is_number) are numbers and keep their value. A float type leaves a literal alone.
Raises Invalid_argument if no type can be derived, as for a Op.t.Where whose condition is not boolean.
An exception raised asynchronously inside v, as Sys.Break on Ctrl-C or a test runner's timeout is, passes through it: later calls on any domain find each node built before.
nth u i is the ith source of u, from 0.
Raises Invalid_argument if u has no ith source.
replace u is u with the given fields replaced; u itself if none changes.
equal u0 u1 is u0 == u1: structural equality, since nodes are hash-consed.
compare is a total order compatible with equal. It depends on the order in which nodes were built; see compare_structure for one that does not.
pp formats a node and its sources, one per line and indented, as the calls that build them:
UOp(Ops.ADD, arg=None, src=( x0:=UOp(Ops.CONST, arg=42, src=()), x0,))
A node reached more than once is named xN:= where first printed and xN afterwards. The argument prints as pp_arg, and raises as it does.
structure_ranks u numbers u and the nodes it reaches, call bodies included, in the order of compare_structure, and is the function that gives each its number: of two of them n0 and n1, n0's number is less than n1's exactly when compare_structure n0 n1 < 0, and the two are equal exactly when it is 0. Numbering a graph costs about its nodes times their logarithm, where compare_structure walks the graphs it compares.
The function raises Not_found on a node u does not reach. Raises as pp_arg does where it prints an argument.
key u is a digest of u's operation, type, argument and sources, recursively: equal for equal graphs, whenever and wherever they are built, and different for NaN constants of different bits.
Raises as pp_arg does on an argument of u.
key_with ~arg u is the key of the graph u would be with each node n for which arg n is Some a taking the argument a, computed without building that graph. Each such a must be an argument n's operation takes and keep n's data type, as a storage parameter's slot does: the key is not that of a graph whose types it would change. arg is called once on each node of u.
Raises as key does.
module Tbl : Stdlib.Hashtbl.S with type key = tHash tables keyed by nodes.
Data types
dtype_of op src arg is the data type of the node op of src with argument arg. Unary operations and movements keep their source's type, comparisons are Dtype.t.Bool, transcendentals widen to a float, broadcastable operations promote (promo_dtype), and effects are Dtype.t.Void.
Raises Invalid_argument if there is none.
promo_dtype us is the common type of us: theirs if they share it, their least upper bound (Dtype.least_upper) otherwise.
val identity_element : Op.t -> Dtype.t -> Dtype.constidentity_element op dt is the value e of type dt with op e x = x for every x of type Dtype.strong dt (Dtype.strong), the width an accumulator of dt takes: 0 for Op.t.Add, 1 for Op.t.Mul, and that type's least value for Op.t.Max.
Raises Invalid_argument for any other op.
Graphs
A walk of a graph, by toposort, topovisit, graph_rewrite or a function built on them, that an exception ends, one raised asynchronously as Sys.Break is included, leaves later walks on any domain their results.
type calls = The type for how a walk of a graph treats a call (Op.t.Call), whose first source is its body.
toposort ~gate ~calls u is u and the nodes it reaches, each after its sources, in the order a depth-first walk of the sources, in order, finishes them. The walk enters only the nodes gate accepts (default all), and treats calls as calls says.
topovisit u f cache is f u, after f has been applied to each node u reaches, each after its sources, and stored in cache. Nodes already in cache are not visited again.
backward_slice ~calls u is the nodes u reaches, without u, in toposort ~calls's order.
backward_slice_with_self ~calls u is u followed by backward_slice ~calls u.
op_in_backward_slice_with_self ~calls u ops is true iff u or a node of backward_slice ~calls u has an operation in ops.
reaches ~gate ~calls u x is true iff x is u or a node u reaches, treating calls as calls says, through nodes other than x that gate accepts (default all).
split_uop u op is the operands of the tree of op nodes rooted at u, left to right: [u] if u's operation is not op.
Shapes
broadcast_shape shapes is the shape shapes broadcast to: aligned to the right, each axis is the size of its sources that is not 1, or 1 if all are.
Raises Invalid_argument if an axis has two different sizes other than 1.
to_max_shape s is s with each symbolic size replaced by its greatest value.
sint_to_uop ~dtype s is s as a node of type dtype, by default Dtype.t.Weak_int.
Ranges
range_start op is the index of the first source of an op node that is a range it ends: 1 for Op.t.Stage, Op.t.Reduce and Op.t.End, 0 for Op.t.Linear, and None otherwise: a call ends none of the ranges its arguments run inside.
ended_ranges u is the ranges that u closes: those among its range sources, those a Op.t.Barrier or an Op.t.After's effects close, the loop a Op.t.Backedge repeats.
ranges u is the ranges u runs inside: u itself if it is a range, then those its sources run inside, minus those it closes.
axis_id r is the identity of the range r.
Raises Invalid_argument if r is not a range.
val axis_type : t -> Axis_type.taxis_type r is the role of the range r.
Raises Invalid_argument if r is not a range.
bound_loads r is true iff the range r has a bound that, its ordering after effects (Op.t.After) aside, reads memory, as j < x[i]: how many trips the loop runs is what memory holds.
Raises Invalid_argument if r is not a range.
known_at_launch r is true iff the range r has a bound that, its ordering after effects (Op.t.After) aside, reads no range and no memory (bound_loads), as j < n for a variable n and unlike j < x[i] or j < i + 1: a launch computes it from the run's variables alone, before the kernel runs, as it does a count of workgroups or of blocks.
Raises Invalid_argument if r is not a range.
compare_range_arg r0 r1 orders the ranges r0 and r1 by argument: axis type (Axis_type.compare), then identity, lexicographically, a prefix before its extensions.
Raises Invalid_argument if r0 or r1 is not a range.
range_str ~color r is r's identity written with underscores, a negative part written m and its magnitude: 0_1, m1. It is in r's axis colour if color (default false).
multirange_str ~color ~pad rs is the range_str of each of rs, sorted by argument, separated by commas and padded with spaces to pad printed columns.
Evaluating
val vmin : t -> Dtype.valuevmin u is a lower bound of every value u can take.
val vmax : t -> Dtype.valuevmax u is an upper bound of every value u can take. Bounds follow interval arithmetic through the integer operations, the less-than and equality comparisons, selections, casts and loads from constant tables, a gated load's alternative included; elsewhere they are the bounds of u's type. A committed integer wraps at its width, and so does a weak integer operand of an operation on one (operand_bounds): an interval that leaves its type is one value wrapped, or the type's bounds. A table holding a NaN has its type's bounds. A comparison of floats is never bounded to true: a NaN compares false.
val operand_bounds : t -> t -> Dtype.value * Dtype.valueoperand_bounds u s is the bounds of u's source s as u reads it: an operation on a committed integer commits a weak integer operand to its type, which wraps it, so bounds that leave the type are one value wrapped, or the type's; an operation on a committed float commits a weak float operand to its type, which rounds it, and its bounds round with it. A shift's count is a number of bits and keeps its own bounds.
fits_no_int u is true iff u's bounds are known and neither Dtype.t.Int64 nor Dtype.t.Uint64 holds them: no machine integer can carry every value of u. Bounds at a weak integer's own range, as of a node nothing bounds, are not known.
val exact : Dtype.t -> Dtype.value list -> boolexact dt vs is true iff dt is not a committed integer type, or each of vs is one of its values. Arithmetic on a committed integer type wraps at its width, so it computes what unbounded integers do only where every value it takes, vs, is exact.
val exec_alu :
?truncate_output:bool ->
Op.t ->
Dtype.t ->
Dtype.const list ->
Dtype.constexec_alu ~truncate_output op dt args computes the arithmetic operation op on the scalars args, one lane of a vector operation, exactly, then truncates the result to dt (Dtype.truncate) if truncate_output (default true). Integers are unbounded before truncation, and an integer to a negative integer power truncates toward 0: it is 0 but for 1 and -1. A shift's count and a power's exponent are numbers, read at no width: truncated to a committed integer type a left shift by a count of its width or more is 0, a right shift is the floor of its division by 2count, whatever the count, and a power is computed in the type's bits, whatever the exponent. Out-of-domain values follow IEEE: log2 0. is -inf, sqrt of a negative is NaN, 1. /. 0. is an infinity of the operand's sign, sin of an infinity is NaN, and an overflowing exp2 or pow is the infinity IEEE gives. A float log2, exp2 and pow are correctly rounded to a double, ties to even, and sin is but where rounding it needs more than 192 bits past its argument's reduction; each is the same on every host. The NaN of an operation none of whose operands is NaN is Dtype.nan, whatever the host gives. Op.t.Fdiv is IEEE division: a zero divisor gives an infinity of the quotient's sign, and 0. /. 0. is NaN. Op.t.Trunc of a float is IEEE's, a float of its operand's sign: trunc (-0.5) is -0.. Op.t.Max is y if x < y and x otherwise, as compiled code computes it, so a NaN wins only as the first operand. A binary operation on `Invalid is `Invalid. Op.t.Where is the branch it picks, `Invalid included; its condition must not be `Invalid. Op.t.Where and Op.t.Neg convert nothing: truncated, a float NaN they give is Dtype.const of dt, which keeps the operand's sign, flipped by Op.t.Neg, and its payload, a signalling NaN staying one, in a float of 16 bits or more.
Raises Invalid_argument if op is not an arithmetic operation or the arguments do not fit it, as for a shift by a negative count, which has no value, a left shift of a non-zero integer by more than max_exact_shift bits that nothing truncates, or a power that nothing truncates and exact_power leaves out: at a weak dt, or without truncate_output.
val exact_power : Dtype.value -> Dtype.value -> boolexact_power b e is true iff b ** e, for integers b and e, can be computed exactly in exec_alu when nothing truncates: a base of 0, 1 or -1, a negative e, or a power of at most max_exact_shift bits.
val max_exact_shift : intmax_exact_shift is the most bits a left shift that nothing truncates computes exactly in exec_alu. Past every committed integer's width, it bounds what one constant in a program may make the compiler allocate.
Construction
val const : ?dtype:Dtype.t -> Dtype.const -> tconst ~dtype c is the constant c of type dtype. Without dtype, or for `Invalid, it is the weak literal of c's kind (Dtype.of_const). A constant of any other type is a Op.t.Cast of the literal, whose value is c converted to dtype (Dtype.const).
val value : t -> Dtype.constvalue c is the value of the constant c, read through a cast.
Raises Invalid_argument if c is neither a constant nor a cast of one.
is_literal u is true iff u is a constant or a cast of one, whose value value reads. A constant of a type other than the weak ones is a cast of one: int ~dtype:Int32 16 is a cast of the weak 16.
ccast u dt is u as a dt constant if u is a constant, and cast u dt otherwise.
val cconst : Dtype.t -> Dtype.const -> tcconst dt c is the cast to dt of the literal c, even where const would fold the cast away.
val sink : ?kernel:kernel_info -> t list -> tsink ~kernel us is the Op.t.Sink of us, the root of the kernel kernel if given.
group us is the empty Op.t.Stack ordered after us (after): the effects us merged without a value. When us is one node, it is that node.
is_group u is true if and only if u is an Op.t.After of the empty Op.t.Stack, as group makes of several effects.
broadcast u n is the Op.t.Stack of n copies of u, or u if n is 1.
index u idxs is the Op.t.Index of u by idxs; the ith source of a stack if u is a stack and idxs the constant i.
store p x is the Op.t.Store of x through p, guarded by gate. A tensor graph that stores through a broadcast is refused when it is prepared (Prepare.prepare_rangeify).
end_ u rs is the Op.t.End of the ranges rs around u, or u if rs is empty.
In a kernel, backedge u ~loop ~cond runs u, then repeats loop while cond holds.
Around a call of a schedule, or an Op.t.Linear of calls, it tests cond before each trip: loop is a Axis_type.t.Loop range, or the 0 that a range of one trip simplifies to, cond is storage of one boolean, and the calls run in order once per trip of loop while cond holds, so they may run no trip.
after u deps is u ordered after deps, or u if deps is empty.
Outside a kernel, an Op.t.After of storage names a state of it. One whose deps list calls, loops of calls or stores is the state those effects leave the storage in, after the state u names: they land in the order listed, each reading the states the graph names, older states as they were, so a later store wins where stores overlap. A call loading through an argument it stores through loads what the effects listed before it left there, with each store the call runs after. An element two Op.t.Afters of one state both write, which Schedule.create_schedule admits for views of the state, holds an unspecified value. A state over the whole of its storage holds its own effects, those of the states under it, and each store those effects run after, by reading it directly or through other storage, or by listing it. A store of an Op.t.After over a view of one of those states that they do not run after is no part of it. A store listed over a later state of its storage than its destination names stores into that later state. One listing only stores into other storage leaves u's state as it is, read once they have landed. One whose deps are all Op.t.Afters is a join (is_join): it names no state of its own, holds the value of the state u names, and is read once the states deps name are reached.
after_disjoint u stores is after u stores with a promise from its maker: stores, the stores of one write of u's storage, write pairwise disjoint elements, none reads an element another writes, and every kernel of them may run at once. A schedule then runs the kernels of this write as one launch, where it can, without testing which elements each writes (Schedule.create_linear_with_vars).
Stores that break the promise give undefined values: a after of them, unmarked, runs them in the order listed.
is_disjoint u is true iff u is the Op.t.After of an after_disjoint.
is_join u is true iff u is an Op.t.After whose dependencies are all Op.t.Afters: a join, which orders a read of its first source after them and writes nothing.
without_after u is u without its Op.t.Afters.
barrier u rest is the Op.t.Barrier of u and rest.
ins u i is the instruction i with u's sources and type, unless given.
val range : axis_type:Axis_type.t -> ?dtype:Dtype.t -> sint -> int list -> trange ~axis_type ~dtype end_ axis_id is the loop variable counting from 0 to end_, excluded, of type dtype (default Dtype.t.Weak_int) and role axis_type, which has no default: a Axis_type.t.Weak range promises that its trips are independent, and a Axis_type.t.Loop runs them in turn. A loop ordered after effects has its bound ordered after them (after).
loop axis is the unbounded loop of identity [axis], which a backedge (Op.t.Backedge) repeats. It runs its trips in turn, whatever its type, and is no optimisation's axis.
val wmma :
?upcast_axes:(wmma_axis list * wmma_axis list * wmma_axis list) ->
t ->
t ->
acc:t ->
dims:(int * int * int) ->
threads:int ->
twmma a b ~acc ~dims ~threads is the matrix multiply-accumulate of a and b into acc.
reduce u op ranges is the kernel reduction of u with op over ranges.
val bufferize : ?opts:bufferize_opts -> t -> t list -> tbufferize ~opts u ranges is the Op.t.Stage of u over ranges, into a buffer placed by opts if given.
Placement
device u is where u lives: its argument's device for storage and copies, its sources' first otherwise.
copy_to_device u d is the copy of u to d.
Raises Invalid_argument if d is a disk or u's type is weak.
size_src n is the first source of a Op.t.Param, Op.t.Buffer or Op.t.Alloc of n elements: the weak integer constant n, or the empty Op.t.Stack for a scalar (None). The size is never symbolic: storage of a symbolic size is a larger one shrunk.
Movement
storage_base u is the storage u targets: base u without bitcasts and Op.t.Afters.
type movement = - #
| Reshape of sint list(*The new shape.
*) - #
| Expand of sint list(*The axes added in front.
*) - #
| Pad of (sint * sint) list(*Where each axis starts in the result, and the result's size.
*) - #
| Shrink of (sint * sint) list(*Where each axis starts in the source, and the result's size.
*) - #
| Permute of int list(*The source axis of each result axis.
*)
The type for the arguments of movements.
Storage
Sizes of storage are ints: a shape of more than max_int elements raises Invalid_argument.
val addrspace : t -> Dtype.addr_space optionaddrspace u is the address space u lives in. A node of type Void holds no memory and lives in Alu.
whole p is the parameter of the storage the Op.t.Param p views, for a view: a parameter of its own that a run of a buffer takes where a call stores into it or a loop reads it, so that the kernel's program does not depend on where the run starts. Any other node is its own whole.
view_state p is the state of storage the view p names, its second source: its storage's parameter, or the Op.t.After whose elements a read of the run takes. Any other node is its own view_state.
is_virtual u is true iff u cannot back a buffer as it is: it has no device, or its type is weak.
has_buffer_identity ~after_ok u is true iff u names storage, through reshapes and, if after_ok, an Op.t.After.
val unique_num : unit -> intunique_num () is a slot number no other call returns.
val take_unique : int -> unittake_unique n makes every number unique_num returns afterwards greater than n, up to max_int, past which no number is left.
Raises Invalid_argument if n is max_int.
new_buffer ~slot ~phase d size dt is a Op.t.Buffer of size elements of dt on d, in slot (default unique_num), whose first element lies phase bytes past a 16-byte boundary (default 0; see param_arg). A given slot is taken (take_unique), so unique_num returns a greater number afterwards. slot must not be a number unique_num returned for another buffer: buffers equal in slot, size, type, device and phase are one node and one storage.
Raises Invalid_argument if dt is weak or slot is max_int.
set p x stores x through p, ends ends, and is p's storage after the store.
Variables
val variable : ?dtype:Dtype.t -> string -> Dtype.value -> Dtype.value -> tvariable ~dtype name lo hi is the scalar variable name of type dtype (default Dtype.t.Weak_int) ranging over lo to hi.
expr u is the name of the parameter or buffer u.
Raises Invalid_argument if it has none.
Divisibility
val pop_const : ?op:Op.t -> t -> t * Dtype.constpop_const ~op u is (x, c) for u = op x c with c a constant, and (u, identity_element op) otherwise. op defaults to Op.t.Add.
Calls
src_without_body u is a call's arguments, and any other node's sources.
opaque_call_bodies is the operations a call body can have: Op.t.Sink, Op.t.Program, Op.t.Linear, Op.t.Store and Op.t.Custom_function.
is_inline_call u is true iff u calls a plain sink that is not compiled on its own.
has_unbound_outputs c is true iff the call c still has outputs that no buffer is bound to.
custom_function ~dtype ~shape ~fmt name src is the external function name, or with fmt (default false) the code name in the target's language (custom_function), which returns dtype (default Dtype.t.Void). Its value has no shape when dtype is Dtype.t.Void, and is of shape (default []) otherwise, which its first source states.
call ~inline body args calls body on args; a call of code in the target's language is written into its reader if inline (default false). The call has the type and the shape of body. Given one state of storage at an argument body writes (Shape.written_params) and at another, it reads that state at the other as it was before the call, as memmove reads its source. A call compiled on its own takes storage, a run of its elements from the first, and a row a loop moves along as they are, and reads a copy of most other arguments, made once before a loop around the call; scheduling refuses one it would copy where the call writes it, and a constant (Indexing.run_rangeify). In a loop, an argument the call only reads that reads a state the loop writes, or a later one, is computed as each trip starts, each read in it taking its own state: a state older than the one the loop is listed over as it was. A value that moves with the loop and is no row of storage, or that reads, at a state the loop writes or a later one, a join over other writes of that storage, is refused.
Raises Invalid_argument if body cannot be called (opaque_call_bodies) or a range leaks out of it.
Elementwise
module type Elementwise = sig ... endElementwise operations.
include Elementwise with type t := t
Arithmetic
add, mul, lt, eq, maximum, bitwise_and, bitwise_or, bitwise_xor, shl and shr are Op.t.Add, Op.t.Mul, Op.t.Cmplt, Op.t.Cmpeq, Op.t.Max, Op.t.And, Op.t.Or, Op.t.Xor, Op.t.Shl and Op.t.Shr of their promoted operands. reciprocal, trunc, sqrt, exp2 and log2 are Op.t.Recip, Op.t.Trunc, Op.t.Sqrt, Op.t.Exp2 and Op.t.Log2 of their operand. The others are built from these.
div ~rounding x y divides integers by Op.t.Truncdiv or Op.t.Div when rounding; otherwise it multiplies x, as a float, by y's reciprocal, then rounds as asked.
minimum x y is neg (maximum (neg x) (neg y)) for floats; for integers, the same through an order-reversing xor.
bitcast x dt reinterprets x's bits as dt, or is x if it is of type dt.
Raises Invalid_argument if either type is weak.
commit_dtype x is the type x is stored as: a weak integer commits to the narrowest type holding its bounds (Dtype.commit_int), a weak float to its default (Dtype.strong).
element_size x is the size in bytes of one of x's elements.
Raises Invalid_argument if x's type is weak.
contiguous x is x materialised into its own buffer: x itself if it is weak, already staged, placed nowhere, or storage.
uprod x ys is the product of x :: ys, or their conjunction if x is boolean.
Patterns
module Pattern_matcher : sig ... endPattern matchers.
Rewriting
exception Bottom_up_gateRaised by a rule before the sources (Before_sources, Around_sources's before) under Fixed_point to keep the node it last produced and leave its sources unvisited. Raised by a rule after the sources, or under Once, it escapes graph_rewrite.
type pass = - #
| Fixed_point(*A rule before the sources rewrites a node until none applies, and the sources of the result are then rewritten. A node that a rule after the sources rewrites, or that is rebuilt on rewritten sources, is rewritten again in turn, to a fixed point.
*) - #
| Once(*A rule rewrites a node at most once, and its result is never rewritten again: a node that a rule before the sources rewrites is replaced and its sources are left as they are.
*)
The type for how often a rewrite applies its rules to a node and its results.
type 'ctx rules = - #
| After_sources of ('ctx, t) Pattern_matcher.t(*Rewrites each node once its sources are rewritten.
*) - #
| Before_sources of ('ctx, t) Pattern_matcher.t(*Rewrites each node before its sources.
*) - #
| Around_sources of {- #
before : ('ctx, t) Pattern_matcher.t; - #
after : ('ctx, t) Pattern_matcher.t;
}(*Rewrites each node with
*)beforebefore its sources and withafteronce they are rewritten. - #
The type for the rules of a rewrite and when they meet a node.
graph_rewrite ~calls ~pass ~ctx u rules is u rewritten by rules as pass says: each node is rebuilt on its rewritten sources and rewritten by the first rule that applies, and shared nodes stay shared. A call's body is rewritten with its arguments under Enter, and left alone under Skip.
Raises Invalid_argument if a Fixed_point rewrite does not terminate: a rule before the sources cycles, the work list exceeds Setting.rewrite_stack_limit, or a node's rewrite depends on itself, and Bottom_up_gate as that exception's description says.
val substitute :
?extra_pm:(t Tbl.t, t) Pattern_matcher.t ->
calls:calls ->
pass:pass ->
t ->
(t * t) list ->
tsubstitute ~calls ~pass u subs is u with each node of subs replaced by its pair, top-first, rewriting with extra_pm as well, which reads the substitution, and treating calls and results as graph_rewrite does. Nodes paired with themselves are ignored.
assuming u subs is u with each node of subs replaced by its pair, as substitute does outside calls, for a fact that holds only where u is read: it leaves each range it does not replace whole, bound included, since a loop is one loop wherever it is read.
val pm_substitute : (t Tbl.t, t) Pattern_matcher.tpm_substitute replaces each node its context maps by its image.
val pm_drop_after : (unit, t) Pattern_matcher.tpm_drop_after replaces each Op.t.After with its first source.
resolve_returned_after r effects is the value stored into the output r by the one store of effects into r's storage: r after that store if r is a parameter of the enclosing function.