Ir.Tnodemodule Lazy = Utils.Lazymodule Nd = Ndarraytype memory_mode = | Effectively_constantA constant, or a subset of Virtual.
| VirtualThe tensor node's computations are inlined on a per-scalar basis. The node has no buffer in any context; its defining computation is tracked (in Low_level.optimize_ctx), so it remains observable: its value can be recomputed on demand, by later routines and by host reads (Context.to_host, gh-ocannl-777). Observability is inductive, not intrinsic: recomputation requires the nodes the tracked computation reads to be observable themselves, so a Virtual node that depends -- directly or transitively through other Virtual nodes, with no materialized node between -- on a Local node inherits its unobservability (Context.observability reports the path).
| Never_virtualAn as-yet-unresolved request; resolves to Local or On_device.
| LocalRoutine-scoped scratch: the tensor node exists only for the duration of a single call to a compiled function, stored to whatever degree the optimizer decides on (e.g. a stack array), and is not persisted across calls. It is not materialized (owns no context buffer) and is not available for merging across devices. Unlike Virtual -- which stays observable via recomputation -- Local is unobservable, and the sole source of unobservability: its computation is not tracked, and compiling a later routine that reads it raises a User_error directing to mark it as materialized before its first use. This mode is only ever assigned by the compiler (never requested), reserving the freedom to optimize placement of nodes whose lifetime is confined to one routine.
| On_deviceThe tensor node is stored on the devices that compute with it and persisted across function calls. It is available for merging across devices (for devices that support merging / P2P). CPU-side access (printing, persistence, inspection) is on-demand via context-mediated device-to-host transfers; no host copy is stored on the node.
*)val sexp_of_memory_mode : memory_mode -> Sexplib0.Sexp.tval equal_memory_mode : memory_mode -> memory_mode -> Base.booltype provenance = | Visit_capPer-cell visits above virtualize_max_visits, or an uncovered read. One of the three heuristic caps of Low_level.decide_placements: policy priors, which the decision-vector search may flip back to inlining, unlike a legality or observability verdict.
| Inline_reduction_capReduction extent above virtualize_max_inline_reduction.
| Inline_fanin_capTransitive fan-in above virtualize_max_inline_fanin.
| Read_before_writeAn uncovered read within the routine: the node is an input, so it owns a device buffer whose prior contents are preserved. Minted from two sites -- the lenient verdict of decide_placements and the strict re-classification in reconcile_traced_store.
| Scope_localA scope local the inliner minted, committed Virtual by cleanup.
| Surviving_readA read that survived inlining, so its target must be materialized. Cleanup is the commitment point, not a re-assertion: a node read here but written only under a virtualized setter is decided right now.
*)| Site of Base.stringA decision no other code reads back, explained by its own tag. Keeping these out of the constructor list is what stops this type from growing a case per backend query site.
*)| Refined of provenance * provenanceA decision that defaulted an earlier one into a concrete placement (Placements.default_to_most_local): the prior decision, then the one that resolved it. Renders as "39:inline-reduction-cap -> 432:is-local-materialized-query" -- what the retired 1000 * prior + own arithmetic encoded as the undecodable 39432.
Why a memory mode was requested or decided (gh-ocannl-609).
Two kinds of tag, and the split is a layering decision, not a convenience. A decision that only ever gets recorded is a Site carrying its own explanation: the ~60 of those are minted across nine modules, from the C renderer's storage queries to the scheduler's tile placements, and giving each a constructor would make this type -- which sits at the bottom of the dependency graph, since Placements is here -- enumerate the vocabulary of every module above it. A tag that some other code reads back gets a constructor instead, so the reading is an exhaustive match rather than a string comparison that can silently stop matching.
A Site's string is spelled "<code>:<kebab-case-reason>", and so is every constructor's rendering: the numeric code is the integer this used to be, kept so that issues, comments and goldens citing e.g. Non_virtual 13 or "provenance 39" still resolve. Codes are not unique -- two sites that were the same integer stay the same integer and are told apart by their reasons. Those collisions are pinned by test/operations/provenance_tag_inventory, which derives every tag from this type and its minting sites and refuses a new collision: a new tag takes a number that inventory's golden does not list.
val sexp_of_provenance : provenance -> Sexplib0.Sexp.tval equal_provenance : provenance -> provenance -> Base.boolval provenance_to_string : provenance -> Base.stringval leading_provenance : provenance -> provenanceThe decision the later ones refined: the leftmost tag of a Refined chain, which Placements.default_to_most_local builds left-nested.
val sexp_of_delayed_prec : delayed_prec -> Sexplib0.Sexp.tmodule Prec_cell : sig ... endA node's delayed precision, sealed (gh-ocannl-1624) together with the storage precision it settles into: read with get, written only by the guarded update_prec / update_infer_prec -- which update_prec and update_infer_prec wrap -- each write that changes the value reported to prec_write_hook. storage is the node's storage_prec, forced from the value; once it is forced, update_prec only validates and update_infer_prec refuses. The cell owns that lazy, so no caller can hand the guard a different one.
type bounds_state = | Bounds_unknownNo proposal yet: readers fall back to the precision's machine range.
*)| Bounds_proposed of Interval.tJoin of all proposals so far.
*)| Bounds_settled of Interval.tConsumed by a guard fold; subsequent proposals must fit or raise.
*)Per-tensor scalar value bounds: the interprocedural layer of the interval analysis (docs/proposals/interval-analysis-scalar-t.md), the third instance of the delayed_prec propose/settle lifecycle. Writers propose bounds (joined, like Inferred promotion); a reader that discharges a guard against the candidate settles it (like forcing the prec lazy); a post-settlement proposal that does not fit the settled interval is an error (like update_prec on a settled precision) -- otherwise already-generated code that folded a bounds guard away would become unsound.
Phase B v1 execution anchoring (binding constraint 3): every compiled device write of a node proposes Interval.top at lowering time (see Low_level.optimize_proc), so a candidate can only be narrower than top for host-initialized, never-device-written tensors; guard folds therefore never depend on device-computed values, sidestepping the runs-never/runs-later/runs-repeatedly hazards.
val sexp_of_bounds_state : bounds_state -> Sexplib0.Sexp.tNamespaces must be legal C-family identifiers so they can prefix generated-code identifiers and debug names verbatim (rendered as ns__n42); see docs/proposals gh-ocannl-372.
type t = {storage_prec : Ops.prec Lazy.t;dims : Base.int Base.array Lazy.t;padding : (Ops.axis_padding Base.array * Base.float) Base.option Lazy.t;If the tensor node is pre-padded, this is the pair of (left padding, right padding) per axis and the padding/neutral value. Both the margins and the neutral value are part of the node's identity: they commit when this lazy is forced (the node's first compilation), the margins permanently hold the neutral value, and later conflicting demands — more padding, or margin-touching operations expecting a different neutral — are rejected at shape-inference time.
*)size_in_bytes : Base.int Lazy.t;id : Base.int;namespace : Base.string;The namespace qualifying id, stamped at creation from the ambient current_namespace unless created with an explicit ?namespace (persistence loads, schedule-internal nodes). Grads share their value node's session namespace. Elided from sexps and renderings when it is default_namespace.
uid : Base.int;Process-unique identity, from a counter that no reinitialization ever resets -- unlike id, which restarts at 0 on Tensor.unsafe_reinitialize for deterministic printing. All comparison/hashing (hence every tnode-keyed map, set and cache in the process) uses uid, so a stale entry surviving a reinitialization can never alias a fresh tnode that reuses its id. Excluded from sexps to keep debug output reinitialization-deterministic.
label : Base.string Base.list;Display information. It is better if the last element of the list is the most narrow or alphanumeric, e.g. an identifier.
*)delayed_prec_unsafe : Prec_cell.t;Participates in the computation of storage_prec. Sealed (gh-ocannl-1624): read with Prec_cell.get (or get_specified_prec), written only by update_prec and update_infer_prec, which guard it and count each write.
mutable bounds : bounds_state;mutable memory_mode_intent : (memory_mode * provenance) Base.option;The tnode's declared intent -- requests made at graph-construction time (parameter and constant marking, Train.set_materialized, op-support Never_virtual), paired with a provenance tag saying which request it was. Since the context-scoped memory-modes split (docs/proposals/context-scoped-memory-modes.md) this is monotone, side-effect free to read, and never written by the compilation pipeline: placement decisions are per-compilation-lineage, recorded in Placements tables riding Low_level.optimize_ctx.
mutable observable : Base.bool;Declared host-observation intent (docs/proposals/context-scoped-memory-modes.md category 2): someone intends to read this node's values (printing, persistence, inspection). Monotone-upward (set, never cleared) and side-effect free to read. Observation does not require materialization -- a Virtual resolution stays observable via recomputation -- so the only constraint this imposes on placement is: do not resolve the node into the Local-dependent unobservable class (Placements.default_to_most_local resolves Never_virtual to On_device rather than Local for observable nodes).
mutable context_owned : Base.bool;The node can be mutated independently in each context (a model parameter). Even when a routine only reads it, it must use working storage rather than a constant pool (cached per root, or per device for a host literal). Set at graph construction and never cleared.
*)mutable host_constant : Base.bool;Declared value-constancy: the node's values are fixed at construction and always equal its registered host-init data (an ndarray-backed literal). Set by Tensor.ndarray (and eligible loaders), never cleared. This carries the constancy that Effectively_constant intent cannot: ndarray-backed nodes are minted On_device (provenance Site "49:ndarray-backed"), so the memory-mode lattice has no room left for the constant marking. Consumed by Schedule.Stage's hoisted packing (gh-ocannl-470) to justify materializing a repacked copy once per device.
mutable zero_valid : Base.bool;Declared accumulator semantics: allocation-time zero is a correct value for this node, so Context.run may bind it as a READ-ONLY input that nothing has written (gh-ocannl-1462). Set at creation of every gradient node (by Tensor) and by mark_zero_valid, never cleared. It is permission to consume the zeros a fresh buffer holds, not evidence that a buffer was written: it does not excuse an unwritten read-before-write input, nor any other node aliasing or slicing this one.
mutable alias_of : (t * Indexing.static_symbol) Base.option;When Some (parent, batch_idx), this node is a zero-copy slice-alias *view* of parent: it owns no buffer of its own, and every read/write of it is redirected (during lowering) to parent with batch_idx prepended as the leading index. Set by Assignments.lower for alias-eligible Fetch.Slices; orthogonal to memory_mode_intent. The strong reference to parent also keeps it reachable for as long as the alias is.
mutable slice_of : (t * Indexing.static_symbol) Base.option;When Some (parent, batch_idx), this node is an \@| sub-tensor slice of parent. Set *eagerly at construction* (independent of alias eligibility), so it is a superset of alias_of: every confirmed alias is a slice, but an ineligible slice (precision- converting, padded, virtual parent) is still a slice that falls back to a materializing copy. Used to reject direct host access (read/write the parent instead) -- including the window before lowering decides eligibility, where alias_of is still None.
mutable backend_info : Base.Sexp.t;mutable code_name : Base.string Base.option;}val sexp_of_t : t -> Sexplib0.Sexp.tRuns f with node uids allocated from the reserved range (see fresh_uid).
val validity_check : (t -> Base.unit) Base.option Base.refval num_elems : t -> Base__Int.tval size_in_bytes_peek : t -> Base__Int.ttn's size in bytes at its storage precision — the value tn.size_in_bytes forces to — computed WITHOUT forcing that lazy. header prints it, so the header reports a property of the node rather than whether some code path happened to force the lazy (gh-ocannl-1369). A query meant to leave the node as it found it — a schedule-time estimate one backend alone runs, such as Schedule.thread_private_bytes or Schedule.workgroup_memory_bytes — reads this too, so no observer of the lazy's state can tell which backend ran.
val dims_without_padding : t -> Base.int Base.arrayval get_padding : t -> (Ops.axis_padding Base.array * Base.float) Base.optionval id : t -> Base.String.tval label : t -> Base.String.tno_grad_ident_label label is whether label names a gradient, and its identifier: the label's identifier components (operator labels such as *. dropped) joined by _ with the numeric components last, a leading "grad" peeled off as the gradient flag, and a _ prepended to a result that starts with a digit; None when no identifier component remains. The one source of both the minted heuristic identifier (styled_ident) and the pre-minting display name (get_debug_name).
val get_debug_name :
?code_name:??? ->
?namespace:??? ->
id:Base__Int.t ->
label:Base.String.t Base.List.t ->
unit ->
Base.String.tget_debug_name ?code_name ~id ~label () is the code name when one was minted (_grad read as .grad). Otherwise it is the name the label spells -- label_name of a node with this id and label (gh-ocannl-1686): the identifier of no_grad_ident_label with .grad for a gradient, and n<id> (a gradient: its value's id) when the label has no identifier component. So before and after minting one scheme names a node.
val debug_name : t -> Base.String.tdebug_name tn is a display name, not an identity. Before any renderer has minted an identifier for tn it is label_name (gh-ocannl-1686). Once one has (update_code_name) it is that code name, and the name can change over the process: a node whose label another node of the same routine also carries is minted as n<id>_<label>, and that form sticks. Which renderers run depends on configuration -- with output_debug_files_in_build_directory the .cd/.ll printers name nodes the C emitter never asks about, such as a merge-buffer source -- so the same program can report different debug names (gh-ocannl-1552). To find a node by name, compare label_name (or label).
val debug_memory_mode : (memory_mode * provenance) option -> Base.String.tval log_debug_info : from_log_level:int -> t -> unitval most_local_materialized_mode : t -> memory_modeThe mode Never_virtual resolves to when defaulted: Local if the node fits the stack threshold, On_device otherwise.
val is_observable : t -> Base.boolval set_observable : t -> unitDeclares host-observation intent (see observable). Monotone: there is no unset.
val is_alias : t -> boolA slice-alias view (see alias_of). Such a node owns no buffer of its own; its accesses are redirected to its parent during lowering.
val alias_of : t -> (t * Indexing.static_symbol) Base.optionval set_alias_of : t -> parent:t -> batch_idx:Indexing.static_symbol -> unitMarks tn as a zero-copy slice-alias view of parent with leading index batch_idx. Idempotent when re-marked with the same parent.
val is_slice : t -> boolval slice_of : t -> (t * Indexing.static_symbol) Base.optionval set_slice_of : t -> parent:t -> batch_idx:Indexing.static_symbol -> unitMarks tn as an \@| slice of parent eagerly at construction (before alias eligibility is known). Idempotent.
val known_not_materialized : t -> boolval known_constant : t -> boolval known_host_constant : t -> Base.boolWhether tn's values are declared fixed at construction, forever equal to its registered host-init data (see host_constant). Includes known_constant intent: small constants keep the Effectively_constant marking (their creation does not pass through the On_device-minting ndarray-backed path).
val set_context_owned : t -> unitDeclares that tn needs independently mutable storage in each context. Monotone: set, never cleared.
val set_host_constant : t -> unitDeclares that tn's values are fixed at construction (host-init-backed literal). Monotone: set, never cleared.
val mark_zero_valid : t -> unitDeclares that allocation-time zero is a correct value of tn (see zero_valid): a routine may then read it, read-only, before anything writes it. Gradients are marked at creation; mark any other read-only accumulator the same way.
val is_zero_valid : t -> Base.boolval known_non_virtual : t -> boolval known_virtual : t -> boolval mode_is_unspecified : t -> boolval transition_memory_mode :
debug_name:Base.string ->
(memory_mode * provenance) option ->
memory_mode ->
provenance ->
memory_mode * provenanceThe pure memory-mode lattice transition, shared between update_memory_mode (tnode-level declared intent) and per-context placement resolution (Placements). Returns the new state; raises on conflicting requests.
val update_memory_mode : t -> memory_mode -> provenance -> unittype open_snapshot = {snap_node : t;snap_prec : Prec_cell.snapshot;snap_bounds : bounds_state;snap_memory_mode_intent : (memory_mode * provenance) Base.option;snap_observable : Base.bool;snap_context_owned : Base.bool;snap_host_constant : Base.bool;snap_zero_valid : Base.bool;snap_alias_of : (t * Indexing.static_symbol) Base.option;snap_slice_of : (t * Indexing.static_symbol) Base.option;snap_backend_info : Base.Sexp.t;snap_code_name : Base.string Base.option;}Checkpoint snapshots of open nodes (gh-ocannl-903)
A checkpoint records every mutable field of each node the open work allocated, and a rewind puts them back: the precision links, memory-mode intents, ownership and observability a later construction (or Tensor.param over an open initializer) wrote. The record is destructured exhaustively, so a new field of t is a compile error here until the snapshot accounts for it. The lazies are not snapshotted: dims, padding and size_in_bytes are forced only through shape inference's committed-state reads, which a rewind's validity check watches, and storage_prec is the precision cell's, whose settling open_snapshot_settled reports.
val snapshot_open : t -> open_snapshotval open_snapshot_node : open_snapshot -> tval open_snapshot_settled : open_snapshot -> Base.boolWhether the node's precision began settling after the snapshot: the rewind cannot reset it.
val restore_open : open_snapshot -> unitval open_snapshot_differences : open_snapshot -> Base.String.t Base.List.tThe fields of the node that differ from the snapshot (physically), by name: a diagnostic.
bounds_state)val propose_bounds : what:Base.String.t -> t -> Interval.t -> unitJoins iv into the node's candidate bounds. Post-settlement, instead validates that iv fits the settled interval and raises Utils.User_error otherwise -- a wider write after a reader already folded a guard against the bounds would leave compiled code unsound. what names the proposing write for the error message.
val pin_bounds_top : what:Base.String.t -> t -> unitPins the candidate to Interval.top: Phase B v1 execution anchoring for compiled device writes. Post-settlement (of non-top bounds) this raises like any non-fitting proposal.
val bounds_candidate : t -> Interval.t optionThe current candidate (or settled) bounds, if any proposal was made. Readers should intersect with the precision's machine range themselves.
val settle_bounds : t -> unitMarks the candidate as consumed by a guard fold. Idempotent; raises if nothing was ever proposed (folds must only consume existing candidates).
val bounds_scan_worthwhile : Ops.prec -> boolWhether host-upload scans can profit for this precision: integer storage only -- the current consumers (gather-guard discharge, index-width selection) act on integer-valued facts, and the Phase A folding policy ignores float bounds (binding constraint 8: gate the O(n) scans).
val scan_host_bounds : Ops.prec -> Nd.t -> Interval.tScans a host buffer about to initialize tn's device data and proposes the observed bounds (pre-settlement) or validates them (post-settlement). Call on every host-write path (Context.from_host-mediated writes and link-time Host_inits uploads). No-op for float and opaque precisions, and when the candidate is already pinned to top.
include sig ... endval comparator : (t, comparator_witness) Base__Comparator.comparatorval hash : t -> Base__Ppx_hash_lib.Std.Hash.hash_valueval hash_fold_t :
Base__.Ppx_hash_lib.Std.Hash.state ->
t ->
Base__.Ppx_hash_lib.Std.Hash.stateval hash_t : t -> Base__Ppx_hash_lib.Std.Hash.hash_valuemodule Comp : sig ... endmodule Placements : sig ... endval sexp_of_t_set : (t, 'a) Base.Set.t -> Sexplib0.Sexp.tval dims_to_string : ?with_axis_numbers:??? -> t -> Base.String.tval label_name : t -> Base.String.tlabel_name tn is the identifier the default (heuristic) style mints for tn when no other node of its routine carries the same label: the label's identifier components (an initializer's operator label such as *. dropped, numeric components last), a leading "grad" as a .grad suffix, and n<id> for a label with no identifier component. It is computed from the label alone, so unlike debug_name -- which equals it until a code name is minted, and takes the n<id>_ form once a routine repeats the label -- it never changes over the process, and it is the name to find a node by (gh-ocannl-1606). label is the raw join, operator labels included.
val styled_ident :
repeating_nograd_idents:(Base.String.t, 'a) Base.Hashtbl.t ->
repeating_grad_idents:(Base.String.t, 'a) Base.Hashtbl.t ->
[< `Heuristic_ocannl of [< `Dot_grad | `Under_grad ]
| `Name_and_label
| `Name_only ] ->
t ->
Base.String.tval update_code_name : t -> Base.string -> unitval header : t -> stringmodule Registry : sig ... endval registry : Registry.tval create :
?namespace:??? ->
delayed_prec ->
id:Base__Int.t ->
label:Base.String.t Base.List.t ->
unpadded_dims:Base__Int.t Base.Array.t Lazy.t ->
padding:(Ops.axis_padding Base.array * Base.float) Base.option Lazy.t ->
unit ->
tval create_from_padded :
?namespace:??? ->
id:Base__Int.t ->
label:Base.String.t Base.List.t ->
ndarray:Nd.t ->
padding:(Ops.axis_padding Base.array * Base.float) Base.option ->
unit ->
t * Nd.t lazy_tval initial_default_prec : Ops.precval find_namespaced :
namespace:Base.string ->
id:Base.int ->
Registry.data optionval find : id:Base.int -> Registry.data optionHost-side value access (get_value, set_value, get_values, set_values, points_1d, points_2d) now lives in Context: it requires an explicit context and performs an on-demand device-to-host transfer. There is no host copy stored on the tensor node.