Module Ir.Tnode

module Lazy = Utils.Lazy
module Nd = Ndarray
val _get_local_debug_runtime : unit -> (module Minidebug_runtime.Debug_runtime)
type memory_mode =
  1. | Effectively_constant
    (*

    A constant, or a subset of Virtual.

    *)
  2. | Virtual
    (*

    The 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, including by later routines and for printing. 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 -- on a Local node inherits its unobservability (the recompilation raises the same User_error).

    *)
  3. | Never_virtual
    (*

    An as-yet-unresolved request; resolves to Local or On_device.

    *)
  4. | Local
    (*

    Routine-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.

    *)
  5. | On_device
    (*

    The 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 memory_mode_of_sexp : Sexplib0.Sexp.t -> memory_mode
val sexp_of_memory_mode : memory_mode -> Sexplib0.Sexp.t
val compare_memory_mode : memory_mode -> memory_mode -> Base.int
val equal_memory_mode : memory_mode -> memory_mode -> Base.bool
type delayed_prec =
  1. | Default of Ops.prec
  2. | Inferred of Ops.prec Lazy.t
  3. | Specified of Ops.prec
val delayed_prec_of_sexp : Sexplib0.Sexp.t -> delayed_prec
val sexp_of_delayed_prec : delayed_prec -> Sexplib0.Sexp.t
val equal_delayed_prec : delayed_prec -> delayed_prec -> Base.bool
type bounds_state =
  1. | Bounds_unknown
    (*

    No proposal yet: readers fall back to the precision's machine range.

    *)
  2. | Bounds_proposed of Interval.t
    (*

    Join of all proposals so far.

    *)
  3. | Bounds_settled of Interval.t
    (*

    Consumed 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 bounds_state_of_sexp : Sexplib0.Sexp.t -> bounds_state
val sexp_of_bounds_state : bounds_state -> Sexplib0.Sexp.t
val equal_bounds_state : bounds_state -> bounds_state -> Base.bool
val default_namespace : string
val validate_namespace : Base.String.t -> unit

Namespaces 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.

val current_namespace : string Base.ref
val set_current_namespace : Base.String.t -> Base.unit
val get_current_namespace : unit -> string
type t = {
  1. prec : Ops.prec Lazy.t;
  2. dims : Base.int Base.array Lazy.t;
  3. padding : (Ops.axis_padding Base.array * Base.float Base.option) 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. The inner float option is None when the tensor is used by operations with different neutral elements, requiring margin resets before each operation.

    *)
  4. size_in_bytes : Base.int Lazy.t;
  5. id : Base.int;
    (*

    The within-session id ("s_id" of gh-ocannl-372): consecutive number for nodes created in the current session, restarting at 0 on Tensor.unsafe_reinitialize. Full node identity for presentation and persistence is the pair (namespace, id); in-memory identity is uid.

    *)
  6. 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.

    *)
  7. 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.

    *)
  8. 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.

    *)
  9. mutable delayed_prec_unsafe : delayed_prec;
    (*

    Participates in the computation of prec.

    *)
  10. mutable bounds : bounds_state;
    (*

    Scalar value bounds summary; see bounds_state for the lifecycle.

    *)
  11. mutable memory_mode : (memory_mode * Base.int) 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 identifier. 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.

    *)
  12. 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).

    *)
  13. 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 49), 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.

    *)
  14. 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. The strong reference to parent also keeps it reachable for as long as the alias is.

    *)
  15. 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.

    *)
  16. mutable backend_info : Base.Sexp.t;
  17. mutable code_name : Base.string Base.option;
}
val sexp_of_t : t -> Sexplib0.Sexp.t
val compare : t -> t -> Base.int
val next_uid : int Base.ref
val fresh_uid : unit -> int
val num_elems : t -> Base__Int.t
val dims_without_padding : t -> Base.int Base.array
val get_padding : t -> (Ops.axis_padding Base.array * Base.float Base.option) Base.option
val ident_prefix : Base.String.t -> Base.String.t
val id : t -> Base.String.t
val label : t -> Base.String.t
val is_alphanum_ : Base.String.t -> Base.bool
val collapse_consecutive : Base.String.t list -> Base.String.t Base.List.t
val get_debug_name : ?code_name:Base.String.t -> ?namespace:Base.String.t -> id:Base__Int.t -> label:Base.String.t Base.List.t -> unit -> Base.String.t
val debug_name : t -> Base.String.t
val debug_memory_mode : (memory_mode * Base.Int.t) option -> Base.String.t
val log_debug_info : from_log_level:int -> t -> unit
val most_local_materialized_mode : t -> memory_mode

The mode Never_virtual resolves to when defaulted: Local if the node fits the stack threshold, On_device otherwise.

val is_observable : t -> Base.bool
val set_observable : t -> unit

Declares host-observation intent (see observable). Monotone: there is no unset.

val is_alias : t -> bool

A 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.option
val set_alias_of : t -> parent:t -> batch_idx:Indexing.static_symbol -> unit

Marks 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 -> bool

Whether tn is an \@| sub-tensor slice (see slice_of) -- set eagerly at construction, independent of alias eligibility. A superset of is_alias.

val slice_of : t -> (t * Indexing.static_symbol) Base.option
val set_slice_of : t -> parent:t -> batch_idx:Indexing.static_symbol -> unit

Marks tn as an \@| slice of parent eagerly at construction (before alias eligibility is known). Idempotent.

val known_not_materialized : t -> bool
val known_constant : t -> bool
val known_host_constant : t -> Base.bool

Whether 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_host_constant : t -> unit

Declares that tn's values are fixed at construction (host-init-backed literal). Monotone: set, never cleared.

val known_non_virtual : t -> bool
val known_virtual : t -> bool
val mode_is_unspecified : t -> bool
val transition_memory_mode : debug_name:string -> (memory_mode * Base.Int.t) option -> memory_mode -> Base.Int.t -> memory_mode * Base.Int.t

The 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 -> Base.Int.t -> unit
val update_prec : ?only_if:(Ops.prec -> bool) -> t -> Ops.prec -> unit
val update_infer_prec : ?only_if:(Ops.prec -> bool) -> t -> Ops.prec Lazy.t -> unit
val get_specified_prec : t -> Ops.prec option

Value-bounds lifecycle

(see bounds_state)

val propose_bounds : what:Base.String.t -> t -> Interval.t -> unit

Joins 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 -> unit

Pins 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 option

The current candidate (or settled) bounds, if any proposal was made. Readers should intersect with the precision's machine range themselves.

val settle_bounds : t -> unit

Marks 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 -> bool

Whether 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.t
val propose_bounds_from_host : t -> Nd.t -> unit

Scans 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 ... end
type comparator_witness
val comparator : (t, comparator_witness) Base__Comparator.comparator
val equal : t -> t -> Base.bool
val hash : t -> Base__Ppx_hash_lib.Std.Hash.hash_value
val hash_fold_t : Base__.Ppx_hash_lib.Std.Hash.state -> t -> Base__.Ppx_hash_lib.Std.Hash.state
val hash_t : t -> Base__Ppx_hash_lib.Std.Hash.hash_value
module Comp : sig ... end
module Placements : sig ... end
type t_set = Base.Set.M(Comp).t
val sexp_of_t_set : (t, 'a) Base.Set.t -> Sexplib0.Sexp.t
type 'a t_map = (t, 'a, comparator_witness) Base.Map.t
val sexp_of_t_map : ('a -> Sexplib0.Sexp.t) -> (t, 'a, 'b) Base.Map.t -> Sexplib0.Sexp.t
val dims_to_string : ?with_axis_numbers:bool -> t -> Base.String.t
val no_grad_ident_label : t -> bool * Base.String.t option
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.t
val update_code_name : t -> Base.string -> unit
val get_style : ?arg_name:Base.string -> ?no_dots:bool -> unit -> [> `Heuristic_ocannl of [> `Dot_grad | `Under_grad ] | `Name_and_label | `Name_only ]
val header : t -> string
module Registry : sig ... end
val registry : Registry.t
val prec_of_dalayed : t -> Ops.prec
val validate_padded_numel_contract : id:Base__Int.t -> label:Base.String.t Base.List.t -> Base.int Base.array -> unit
val create : ?namespace:Base.String.t -> 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) Base.option Lazy.t -> unit -> t
val create_from_padded : ?namespace:Base.String.t -> id:Base__Int.t -> label:Base.String.t Base.List.t -> ndarray:Nd.t -> padding:(Ops.axis_padding Base.array * Base.float Base.option) Base.option -> unit -> t * Nd.t lazy_t
val create_with_reshape : id:Base__Int.t -> label:Base.String.t Base.List.t -> base_ndarray:Nd.t -> unpadded_dims:Base__Int.t Base.Array.t Lazy.t -> padding: (Ops.axis_padding Base.array * Base.float Base.option) Base.Option.t Lazy.t -> from_padded:bool -> unit -> t * Nd.t lazy_t
val initial_default_prec : Ops.prec
val find_namespaced : namespace:Base.string -> id:Base.int -> Registry.data option
val find : id:Base.int -> Registry.data option

Accessors

Host-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.

val print_accessible_headers : ?pred:(Registry.data -> bool) -> unit -> Base.unit
val log_accessible_headers : ?pred:(Registry.data -> bool) -> unit -> unit