1use std::collections::BTreeSet;
4use std::sync::Arc;
5
6use onnx_runtime_ir::DataType;
7
8use crate::ExternalMmapRegion;
9
10pub const NXRT_WEIGHT_PAGING_CAPABILITY: &str = "nxrt";
12
13#[derive(Clone, Debug, Default, PartialEq, Eq)]
14pub struct ExecutionProviderCapabilities {
15 flags: BTreeSet<String>,
16}
17
18impl ExecutionProviderCapabilities {
19 pub fn stock() -> Self {
20 Self::default()
21 }
22
23 pub fn nxrt_weight_paging() -> Self {
24 Self::from_flags([NXRT_WEIGHT_PAGING_CAPABILITY])
25 }
26
27 pub fn from_flags(flags: impl IntoIterator<Item = impl Into<String>>) -> Self {
28 Self {
29 flags: flags.into_iter().map(Into::into).collect(),
30 }
31 }
32
33 pub fn advertises(&self, capability: &str) -> bool {
34 self.flags.contains(capability)
35 }
36}
37
38#[derive(Clone, Debug, PartialEq, Eq)]
39pub struct ResidentWeight {
40 pub dtype: DataType,
41 pub shape: Vec<usize>,
42 bytes: Arc<[u8]>,
43}
44
45impl ResidentWeight {
46 pub fn new(
47 dtype: DataType,
48 shape: Vec<usize>,
49 bytes: impl Into<Arc<[u8]>>,
50 ) -> Result<Self, WeightHandleError> {
51 let elements = checked_shape_product(&shape)?;
52 let expected = dtype.checked_storage_bytes(elements).ok_or_else(|| {
53 WeightHandleError::InvalidResident("resident weight byte count overflow".into())
54 })?;
55 if expected > isize::MAX as usize {
56 return Err(WeightHandleError::InvalidResident(
57 "resident weight byte count exceeds isize::MAX".into(),
58 ));
59 }
60 let bytes = bytes.into();
61 if bytes.len() != expected {
62 return Err(WeightHandleError::InvalidResident(format!(
63 "resident weight has {} bytes, expected {expected}",
64 bytes.len()
65 )));
66 }
67 Ok(Self {
68 dtype,
69 shape,
70 bytes,
71 })
72 }
73
74 pub fn bytes(&self) -> &[u8] {
75 &self.bytes
76 }
77}
78
79fn checked_shape_product(shape: &[usize]) -> Result<usize, WeightHandleError> {
80 let mut product = 1usize;
81 let mut has_zero = false;
82 for &dimension in shape {
83 if dimension == 0 {
84 has_zero = true;
85 } else {
86 product = product.checked_mul(dimension).ok_or_else(|| {
87 WeightHandleError::InvalidResident("resident weight element count overflow".into())
88 })?;
89 }
90 }
91 Ok(if has_zero { 0 } else { product })
92}
93
94#[derive(Clone, Copy, Debug, PartialEq, Eq)]
95pub enum LazyWeightBoundary {
96 BlockQuantizedMoe,
98}
99
100impl LazyWeightBoundary {
101 pub fn matches(self, domain: &str, op_type: &str) -> bool {
102 matches!(self, Self::BlockQuantizedMoe)
103 && domain == "pkg.nxrt"
104 && op_type == "BlockQuantizedMoE"
105 }
106}
107
108pub trait ResidentWeightMaterializer: Send + Sync {
109 fn materialize(&self) -> Result<ResidentWeight, WeightHandleError>;
110}
111
112impl<F> ResidentWeightMaterializer for F
113where
114 F: Fn() -> Result<ResidentWeight, WeightHandleError> + Send + Sync,
115{
116 fn materialize(&self) -> Result<ResidentWeight, WeightHandleError> {
117 self()
118 }
119}
120
121#[derive(Clone)]
122pub struct LazyWeight {
123 pub boundary: LazyWeightBoundary,
124 pub regions: Vec<ExternalMmapRegion>,
126 resident_materializer: Arc<dyn ResidentWeightMaterializer>,
127}
128
129impl std::fmt::Debug for LazyWeight {
130 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
131 formatter
132 .debug_struct("LazyWeight")
133 .field("boundary", &self.boundary)
134 .field("regions", &self.regions)
135 .field("resident_materializer", &"<deferred>")
136 .finish()
137 }
138}
139
140impl LazyWeight {
141 pub fn block_quantized_moe<M>(
142 regions: Vec<ExternalMmapRegion>,
143 resident_materializer: M,
144 ) -> Result<Self, WeightHandleError>
145 where
146 M: ResidentWeightMaterializer + 'static,
147 {
148 if regions.is_empty() {
149 return Err(WeightHandleError::MissingRegions);
150 }
151 Ok(Self {
152 boundary: LazyWeightBoundary::BlockQuantizedMoe,
153 regions,
154 resident_materializer: Arc::new(resident_materializer),
155 })
156 }
157
158 pub fn materialize(&self) -> Result<ResidentWeight, WeightHandleError> {
160 self.resident_materializer.materialize()
161 }
162}
163
164#[derive(Clone, Debug)]
166pub enum WeightHandle {
167 Resident(ResidentWeight),
168 Lazy(LazyWeight),
169}
170
171impl WeightHandle {
172 pub fn negotiate(
173 &self,
174 capabilities: &ExecutionProviderCapabilities,
175 ) -> Result<NegotiatedWeight, WeightHandleError> {
176 match self {
177 Self::Resident(weight) => Ok(NegotiatedWeight::Resident(weight.clone())),
178 Self::Lazy(weight) if capabilities.advertises(NXRT_WEIGHT_PAGING_CAPABILITY) => {
179 Ok(NegotiatedWeight::Lazy(weight.clone()))
180 }
181 Self::Lazy(weight) => Ok(NegotiatedWeight::Resident(weight.materialize()?)),
182 }
183 }
184
185 pub fn is_lazy_for(&self, capabilities: &ExecutionProviderCapabilities) -> bool {
186 matches!(self, Self::Lazy(_)) && capabilities.advertises(NXRT_WEIGHT_PAGING_CAPABILITY)
187 }
188}
189
190#[derive(Clone, Debug)]
191pub enum NegotiatedWeight {
192 Resident(ResidentWeight),
193 Lazy(LazyWeight),
194}
195
196impl NegotiatedWeight {
197 pub fn materialize_host_fallback(&self) -> Result<ResidentWeight, WeightHandleError> {
199 match self {
200 Self::Resident(weight) => Ok(weight.clone()),
201 Self::Lazy(weight) => weight.materialize(),
202 }
203 }
204
205 pub fn try_bind_device<B: LazyDeviceWeightBinder>(
207 &self,
208 binder: &B,
209 ) -> Result<B::Binding, WeightHandleError> {
210 match self {
211 Self::Resident(_) => Err(WeightHandleError::Unsupported(
212 "resident weights do not require lazy device binding".into(),
213 )),
214 Self::Lazy(weight) => binder.bind_block_quantized_moe(weight),
215 }
216 }
217}
218
219pub trait LazyDeviceWeightBinder {
221 type Binding;
222
223 fn bind_block_quantized_moe(
224 &self,
225 weight: &LazyWeight,
226 ) -> Result<Self::Binding, WeightHandleError>;
227}
228
229#[derive(Clone, Copy, Debug, Default)]
231pub struct Phase3aHostOnlyBinder;
232
233impl LazyDeviceWeightBinder for Phase3aHostOnlyBinder {
234 type Binding = ();
235
236 fn bind_block_quantized_moe(
237 &self,
238 _weight: &LazyWeight,
239 ) -> Result<Self::Binding, WeightHandleError> {
240 Err(WeightHandleError::Unsupported(
241 "live device weight paging is deferred to WEIGHT_OFFLOAD Phase 3b".into(),
242 ))
243 }
244}
245
246#[derive(Clone, Debug, thiserror::Error, PartialEq, Eq)]
247pub enum WeightHandleError {
248 #[error("invalid resident weight: {0}")]
249 InvalidResident(String),
250 #[error("lazy weight requires at least one external mmap region")]
251 MissingRegions,
252 #[error("unsupported: {0}")]
253 Unsupported(String),
254}
255
256#[cfg(test)]
257mod tests {
258 use std::sync::atomic::{AtomicUsize, Ordering};
259
260 use super::*;
261
262 fn resident() -> ResidentWeight {
263 ResidentWeight::new(DataType::Uint8, vec![4], vec![1, 2, 3, 4]).unwrap()
264 }
265
266 fn region() -> ExternalMmapRegion {
267 ExternalMmapRegion {
268 mapping_id: 7,
269 offset: 100,
270 len: 4,
271 }
272 }
273
274 fn lazy() -> WeightHandle {
275 WeightHandle::Lazy(
276 LazyWeight::block_quantized_moe(vec![region()], || Ok(resident())).unwrap(),
277 )
278 }
279
280 #[test]
281 fn stock_ep_materializes_the_resident_fallback() {
282 let NegotiatedWeight::Resident(weight) = lazy()
283 .negotiate(&ExecutionProviderCapabilities::stock())
284 .unwrap()
285 else {
286 panic!("stock EP must receive resident materialization");
287 };
288 assert_eq!(weight.bytes(), &[1, 2, 3, 4]);
289 }
290
291 #[test]
292 fn nxrt_capability_preserves_lazy_block_quantized_moe_handle() {
293 let materializations = Arc::new(AtomicUsize::new(0));
294 let counter = Arc::clone(&materializations);
295 let lazy = WeightHandle::Lazy(
296 LazyWeight::block_quantized_moe(vec![region()], move || {
297 counter.fetch_add(1, Ordering::Relaxed);
298 Ok(resident())
299 })
300 .unwrap(),
301 );
302 let NegotiatedWeight::Lazy(weight) = lazy
303 .negotiate(&ExecutionProviderCapabilities::nxrt_weight_paging())
304 .unwrap()
305 else {
306 panic!("nxrt EP must receive lazy weight handle");
307 };
308 assert_eq!(weight.boundary, LazyWeightBoundary::BlockQuantizedMoe);
309 assert_eq!(weight.regions, vec![region()]);
310 assert_eq!(materializations.load(Ordering::Relaxed), 0);
311 assert_eq!(weight.materialize().unwrap().bytes(), &[1, 2, 3, 4]);
312 assert_eq!(materializations.load(Ordering::Relaxed), 1);
313 }
314
315 #[test]
316 fn phase3a_device_binding_is_explicitly_unsupported_with_host_route() {
317 let negotiated = lazy()
318 .negotiate(&ExecutionProviderCapabilities::nxrt_weight_paging())
319 .unwrap();
320 assert_eq!(
321 negotiated.try_bind_device(&Phase3aHostOnlyBinder),
322 Err(WeightHandleError::Unsupported(
323 "live device weight paging is deferred to WEIGHT_OFFLOAD Phase 3b".into()
324 ))
325 );
326 assert_eq!(
327 negotiated.materialize_host_fallback().unwrap().bytes(),
328 &[1, 2, 3, 4]
329 );
330 }
331}