1use std::collections::HashMap;
10use std::fs::File;
11use std::ops::Range;
12use std::path::{Path, PathBuf};
13use std::sync::atomic::{AtomicUsize, Ordering};
14
15use memmap2::Mmap;
16use onnx_runtime_ir::{DataType, TensorData, ValueId, WeightRef};
17
18use crate::proto::onnx::{ModelProto, TensorProto, tensor_proto};
19use crate::{LoaderError, pathsafe::guarded_join};
20
21#[derive(Debug, Default)]
24pub struct WeightStore {
25 pub weights: HashMap<ValueId, WeightRef>,
27 mmaps: HashMap<PathBuf, MappedFile>,
29}
30
31#[derive(Debug)]
32struct MappedFile {
33 id: usize,
34 mmap: Mmap,
35}
36
37static NEXT_MAPPING_ID: AtomicUsize = AtomicUsize::new(1);
38
39#[derive(Clone, Copy, Debug, PartialEq, Eq)]
41pub struct ExpertQuantization {
42 pub bits: usize,
44 pub block_size: usize,
46 pub blocks_per_row: usize,
48}
49
50#[derive(Clone, Copy, Debug, PartialEq, Eq)]
52pub enum ExpertStorageOrder {
53 ExpertMajor,
55 Interleaved,
57}
58
59#[derive(Clone, Debug, PartialEq, Eq)]
61pub struct ExpertTensorLayout {
62 pub version: u32,
64 pub experts: usize,
65 pub rows_per_expert: usize,
66 pub storage_elements_per_row: usize,
68 pub order: ExpertStorageOrder,
69 pub quantization: Option<ExpertQuantization>,
70}
71
72#[derive(Clone, Debug, PartialEq, Eq)]
74pub enum NonPageableReason {
75 InlineTensor,
76 UnsupportedLayoutVersion(u32),
77 NotExpertMajor,
78 ShapeMismatch {
79 expected: Vec<usize>,
80 actual: Vec<usize>,
81 },
82 InvalidQuantization(String),
83 Range(String),
84 ExternalLengthMismatch {
85 expected: usize,
86 actual: usize,
87 },
88}
89
90#[derive(Clone, Debug, PartialEq, Eq)]
93pub enum Pageability {
94 Pageable,
95 NonPageable(NonPageableReason),
96}
97
98#[derive(Clone, Debug, PartialEq, Eq)]
100pub struct ExpertWeightRegion {
101 pub expert: usize,
102 pub offset: usize,
104 pub len: usize,
105}
106
107#[derive(Clone, Debug, PartialEq, Eq)]
113pub struct WeightRegionCatalog {
114 path: Option<PathBuf>,
115 tensor_offset: usize,
116 tensor_len: usize,
117 dtype: DataType,
118 layout: ExpertTensorLayout,
119 regions: Vec<ExpertWeightRegion>,
120 pageability: Pageability,
121}
122
123impl WeightRegionCatalog {
124 pub fn classify(weight: &WeightRef, layout: ExpertTensorLayout) -> Self {
129 let dtype = weight.dtype();
130 let dims = weight.dims();
131 let (path, tensor_offset, tensor_len) = match weight {
132 WeightRef::Inline(tensor) => {
133 return Self::non_pageable(
134 None,
135 0,
136 tensor.data.len(),
137 dtype,
138 layout,
139 NonPageableReason::InlineTensor,
140 );
141 }
142
143 WeightRef::External {
144 path,
145 offset,
146 length,
147 ..
148 } => (Some(path.clone()), *offset, *length),
149 };
150
151 if layout.version != 1 {
152 return Self::non_pageable(
153 path,
154 tensor_offset,
155 tensor_len,
156 dtype,
157 layout.clone(),
158 NonPageableReason::UnsupportedLayoutVersion(layout.version),
159 );
160 }
161 if layout.order != ExpertStorageOrder::ExpertMajor {
162 return Self::non_pageable(
163 path,
164 tensor_offset,
165 tensor_len,
166 dtype,
167 layout,
168 NonPageableReason::NotExpertMajor,
169 );
170 }
171 let expected_shape = vec![
172 layout.experts,
173 layout.rows_per_expert,
174 layout.storage_elements_per_row,
175 ];
176 if dims != expected_shape {
177 return Self::non_pageable(
178 path,
179 tensor_offset,
180 tensor_len,
181 dtype,
182 layout,
183 NonPageableReason::ShapeMismatch {
184 expected: expected_shape,
185 actual: dims.to_vec(),
186 },
187 );
188 }
189 if let Some(quantization) = layout.quantization
190 && (!matches!(quantization.bits, 1 | 2 | 4 | 8)
191 || quantization.block_size == 0
192 || quantization.blocks_per_row == 0)
193 {
194 return Self::non_pageable(
195 path,
196 tensor_offset,
197 tensor_len,
198 dtype,
199 layout,
200 NonPageableReason::InvalidQuantization(format!(
201 "bits={}, block_size={}, blocks_per_row={}",
202 quantization.bits, quantization.block_size, quantization.blocks_per_row
203 )),
204 );
205 }
206
207 let elements_per_expert = match checked_product(
208 &[layout.rows_per_expert, layout.storage_elements_per_row],
209 "per-expert element count",
210 ) {
211 Ok(value) => value,
212 Err(error) => {
213 return Self::non_pageable(
214 path,
215 tensor_offset,
216 tensor_len,
217 dtype,
218 layout,
219 NonPageableReason::Range(error.to_string()),
220 );
221 }
222 };
223 let bytes_per_expert =
224 match checked_storage_byte_count(dtype, elements_per_expert, "per-expert byte count") {
225 Ok(value) => value,
226 Err(error) => {
227 return Self::non_pageable(
228 path,
229 tensor_offset,
230 tensor_len,
231 dtype,
232 layout,
233 NonPageableReason::Range(error.to_string()),
234 );
235 }
236 };
237 let expected_len = match checked_byte_count(
238 layout.experts,
239 bytes_per_expert,
240 "expert tensor byte count",
241 ) {
242 Ok(value) => value,
243 Err(error) => {
244 return Self::non_pageable(
245 path,
246 tensor_offset,
247 tensor_len,
248 dtype,
249 layout,
250 NonPageableReason::Range(error.to_string()),
251 );
252 }
253 };
254 if expected_len != tensor_len {
255 return Self::non_pageable(
256 path,
257 tensor_offset,
258 tensor_len,
259 dtype,
260 layout,
261 NonPageableReason::ExternalLengthMismatch {
262 expected: expected_len,
263 actual: tensor_len,
264 },
265 );
266 }
267 let tensor_end = match tensor_offset.checked_add(tensor_len) {
268 Some(end) if end <= isize::MAX as usize => end,
269 Some(_) => {
270 return Self::non_pageable(
271 path,
272 tensor_offset,
273 tensor_len,
274 dtype,
275 layout,
276 NonPageableReason::Range(
277 "expert tensor absolute endpoint exceeds isize::MAX".into(),
278 ),
279 );
280 }
281 None => {
282 return Self::non_pageable(
283 path,
284 tensor_offset,
285 tensor_len,
286 dtype,
287 layout,
288 NonPageableReason::Range("expert tensor absolute endpoint overflow".into()),
289 );
290 }
291 };
292
293 let mut regions = Vec::new();
294 if let Err(error) = regions.try_reserve_exact(layout.experts) {
295 return Self::non_pageable(
296 path,
297 tensor_offset,
298 tensor_len,
299 dtype,
300 layout,
301 NonPageableReason::Range(format!("expert region allocation failed: {error}")),
302 );
303 }
304 for expert in 0..layout.experts {
305 let relative = match checked_range(expert, bytes_per_expert, "expert byte range") {
306 Ok(value) => value,
307 Err(error) => {
308 return Self::non_pageable(
309 path,
310 tensor_offset,
311 tensor_len,
312 dtype,
313 layout,
314 NonPageableReason::Range(error.to_string()),
315 );
316 }
317 };
318 let offset = match tensor_offset.checked_add(relative.start) {
319 Some(value) => value,
320 None => {
321 return Self::non_pageable(
322 path,
323 tensor_offset,
324 tensor_len,
325 dtype,
326 layout,
327 NonPageableReason::Range("expert absolute offset overflow".into()),
328 );
329 }
330 };
331 let _end = match offset.checked_add(bytes_per_expert) {
332 Some(end) if end <= isize::MAX as usize && end <= tensor_end => end,
333 Some(_) => {
334 return Self::non_pageable(
335 path,
336 tensor_offset,
337 tensor_len,
338 dtype,
339 layout,
340 NonPageableReason::Range(
341 "expert absolute endpoint exceeds validated tensor range".into(),
342 ),
343 );
344 }
345 None => {
346 return Self::non_pageable(
347 path,
348 tensor_offset,
349 tensor_len,
350 dtype,
351 layout,
352 NonPageableReason::Range("expert absolute endpoint overflow".into()),
353 );
354 }
355 };
356 regions.push(ExpertWeightRegion {
357 expert,
358 offset,
359 len: bytes_per_expert,
360 });
361 }
362
363 Self {
364 path,
365 tensor_offset,
366 tensor_len,
367 dtype,
368 layout,
369 regions,
370 pageability: Pageability::Pageable,
371 }
372 }
373
374 pub fn for_mapped_tensor_view(
381 dtype: DataType,
382 dims: &[usize],
383 tensor_len: usize,
384 layout: ExpertTensorLayout,
385 ) -> Self {
386 let synthetic = WeightRef::External {
387 path: PathBuf::new(),
388 offset: 0,
389 length: tensor_len,
390 dtype,
391 dims: dims.to_vec(),
392 };
393 let mut catalog = Self::classify(&synthetic, layout);
394 catalog.path = None;
395 catalog
396 }
397
398 fn non_pageable(
399 path: Option<PathBuf>,
400 tensor_offset: usize,
401 tensor_len: usize,
402 dtype: DataType,
403 layout: ExpertTensorLayout,
404 reason: NonPageableReason,
405 ) -> Self {
406 Self {
407 path,
408 tensor_offset,
409 tensor_len,
410 dtype,
411 layout,
412 regions: Vec::new(),
413 pageability: Pageability::NonPageable(reason),
414 }
415 }
416
417 pub fn pageability(&self) -> &Pageability {
418 &self.pageability
419 }
420
421 pub fn is_pageable(&self) -> bool {
422 matches!(self.pageability, Pageability::Pageable)
423 }
424
425 pub fn layout(&self) -> &ExpertTensorLayout {
426 &self.layout
427 }
428
429 pub fn dtype(&self) -> DataType {
430 self.dtype
431 }
432
433 pub fn mapped_bytes(&self) -> usize {
434 if self.is_pageable() {
435 self.tensor_len
436 } else {
437 0
438 }
439 }
440
441 pub fn region(&self, expert: usize) -> Option<&ExpertWeightRegion> {
442 self.regions.get(expert)
443 }
444
445 pub fn relative_range(&self, expert: usize) -> Option<Range<usize>> {
446 let region = self.region(expert)?;
447 let start = region.offset.checked_sub(self.tensor_offset)?;
448 let end = start.checked_add(region.len)?;
449 Some(start..end)
450 }
451
452 pub fn expert_bytes<'a>(&self, store: &'a WeightStore, expert: usize) -> Option<&'a [u8]> {
454 let path = self.path.as_ref()?;
455 let region = self.region(expert)?;
456 store.external_bytes(path, region.offset, region.len)
457 }
458
459 pub fn tensor_offset(&self) -> usize {
460 self.tensor_offset
461 }
462}
463
464#[derive(Clone, Debug, thiserror::Error, PartialEq, Eq)]
466#[error("{0}")]
467pub struct WeightRangeError(String);
468
469pub fn checked_product(factors: &[usize], context: &str) -> Result<usize, WeightRangeError> {
471 let mut product = 1usize;
472 let mut has_zero = false;
473 for &factor in factors {
474 if factor == 0 {
475 has_zero = true;
476 } else {
477 product = product
478 .checked_mul(factor)
479 .ok_or_else(|| WeightRangeError(format!("{context} overflow")))?;
480 }
481 }
482 Ok(if has_zero { 0 } else { product })
483}
484
485pub fn checked_byte_count(
487 elements: usize,
488 element_size: usize,
489 context: &str,
490) -> Result<usize, WeightRangeError> {
491 let bytes = elements
492 .checked_mul(element_size)
493 .ok_or_else(|| WeightRangeError(format!("{context} overflow")))?;
494 if bytes > isize::MAX as usize {
495 return Err(WeightRangeError(format!("{context} exceeds isize::MAX")));
496 }
497 Ok(bytes)
498}
499
500pub fn checked_storage_byte_count(
502 dtype: DataType,
503 elements: usize,
504 context: &str,
505) -> Result<usize, WeightRangeError> {
506 let bytes = dtype
507 .checked_storage_bytes(elements)
508 .ok_or_else(|| WeightRangeError(format!("{context} overflow")))?;
509 if bytes > isize::MAX as usize {
510 return Err(WeightRangeError(format!("{context} exceeds isize::MAX")));
511 }
512 Ok(bytes)
513}
514
515pub fn checked_range(
517 index: usize,
518 width: usize,
519 context: &str,
520) -> Result<Range<usize>, WeightRangeError> {
521 let start = index
522 .checked_mul(width)
523 .ok_or_else(|| WeightRangeError(format!("{context} start offset overflow")))?;
524 let end = start
525 .checked_add(width)
526 .ok_or_else(|| WeightRangeError(format!("{context} end offset overflow")))?;
527 if end > isize::MAX as usize {
528 return Err(WeightRangeError(format!("{context} exceeds isize::MAX")));
529 }
530 Ok(start..end)
531}
532
533impl WeightStore {
534 pub fn new() -> Self {
536 Self::default()
537 }
538
539 pub fn map_external(&mut self, path: impl AsRef<Path>) -> Result<(), LoaderError> {
549 self.mmap_file(path.as_ref())
550 }
551
552 pub fn bytes<'a>(&'a self, weight: &'a WeightRef) -> Option<&'a [u8]> {
559 match weight {
560 WeightRef::Inline(t) => Some(&t.data),
561 WeightRef::External {
562 path,
563 offset,
564 length,
565 ..
566 } => {
567 let mmap = self.mmaps.get(path)?;
568 mmap.mmap.get(*offset..offset.checked_add(*length)?)
569 }
570 }
571 }
572
573 pub fn external_mmap_provenance(&self, weight: &WeightRef) -> Option<(usize, usize, usize)> {
575 let WeightRef::External {
576 path,
577 offset,
578 length,
579 ..
580 } = weight
581 else {
582 return None;
583 };
584 let mmap = self.mmaps.get(path)?;
585 let end = offset.checked_add(*length)?;
586 if end > mmap.mmap.len() || end > isize::MAX as usize {
587 return None;
588 }
589 Some((mmap.id, *offset, *length))
590 }
591
592 fn external_bytes(&self, path: &Path, offset: usize, length: usize) -> Option<&[u8]> {
593 let mmap = self.mmaps.get(path)?;
594 mmap.mmap.get(offset..offset.checked_add(length)?)
595 }
596
597 fn mmap_file(&mut self, path: &Path) -> Result<(), LoaderError> {
598 if self.mmaps.contains_key(path) {
599 return Ok(());
600 }
601 let file = File::open(path).map_err(|_| LoaderError::ExternalDataNotFound {
602 path: path.to_path_buf(),
603 })?;
604 let mmap = unsafe { Mmap::map(&file) }.map_err(|e| LoaderError::Mmap(e.to_string()))?;
609 let id = NEXT_MAPPING_ID
610 .fetch_update(Ordering::Relaxed, Ordering::Relaxed, |id| id.checked_add(1))
611 .map_err(|_| LoaderError::Mmap("external mmap identity space exhausted".into()))?;
612 self.mmaps
613 .insert(path.to_path_buf(), MappedFile { id, mmap });
614 Ok(())
615 }
616}
617
618pub fn load_weights(
622 model: &ModelProto,
623 model_dir: &Path,
624 name_map: &HashMap<String, ValueId>,
625) -> Result<WeightStore, LoaderError> {
626 let mut store = WeightStore::new();
627 let Some(graph) = model.graph.as_ref() else {
628 return Ok(store);
629 };
630
631 for init in &graph.initializer {
632 let Some(&vid) = name_map.get(&init.name) else {
633 continue;
637 };
638 let weight = resolve_initializer(&mut store, init, model_dir)?;
639 store.weights.insert(vid, weight);
640 }
641
642 Ok(store)
643}
644
645fn resolve_initializer(
646 store: &mut WeightStore,
647 init: &TensorProto,
648 model_dir: &Path,
649) -> Result<WeightRef, LoaderError> {
650 let dtype =
651 DataType::from_onnx(init.data_type).ok_or_else(|| LoaderError::UnsupportedDataType {
652 raw: init.data_type,
653 context: format!("initializer {:?}", init.name),
654 })?;
655 let dims: Vec<usize> = init.dims.iter().map(|&d| d.max(0) as usize).collect();
656
657 if init.data_location == tensor_proto::DataLocation::External as i32 {
658 let mut location = None;
659 let mut offset: usize = 0;
660 let mut length: Option<usize> = None;
661 for kv in &init.external_data {
662 match kv.key.as_str() {
663 "location" => location = Some(kv.value.clone()),
664 "offset" => offset = kv.value.parse().unwrap_or(0),
665 "length" => length = kv.value.parse().ok(),
666 _ => {}
667 }
668 }
669 let location = location.ok_or_else(|| {
670 LoaderError::GraphBuild(format!(
671 "external initializer {:?} missing 'location'",
672 init.name
673 ))
674 })?;
675 let path = resolve_external_path(model_dir, &location)?;
676 store.mmap_file(&path)?;
677 let numel: usize = dims.iter().product();
678 let length = length.unwrap_or_else(|| dtype.storage_bytes(numel));
679 if let Some(mmap) = store.mmaps.get(&path) {
682 let end = offset.checked_add(length);
683 if end.is_none_or(|e| e > mmap.mmap.len()) {
684 return Err(LoaderError::Mmap(format!(
685 "external initializer {:?}: window [{offset}, {:?}) exceeds file {} ({} bytes)",
686 init.name,
687 end,
688 path.display(),
689 mmap.mmap.len()
690 )));
691 }
692 }
693 Ok(WeightRef::External {
694 path,
695 offset,
696 length,
697 dtype,
698 dims,
699 })
700 } else {
701 let data = tensor_data_from_proto(init, dtype, &dims)?;
702 Ok(WeightRef::Inline(data))
703 }
704}
705
706fn resolve_external_path(model_dir: &Path, location: &str) -> Result<PathBuf, LoaderError> {
709 guarded_join(model_dir, location).map_err(|reason| LoaderError::ExternalDataPath {
710 path: location.to_string(),
711 reason,
712 })
713}
714
715pub(crate) fn tensor_data_from_proto(
718 proto: &TensorProto,
719 dtype: DataType,
720 dims: &[usize],
721) -> Result<TensorData, LoaderError> {
722 let mut td = TensorData::from_raw(dtype, dims.to_vec(), Vec::new());
723 if !proto.name.is_empty() {
724 td.name = Some(proto.name.clone());
725 }
726
727 if dtype == DataType::String {
728 td.strings = proto
729 .string_data
730 .iter()
731 .map(|b| String::from_utf8_lossy(b).into_owned())
732 .collect();
733 return Ok(td);
734 }
735
736 if !proto.raw_data.is_empty() {
738 td.data = proto.raw_data.clone();
739 return Ok(td);
740 }
741
742 td.data = match dtype {
744 DataType::Undefined => {
745 return Err(LoaderError::UnsupportedDataType {
746 raw: 0,
747 context: format!("tensor {:?}", proto.name),
748 });
749 }
750 DataType::Float32 => proto
751 .float_data
752 .iter()
753 .flat_map(|v| v.to_le_bytes())
754 .collect(),
755 DataType::Float64 => proto
756 .double_data
757 .iter()
758 .flat_map(|v| v.to_le_bytes())
759 .collect(),
760 DataType::Complex64 => proto
761 .float_data
762 .iter()
763 .flat_map(|v| v.to_le_bytes())
764 .collect(),
765 DataType::Complex128 => proto
766 .double_data
767 .iter()
768 .flat_map(|v| v.to_le_bytes())
769 .collect(),
770 DataType::Int64 => proto
771 .int64_data
772 .iter()
773 .flat_map(|v| v.to_le_bytes())
774 .collect(),
775 DataType::Uint64 | DataType::Uint32 => proto
776 .uint64_data
777 .iter()
778 .flat_map(|v| match dtype {
779 DataType::Uint32 => (*v as u32).to_le_bytes().to_vec(),
780 _ => v.to_le_bytes().to_vec(),
781 })
782 .collect(),
783 DataType::Int32 => proto
784 .int32_data
785 .iter()
786 .flat_map(|v| v.to_le_bytes())
787 .collect(),
788 DataType::Int16 | DataType::Uint16 | DataType::Float16 | DataType::BFloat16 => proto
790 .int32_data
791 .iter()
792 .flat_map(|v| (*v as u16).to_le_bytes())
793 .collect(),
794 DataType::Int8 | DataType::Uint8 | DataType::Bool => {
795 proto.int32_data.iter().map(|v| *v as u8).collect()
796 }
797 DataType::Float8E4M3FN
798 | DataType::Float8E4M3FNUZ
799 | DataType::Float8E5M2
800 | DataType::Float8E5M2FNUZ
801 | DataType::Float8E8M0
802 | DataType::Int4
803 | DataType::Uint4
804 | DataType::Float4E2M1
805 | DataType::Int2
806 | DataType::Uint2 => proto.int32_data.iter().map(|v| *v as u8).collect(),
807 DataType::String => unreachable!("STRING tensors returned above"),
808 };
809 Ok(td)
810}
811
812#[cfg(test)]
813mod tests {
814 use super::*;
815 use std::time::{SystemTime, UNIX_EPOCH};
816
817 #[test]
818 fn float8_typed_data_preserves_each_byte() {
819 let proto = TensorProto {
820 data_type: DataType::Float8E4M3FN.to_onnx(),
821 dims: vec![3],
822 int32_data: vec![0x01, 0x7f, 0xff],
823 ..Default::default()
824 };
825
826 let data =
827 tensor_data_from_proto(&proto, DataType::Float8E4M3FN, &[3]).expect("tensor data");
828 assert_eq!(data.data, [0x01, 0x7f, 0xff]);
829 }
830
831 #[test]
832 fn four_bit_typed_data_preserves_packed_nibbles() {
833 let proto = TensorProto {
834 data_type: DataType::Int4.to_onnx(),
835 dims: vec![3],
836 int32_data: vec![0x21, 0x03],
839 ..Default::default()
840 };
841
842 let data = tensor_data_from_proto(&proto, DataType::Int4, &[3]).expect("tensor data");
843 assert_eq!(data.data, [0x21, 0x03]);
844 }
845
846 #[test]
847 fn two_bit_typed_data_preserves_four_packed_elements_per_byte() {
848 let proto = TensorProto {
849 data_type: DataType::Int2.to_onnx(),
850 dims: vec![5],
851 int32_data: vec![0b11_10_01_00, 0b0000_0001],
853 ..Default::default()
854 };
855
856 let data = tensor_data_from_proto(&proto, DataType::Int2, &[5]).expect("tensor data");
857 assert_eq!(data.data, [0b11_10_01_00, 0b0000_0001]);
858 assert_eq!(data.data.len(), DataType::Int2.storage_bytes(5));
859 }
860
861 #[test]
862 fn float8e8m0_typed_data_preserves_each_byte() {
863 let proto = TensorProto {
864 data_type: DataType::Float8E8M0.to_onnx(),
865 dims: vec![2],
866 int32_data: vec![0x7f, 0xff],
867 ..Default::default()
868 };
869
870 let data = tensor_data_from_proto(&proto, DataType::Float8E8M0, &[2]).expect("tensor data");
871 assert_eq!(data.data, [0x7f, 0xff]);
872 }
873
874 fn external_weight(path: PathBuf, length: usize, dims: Vec<usize>) -> WeightRef {
875 WeightRef::External {
876 path,
877 offset: 16,
878 length,
879 dtype: DataType::Uint8,
880 dims,
881 }
882 }
883
884 fn expert_layout(order: ExpertStorageOrder) -> ExpertTensorLayout {
885 ExpertTensorLayout {
886 version: 1,
887 experts: 3,
888 rows_per_expert: 2,
889 storage_elements_per_row: 4,
890 order,
891 quantization: Some(ExpertQuantization {
892 bits: 4,
893 block_size: 16,
894 blocks_per_row: 1,
895 }),
896 }
897 }
898
899 #[test]
900 fn expert_major_external_tensor_catalogs_contiguous_ranges() {
901 let stamp = SystemTime::now()
902 .duration_since(UNIX_EPOCH)
903 .expect("clock")
904 .as_nanos();
905 let path = std::env::current_dir()
906 .expect("cwd")
907 .join("target")
908 .join(format!(
909 "weight-region-catalog-{}-{stamp}.bin",
910 std::process::id()
911 ));
912 std::fs::create_dir_all(path.parent().expect("parent")).expect("create target");
913 let mut bytes = vec![0u8; 40];
914 for (index, byte) in bytes.iter_mut().enumerate() {
915 *byte = index as u8;
916 }
917 std::fs::write(&path, &bytes).expect("write external data");
918
919 let weight = external_weight(path.clone(), 24, vec![3, 2, 4]);
920 let catalog =
921 WeightRegionCatalog::classify(&weight, expert_layout(ExpertStorageOrder::ExpertMajor));
922 assert_eq!(catalog.pageability(), &Pageability::Pageable);
923 assert_eq!(catalog.mapped_bytes(), 24);
924 assert_eq!(
925 catalog.region(1),
926 Some(&ExpertWeightRegion {
927 expert: 1,
928 offset: 24,
929 len: 8,
930 })
931 );
932
933 let mut store = WeightStore::new();
934 store.map_external(&path).expect("map external data");
935 assert_eq!(catalog.expert_bytes(&store, 1), Some(&bytes[24..32]));
936 drop(store);
937 std::fs::remove_file(path).expect("remove external data");
938 }
939
940 #[test]
941 fn interleaved_external_tensor_is_non_pageable_without_error() {
942 let weight = external_weight(PathBuf::from("weights.bin"), 24, vec![3, 2, 4]);
943 let catalog =
944 WeightRegionCatalog::classify(&weight, expert_layout(ExpertStorageOrder::Interleaved));
945 assert_eq!(
946 catalog.pageability(),
947 &Pageability::NonPageable(NonPageableReason::NotExpertMajor)
948 );
949 assert!(catalog.region(0).is_none());
950 }
951
952 #[test]
953 fn catalog_range_math_rejects_zero_masked_overflow_and_isize_excess() {
954 let overflow = ExpertTensorLayout {
955 version: 1,
956 experts: 0,
957 rows_per_expert: usize::MAX,
958 storage_elements_per_row: 2,
959 order: ExpertStorageOrder::ExpertMajor,
960 quantization: None,
961 };
962 let weight = external_weight(PathBuf::from("weights.bin"), 0, vec![0, usize::MAX, 2]);
963 assert!(matches!(
964 WeightRegionCatalog::classify(&weight, overflow).pageability(),
965 Pageability::NonPageable(NonPageableReason::Range(message))
966 if message.contains("overflow")
967 ));
968
969 assert!(
970 checked_range(0, isize::MAX as usize + 1, "range")
971 .unwrap_err()
972 .to_string()
973 .contains("isize::MAX")
974 );
975
976 let endpoint = WeightRef::External {
977 path: PathBuf::from("weights.bin"),
978 offset: isize::MAX as usize,
979 length: 1,
980 dtype: DataType::Uint8,
981 dims: vec![1, 1, 1],
982 };
983 let layout = ExpertTensorLayout {
984 version: 1,
985 experts: 1,
986 rows_per_expert: 1,
987 storage_elements_per_row: 1,
988 order: ExpertStorageOrder::ExpertMajor,
989 quantization: None,
990 };
991 assert!(matches!(
992 WeightRegionCatalog::classify(&endpoint, layout).pageability(),
993 Pageability::NonPageable(NonPageableReason::Range(message))
994 if message.contains("endpoint")
995 ));
996 }
997}