Module Ir.Ndarray

module Lazy = Utils.Lazy

N-dimensional arrays: a precision-handling wrapper for Bigarray.Genarray and its utilities.

val _get_local_debug_runtime : unit -> (module Minidebug_runtime.Debug_runtime)
module A = Stdlib.Bigarray.Genarray

*** Handling of precisions ***

type ('ocaml, 'elt_t) bigarray = ('ocaml, 'elt_t, Stdlib.Bigarray.c_layout) A.t
val bigarray_start_not_managed : ('ocaml, 'elt_t) bigarray -> Ctypes_ptr.voidp
val big_ptr_to_string : ('a, 'b) bigarray -> Base.String.t
val sexp_of_bigarray : ('a, 'b) bigarray -> Base.Sexp.t
type byte_nd = (Base.char, Ops.uint8_elt) bigarray
type uint16_nd = (Base.int, Ops.uint16_elt) bigarray
type int32_nd = (Base.int32, Ops.int32_elt) bigarray
type uint32_nd = (Base.int32, Ops.int32_elt) bigarray
type int64_nd = (Base.int64, Ops.int64_elt) bigarray
type uint64_nd = (Base.int64, Ops.int64_elt) bigarray
type uint4x32_nd = (Stdlib.Complex.t, Stdlib.Bigarray.complex64_elt) bigarray
type half_nd = (Base.float, Ops.float16_elt) bigarray
type bfloat16_nd = (Base.int, Ops.uint16_elt) bigarray
type fp8_nd = (Base.char, Ops.uint8_elt) bigarray
type single_nd = (Base.float, Ops.float32_elt) bigarray
type double_nd = (Base.float, Ops.float64_elt) bigarray
val sexp_of_byte_nd : byte_nd -> Base.Sexp.t
val sexp_of_uint16_nd : uint16_nd -> Base.Sexp.t
val sexp_of_int32_nd : int32_nd -> Base.Sexp.t
val sexp_of_uint32_nd : uint32_nd -> Base.Sexp.t
val sexp_of_int64_nd : int64_nd -> Base.Sexp.t
val sexp_of_uint64_nd : uint64_nd -> Base.Sexp.t
val sexp_of_uint4x32_nd : uint4x32_nd -> Base.Sexp.t
val sexp_of_half_nd : half_nd -> Base.Sexp.t
val sexp_of_bfloat16_nd : bfloat16_nd -> Base.Sexp.t
val sexp_of_fp8_nd : fp8_nd -> Base.Sexp.t
val sexp_of_single_nd : single_nd -> Base.Sexp.t
val sexp_of_double_nd : double_nd -> Base.Sexp.t
type t =
  1. | Byte_nd of byte_nd
  2. | Uint16_nd of uint16_nd
  3. | Int32_nd of int32_nd
  4. | Uint32_nd of uint32_nd
  5. | Int64_nd of int64_nd
  6. | Uint64_nd of uint64_nd
  7. | Uint4x32_nd of uint4x32_nd
  8. | Half_nd of half_nd
  9. | Bfloat16_nd of bfloat16_nd
  10. | Fp8_nd of fp8_nd
  11. | Single_nd of single_nd
  12. | Double_nd of double_nd
val sexp_of_t : t -> Sexplib0.Sexp.t
val as_array : ('ocaml, 'elt_t) Ops.precision -> ('ocaml, 'elt_t) bigarray -> t
val precision_to_bigarray_kind : ('ocaml, 'elt_t) Ops.precision -> ('ocaml, 'elt_t) Stdlib.Bigarray.kind
val precision_string : t -> string
val default_kind : (Base.float, Ops.float32_elt) Ops.precision
val get_prec : t -> Ops.prec
type 'r apply_with_prec = {
  1. f : 'ocaml 'elt_t. ('ocaml, 'elt_t) Ops.precision -> ('ocaml, 'elt_t) bigarray -> 'r;
}
val apply_with_prec : 'a apply_with_prec -> t -> 'a
val create_bigarray_of_prec : ('ocaml, 'elt_t) Ops.precision -> int array -> ('ocaml, 'elt_t) bigarray

*** Initialization ***

val create_bigarray : ('ocaml, 'elt_t) Ops.precision -> dims:int array -> padding:('a * float) option -> ('ocaml, 'elt_t) bigarray

*** Accessing ***

type 'r apply_as_bigarray = {
  1. f : 'ocaml 'elt_t. ('ocaml, 'elt_t) bigarray -> 'r;
}
val apply : 'a apply_as_bigarray -> t -> 'a
type 'r apply2_as_bigarray = {
  1. f2 : 'ocaml 'elt_t. ('ocaml, 'elt_t) bigarray -> ('ocaml, 'elt_t) bigarray -> 'r;
}
val apply2 : 'a apply2_as_bigarray -> t -> t -> 'a
val dims : t -> int array
val get_fatptr_not_managed : t -> ('a option, unit Ctypes_static.typ) Ctypes_memory.Fat.t
val get_voidptr_not_managed : t -> Base.unit Ctypes.ptr
val adjust_idx_for_padding : ?padding:??? -> Base__Int.t Base.Array.t -> Base__Int.t Base.Array.t

Helper function to adjust indices by adding left padding when padding is specified

val compute_end_idx : ?padding:??? -> Base__Int.t Base.Array.t -> int -> Base__Int.t

Helper function to compute end index for iteration, respecting padding margins

val uint32_to_float : Base.int32 -> float
val float_to_uint32 : float -> int32
val two_pow_63 : float
val uint64_to_float : Base.int64 -> Base__Float.t
val float_to_uint64 : Base.Float.t -> int64
val set_from_float : ?padding:??? -> t -> Base__Int.t Base.Array.t -> Base.Float.t -> unit
val fill_from_float : t -> Base.Float.t -> unit
val fold_bigarray : ?padding:??? -> ('a, 'b, 'c) A.t -> init:'d -> f:('d -> Base__Int.t Base.Array.t -> 'a -> 'd) -> 'd
val fold_as_float : ?padding:??? -> init:'a -> f:('a -> Base__Int.t Base.Array.t -> Base.float -> 'a) -> t -> 'a
val size_in_bytes : t -> int
val get_as_float : ?padding:??? -> t -> Base__Int.t Base.Array.t -> Base.Float.t
val retrieve_2d_points : ?from_axis:??? -> ?padding:??? -> xdim:int -> ydim:int -> t -> (Base.Float.t * Base.Float.t) Base.Array.t
val retrieve_1d_points : ?from_axis:??? -> ?padding:??? -> xdim:int -> t -> Base.Float.t Base.Array.t
val retrieve_flat_values : ?padding:??? -> t -> Base.Float.t Base.Array.t
val set_flat_values : ?padding:??? -> t -> Base.Float.t Base.Array.t -> unit
val c_ptr_to_string : t -> Base.String.t
val ptr_to_string_hum : t -> Base.String.t
val to_native : t -> Ctypes_ptr.voidp
val equal : t -> t -> Base.bool
val compare : t -> t -> Base.int
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
val copy_with_padding : source:t -> target:t -> padding:Ops.axis_padding Base.array -> Base.unit

Copies the whole of source onto the parts of target skipping over padding margins -- requires that source dimensions + padding = target dimensions.

*** Creating ***

val used_memory : int Stdlib.Atomic.t
val create_array : debug:Base.string -> Ops.prec -> dims:Base.int Base.array -> padding:('a * float) option -> t
val mappable_file_region : prec:Ops.prec -> byte_offset:Base.int -> nbytes:Base.int -> Base.bool

Whether a prec-typed region of nbytes bytes at byte_offset of a file may be wrapped by map_file_array rather than decoded (gh-ocannl-588). Three conditions, none of them about the caller's format: a mapping is read in the host's byte order while the payload formats here are little-endian; Unix.map_file has no empty mapping; and the data pointer it hands back sits at byte_offset exactly, so an offset that is not a multiple of the element size would make a misaligned typed pointer -- undefined behaviour, and a trap on strict targets. That last one is easy to assume away: a format may align its payloads by construction and still put a wide one at an odd offset once a narrow payload precedes it. Whether the file's bytes are the buffer's bytes (no padding, no re-layout) is the caller's half of the question.

val map_file_array : ?shared:??? -> Ops.prec -> dims:Base.int Base.array -> byte_offset:Base.int -> Unix.file_descr -> t

Wraps the dims-shaped region of the file fd starting at byte_offset as an ndarray of precision prec, via Unix.map_file (gh-ocannl-467).

The returned array owns its mapping: the runtime unmaps it when the array is collected, and the mapping stays valid after fd is closed. byte_offset need not be page aligned -- the runtime maps from the enclosing page (on Windows, allocation-granularity) boundary and offsets the data pointer accordingly.

With ~shared:false (the default) the mapping is copy-on-write: writes to the array stay private to the process and never reach the file.

A mapped array is read with the host's byte order, whereas the payloads written by write_payload_to_channel are little-endian, so the caller is responsible for checking Stdlib.Sys.big_endian before mapping a payload. The mapping is not counted in get_used_memory: its pages are file-backed, not heap.

val reshape : t -> int array -> t

See Bigarray.reshape. The view shares nd's data, so it keeps nd itself alive: nd is the wrapper carrying the get_used_memory finalizer, and collecting it while a view still holds the bytes would end their accounting early.

val init_array : debug:Base.string -> Ops.prec -> dims:Base.int Base.array -> padding:(Ops.axis_padding Base.Array.t * float) option -> f:(Base.int Base.array -> Base.float) -> t

Initializes an array using a function from indices to values. Note: dims must include padding if padding is specified, but the callback f indices operate in the before-padding space.

This function is slow as it performs unboxing at each index. Generate and wrap a bigarray if efficiency is a concern.

val convert : Ops.prec -> t -> t

convert prec src is a fresh ndarray of precision prec with src's dimensions and values (converted through float); src itself when the precision already matches. Padding is not carried — the source's raw dims are used as-is. Load-time conversion for data-backed tensors (gh-ocannl-492): the inference-side counterpart of the training recipe's cast twins.

val get_used_memory : unit -> int

Bytes currently held by live host arrays created through create_array (and therefore init_array, which delegates to it). A live gauge, not a cumulative total: the allocation adds and the array's finalizer subtracts, so the count returns to its earlier value once the arrays are collected. Since finalizers only run at collection time, a reading right after dropping the arrays can still include them -- force a Stdlib.Gc.full_major first. A reshape view is not counted on its own but keeps its source's bytes counted, since it shares them; a map_file_array mapping is not counted at all. The backends' own device-side counters are separate (see Context.get_used_memory).

*** Printing ***

val int_dims_to_string : ?with_axis_numbers:??? -> ?padding:??? -> Base.Int.t Base.Array.t -> Base.String.t

Dimensions to string, "x"-separated, e.g. 1x2x3 for batch dims 1, input dims 3, output dims 2. Outputs "-" for empty dimensions.

val log_debug_info : from_log_level:int -> t -> unit

Logs information about the array on the default ppx_minidebug runtime, if from_log_level > Utlis.settings.with_log_level.

val concise_float : prec:Base__Int.t -> float -> string
val render_array : ?brief:??? -> ?prefix:??? -> ?entries_per_axis:??? -> ?labels:??? -> indices:Base__Int.t Base.Array.t -> t -> PrintBox.t

Prints 0-based indices entries out of arr, where a number between -5 and -1 in an axis means to print out the axis, and a non-negative number means to print out only the indexed dimension of the axis. Prints up to entries_per_axis or entries_per_axis+1 entries per axis, possibly with ellipsis in the middle. labels provides the axis labels for all axes (use "" or "_" for no label). The last label corresponds to axis -1 etc. The printed out axes are arranged as:

  • -1: a horizontal segment in an inner rectangle (i.e. column numbers of the inner rectangle),
  • -2: a sequence of segments in a line of text (i.e. column numbers of an outer rectangle),
  • -3: a vertical segment in an inner rectangle (i.e. row numbers of the inner rectangle),
  • -4: a vertical sequence of segments (i.e. column numbers of an outer rectangle),
  • -5: a sequence of screens of text (i.e. stack numbers of outer rectangles).
val to_doc : ?prefix:??? -> ?entries_per_axis:??? -> ?labels:??? -> indices:Base__Int.t Base.Array.t -> t -> PPrint.document
val to_doc_inline : num_batch_axes:Base__Int.t -> num_output_axes:Base__Int.t -> num_input_axes:Base__Int.t -> ?axes_spec:??? -> t -> PPrint.document

Prints the whole array in an inline syntax.

*** Binary payload I/O for persistence ***

val linear_offset_of_idx : Base__Int.t Base.Array.t -> Base__Int.t Base.Array.t -> int

Compute the C-layout linear offset from multi-dimensional index and dims.

val count_logical_elems : ?padding:??? -> t -> Base__Int.t

Count the number of logical (unpadded) elements in an ndarray.

val write_payload_to_channel : ?padding:??? -> t -> Stdlib.Out_channel.t -> int

Write the logical (unpadded) payload of an ndarray to a channel in native binary format (little-endian). Returns the number of bytes written.

val read_payload_from_channel : ?padding:??? -> t -> Stdlib.in_channel -> int -> Base.unit

Read native binary data from a channel into an ndarray, populating only the logical (unpadded) region.

*** Payload ingestion: mapped or decoded ***

type ingestion =
  1. | Mapped
  2. | Decoded

Which path ingest_payload took for one payload. The two produce equal values by construction, so this -- and the running ingestion_counts -- is the only way to observe which one ran.

val mapped_count : int Stdlib.Atomic.t
val decoded_count : int Stdlib.Atomic.t
val ingestion_counts : unit -> int * int

(mapped, decoded) payload counts since the start of the process: how many payloads ingest_payload wrapped as file mappings, and how many it decoded into fresh host buffers. One pair for every reader that ingests through ingest_payload -- checkpoints and safetensors alike -- so a caller interested in a single file's split reads it before and after that file's payloads. For tests, and for diagnosing a load that decodes more than expected.

val ingest_payload : ?padding:??? -> ?mmap:??? -> debug:Base.string -> Ops.prec -> dims:Base.int Base.array -> byte_offset:Base.int -> nbytes:Base.int -> Stdlib.in_channel -> t * ingestion

Ingests one payload of a little-endian binary file: the dims-shaped, prec-typed region of nbytes bytes at byte_offset of ic, as a mapping of the file where that is byte-equivalent (gh-ocannl-467, gh-ocannl-587) and otherwise as a fresh host buffer decoded from the channel. Returns the array and which of the two ran; both are counted in ingestion_counts.

A mapping is byte-equivalent when the file's bytes are the buffer's bytes. Three of the four conditions are the caller's format speaking through the arguments: ?padding means the payload holds only the logical region, which read_payload_from_channel has to scatter into the padded buffer; an empty dims has no mapping to take; and nbytes must be exactly the buffer, which also rejects a header claiming a byte length its dimensions and precision do not add up to. The fourth is mappable_file_region, the conditions the mapping itself imposes -- host byte order, non-emptiness, and element alignment of byte_offset, which no format guarantees for free: a packed layout puts a wide payload at an odd offset as soon as a narrow one precedes it. ?mmap:false declines mapping outright, for a caller that offers the choice.

The mapping is taken from the descriptor behind ic -- the channel the caller read the file's header through -- and NOT from a fresh open of its path: a concurrent atomic save would put a different inode at that name, and the offsets, extents and precisions being mapped describe the file this read started on. The mapping outlives the descriptor, and the directory entry, so nothing here depends on the file staying put; it is the returned array that owns it, and the caller's remaining duty is to keep whatever holds ic alive across this call.

val payloads_equal : ?padding:??? -> t -> t -> bool

Byte-for-byte comparison of logical payloads of two ndarrays. Both must have the same precision and logical dimensions.