Module Tolk.Jit
JIT compilation and replay.
A JIT (tiny_jit) wraps a function and transparently captures its computation as a Tolk_uop.Ops.t.Linear on the second call, then replays that linear on all subsequent calls. Three phases:
- Warmup (cnt=0): execute eagerly.
- Capture (cnt=1): install a capturer in
Realize.capturingso every schedule the function creates is recorded instead of executed, then lower the combined record for replay: substitute each input buffer node with a slottedTolk_uop.Ops.t.Param, plan intermediate buffer memory once over the combined linear, compile every kernel, and, when the device has aDevice.Graphcapability, batch consecutive compatible calls into graph calls that replay as a single dispatch each (JIT_BATCH_SIZEcaps the first batch and the cap doubles per batch;JIT >= 2disables batching). - Exec (cnt>=2): validate the inputs against the capture and replay the compiled linear through Linear execution, passing the current input buffer nodes as
input_uopsand the per-call variable values asvar_vals.
Non-input buffers (weights, outputs, held buffers) are bound once at capture through the caller's buffer resolver; planned intermediates live in arena buffers owned by the capture's persistent binding.
Exceptions
Raised for JIT-specific errors: nested capture, empty capture, or an input mismatch on replay.
Graph batching
val batch_graphs : device:Device.t -> Tolk_uop.Uop.t -> Tolk_uop.Uop.tbatch_graphs ~device linear groups consecutive graph-compatible calls of a compiled Tolk_uop.Ops.t.Linear into Tolk_uop.Ops.t.Custom_function "graph" calls, each replayed as a single batched dispatch through the device's Device.Graph capability by Linear execution. A call is compatible when its body is a compiled Tolk_uop.Ops.t.Program (or a Tolk_uop.Ops.t.Copy, if the capability supports copies) and every buffer argument lives on device; incompatible calls break the batch and Tolk_uop.Ops.t.Slice calls are dropped (argument resolution derives offset views structurally). JIT_BATCH_SIZE caps the first batch and the cap doubles per emitted batch. The identity when JIT >= 2 or the device has no graph capability.
call applies this to its captured linear at lowering; it is exposed for callers that compile with Realize.pm_compile and replay through Linear execution directly.
Captured schedule
TinyJit
val captured : 'a tiny_jit -> 'a captured_jit optioncaptured t is t's captured schedule, or None before capture.
val create :
device:Device.t ->
to_program:(Tolk_uop.Uop.t -> Tolk_uop.Uop.t) ->
?fxn:(Tolk_uop.Uop.t array -> (string * int) list -> 'a) ->
?captured:'a captured_jit ->
?prune:bool ->
unit ->
'a tiny_jitcreate ~device ~to_program ?fxn ?captured ?prune () is a JIT wrapper.
to_program compiles a kernel Tolk_uop.Ops.t.Sink into an on-graph Tolk_uop.Ops.t.Program; the captured linear is compiled with it once, then replayed.
Provide either fxn (the function to JIT, taking the input buffer nodes and the variable values of the call) or captured (a pre-captured schedule); when captured is given, execution starts at the replay phase. prune is accepted for compatibility and currently ignored.
Raises Invalid_argument if neither fxn nor captured is provided.
val reset : 'a tiny_jit -> unitreset t returns t to the warmup phase, discarding any captured schedule.
Raises Invalid_argument if t was created without a function.
val call :
?wait:bool ->
?held_buffers:(unit -> Tolk_uop.Uop.t list) ->
'a tiny_jit ->
Tolk_uop.Uop.t array ->
(string * int) list ->
buffers:(Tolk_uop.Uop.t -> Device.Buffer.t option) ->
'acall ?wait ?held_buffers t input_uops var_vals ~buffers runs t with the input buffer nodes input_uops and variable values var_vals.
- Warmup (cnt=0): calls the wrapped function eagerly.
- Capture (cnt=1): calls the function under the capture handler, lowers the combined recorded schedule for replay, binds the non-input buffers it references through
buffers, and replays. - Exec (cnt>=2): validates inputs against the capture and replays with the current
input_uopsandvar_vals.
buffers maps buffer nodes to their concrete device buffers. At capture it binds the non-input buffers of the recorded schedule; at every replay it resolves the current input_uops, so each input node must have a backing buffer when call runs.
held_buffers, evaluated once when capture completes, lists the buffer nodes that outlive the jitted computation — external outputs, and any buffer assigned inside the jit but read outside it (for example a cache updated in place across calls). Held buffers keep their identity and their own allocation; all other intermediate buffers are folded into per-device arenas by the memory planner and their contents do not survive the call. Defaults to holding nothing.
Replay validates each input's element count, dtype, and device against the capture and raises Jit_error on mismatch. A fuller check would also compare each input's movement view and its set of bound symbolic variables, catching inputs that alias the same buffer through a different layout or binding; inputs here are whole buffer nodes, so those checks have nothing further to compare today.