Skip to main content

onnx_runtime_memory_api/
lib.rs

1//! # `onnx-runtime-memory-api`
2//!
3//! Low-dependency memory mechanism contracts shared by allocators, governors,
4//! and execution providers.
5//!
6//! This crate is the lowest layer of the runtime memory stack. It owns the
7//! minimum ordinary allocator contract, explicit optional virtual-backing and
8//! shared-mapping capabilities, manager-issued binding identity/lifetime pins,
9//! and the owning/deferred-release contract that says who owns a physical
10//! release and what is true after one partially fails. It does not own
11//! allocation policy, accounting, synchronization, or process-level transaction
12//! management.
13//!
14//! Governor-specific capacity tokens and grants remain in
15//! `onnx-runtime-memory-governor`; they are not methods every allocator or
16//! optional capability must implement.
17
18pub mod allocator;
19pub mod binding;
20pub mod capability;
21pub mod context_pin;
22pub mod deferred;
23
24pub use allocator::{
25    AllocationCommitRange, DeviceAllocator, DeviceKey, HostAllocator, MappedAllocation,
26    SharedDevicePrefix, SharedPrefixCommitInfo,
27};
28pub use binding::{
29    AllocationGeneration, AllocationIdentity, AuthorityIdentity, BindingError, BindingGeneration,
30    BindingId, BindingIdentity, BindingRegistry, BindingResource, BoundAllocation, BoundMemoryView,
31    BoundSharedMapping, BoundSharedPrefix, BoundVirtualBacking, ExplicitReleaseError,
32    MechanismCoherence, MechanismIdentity, MechanismLifecycle, MechanismSnapshot, MemoryBinding,
33    OwnedView, OwningAllocation, OwningReleaseError, ProviderContextIdentity, RegisteredAuthority,
34    RegisteredMechanism, RegisteredProviderContext, ValidatedMemoryView,
35};
36pub use capability::{SharedMapping, VirtualBacking};
37pub use context_pin::{ProviderContextPin, ProviderContextPinError, ProviderContextPinSource};
38pub use deferred::{
39    AllocationReleaseOutcome, AllocationReleaseState, DeferredEnqueueError,
40    DeferredEnqueueRejection, DeferredReleaseDisposition, DeferredReleaseQueue,
41    PreparedAllocationRelease, QuarantineReason, QuarantinedAllocation, ReleaseAccounting,
42    ReleaseFailure, ResidualOwnership,
43};
44
45/// Where the bytes physically live.
46///
47/// Ordered from fastest to slowest, which is also the demotion order.
48#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
49pub enum Tier {
50    /// Accelerator memory (VRAM).
51    Device,
52    /// Host RAM.
53    Host,
54    /// Spill file on disk.
55    Disk,
56}
57
58impl Tier {
59    /// Every tier, fastest first.
60    pub const ALL: [Tier; 3] = [Tier::Device, Tier::Host, Tier::Disk];
61
62    pub const fn index(self) -> usize {
63        match self {
64            Tier::Device => 0,
65            Tier::Host => 1,
66            Tier::Disk => 2,
67        }
68    }
69
70    /// Human-facing name used in error messages.
71    pub const fn name(self) -> &'static str {
72        match self {
73            Tier::Device => "device",
74            Tier::Host => "host",
75            Tier::Disk => "disk",
76        }
77    }
78}
79
80/// What a reservation is for.
81#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
82pub enum MemoryRole {
83    KvCache,
84    Workspace { step_scoped: bool },
85    Weights,
86    Activation,
87}
88
89/// Shared error vocabulary for mechanism and governance operations.
90///
91/// Not `Clone`/`PartialEq`: a refusal that carries the cause underneath it
92/// cannot be meaningfully duplicated or compared, and keeping the cause is
93/// worth more than either. Match on the variant instead.
94#[derive(Debug, thiserror::Error)]
95pub enum MemoryError {
96    /// The tier does not have room, and no holder released enough.
97    #[error(
98        "cannot reserve {requested} bytes of {tier} memory for {role:?}: {used} of {limit} bytes \
99         are already leased, leaving {available}; free memory by closing sessions, lower the \
100         demand, or raise the {tier} limit"
101    )]
102    TierExhausted {
103        /// Which tier ran out.
104        tier: &'static str,
105        /// What the caller asked for.
106        requested: u64,
107        /// Bytes leased before this request.
108        used: u64,
109        /// The tier ceiling.
110        limit: u64,
111        /// `limit - used`.
112        available: u64,
113        /// The role that was refused.
114        role: MemoryRole,
115    },
116    /// The request itself is not representable.
117    #[error("cannot reserve {requested} bytes of {tier} memory: {reason}")]
118    InvalidRequest {
119        /// Which tier was addressed.
120        tier: &'static str,
121        /// What the caller asked for.
122        requested: u64,
123        /// What is wrong with it.
124        reason: &'static str,
125    },
126    /// The request was well formed and within budget, but the allocator behind
127    /// the tier refused it for a reason of its own.
128    ///
129    /// Distinct from [`MemoryError::TierExhausted`], which means *we* declined,
130    /// and from [`MemoryError::InvalidRequest`], which means the caller asked
131    /// for something impossible. This one carries the backing allocator's own
132    /// account of the failure, which is usually the only thing that identifies
133    /// it: a driver that is out of memory and a driver that has no context both
134    /// fail an allocation, and calling them both "out of memory" sends the next
135    /// person to read the log in the wrong direction.
136    #[error("cannot allocate {requested} bytes of {tier} memory: {reason}")]
137    AllocationFailed {
138        /// Which tier was addressed.
139        tier: &'static str,
140        /// What the caller asked for.
141        requested: u64,
142        /// What the backing allocator said.
143        reason: String,
144    },
145    /// A well-formed capacity transfer or backing claim could not make enough
146    /// governed bytes available.
147    #[error(
148        "cannot make {requested} bytes of {tier} capacity available for {role:?}: only \
149         {available} bytes became available; {detail}"
150    )]
151    CapacityUnavailable {
152        tier: &'static str,
153        requested: u64,
154        available: u64,
155        role: MemoryRole,
156        /// What this layer can say about the shortfall on its own.
157        ///
158        /// Names the operation that came up short; the refusal underneath it,
159        /// when there was one, belongs in `source` rather than being folded in
160        /// here, so that a caller can still match on it and a reader is not
161        /// shown the same sentence twice.
162        detail: String,
163        /// The refusal this one is reporting, when it is reporting one.
164        ///
165        /// `None` when this layer decided on its own, as when a reclaim target
166        /// simply was not reached. Typed as `dyn Error` rather than a boxed
167        /// [`MemoryError`] both because the layer underneath is not always a
168        /// governor and because `#[source]` on a `Box<ConcreteError>` hands
169        /// callers a chain node whose concrete type is the *box*, so
170        /// `downcast_ref::<MemoryError>()` would miss it -- which is the whole
171        /// thing this field exists to make possible.
172        #[source]
173        source: Option<Box<dyn std::error::Error + Send + Sync>>,
174    },
175}
176
177#[cfg(test)]
178mod tests {
179    use super::*;
180
181    #[test]
182    fn tier_order_and_names_are_stable() {
183        assert_eq!(Tier::ALL, [Tier::Device, Tier::Host, Tier::Disk]);
184        assert_eq!(Tier::Device.name(), "device");
185        assert_eq!(Tier::Host.name(), "host");
186        assert_eq!(Tier::Disk.name(), "disk");
187    }
188}