1use crate::error::{GGUFError, Result};
4use crate::tensor::{TensorData, TensorInfo};
5use std::io::{Read, Seek, SeekFrom};
6
7const MAX_READ_CHUNK_SIZE: usize = 1024 * 1024;
8
9#[derive(Debug)]
11pub struct TensorReader<R> {
12 reader: R,
14 position: u64,
16}
17
18#[derive(Debug, Clone)]
20pub struct TensorReadOptions {
21 pub validate_alignment: bool,
23 pub compute_checksum: bool,
28 pub decompress_quantized: bool,
30 pub max_tensor_size: usize,
32 pub buffer_size: usize,
34}
35
36impl Default for TensorReadOptions {
37 fn default() -> Self {
38 Self {
39 validate_alignment: true,
40 compute_checksum: false,
41 decompress_quantized: false, max_tensor_size: 0,
43 buffer_size: 1024 * 1024, }
45 }
46}
47
48#[derive(Debug, Clone)]
50pub struct TensorReadResult {
51 pub data: TensorData,
53 pub bytes_read: usize,
55 pub was_decompressed: bool,
57 pub checksum: Option<u32>,
59}
60
61impl<R: Read> TensorReader<R> {
62 pub fn new(reader: R) -> Self {
64 Self { reader, position: 0 }
65 }
66
67 pub fn read_tensor_data(&mut self, tensor_info: &TensorInfo) -> Result<TensorReadResult> {
69 self.read_tensor_data_with_options(tensor_info, &TensorReadOptions::default())
70 }
71
72 pub fn read_tensor_data_with_options(
74 &mut self,
75 tensor_info: &TensorInfo,
76 options: &TensorReadOptions,
77 ) -> Result<TensorReadResult> {
78 let expected_size =
79 usize::try_from(tensor_info.checked_expected_data_size()?).map_err(|_| {
80 GGUFError::InvalidTensorData(format!(
81 "Tensor '{}' size does not fit this platform",
82 tensor_info.name()
83 ))
84 })?;
85
86 if options.max_tensor_size > 0 && expected_size > options.max_tensor_size {
88 return Err(GGUFError::InvalidTensorData(format!(
89 "Tensor '{}' size {} exceeds maximum {}",
90 tensor_info.name(),
91 expected_size,
92 options.max_tensor_size
93 )));
94 }
95
96 if options.validate_alignment {
99 self.validate_tensor_alignment(tensor_info)?;
100 }
101 if options.decompress_quantized && tensor_info.tensor_type().is_quantized() {
102 return Err(GGUFError::FeatureUnavailable(format!(
103 "decompression for {} tensors",
104 tensor_info.tensor_type()
105 )));
106 }
107
108 let data = self.read_exact_owned_tracking(expected_size, options.buffer_size)?;
111
112 let tensor_data = TensorData::new_owned(data);
113 let checksum = options.compute_checksum.then(|| tensor_data.checksum());
114
115 Ok(TensorReadResult {
116 data: tensor_data,
117 bytes_read: expected_size,
118 was_decompressed: false,
119 checksum,
120 })
121 }
122
123 pub fn read_multiple_tensors(
125 &mut self,
126 tensor_infos: &[&TensorInfo],
127 options: &TensorReadOptions,
128 ) -> Result<Vec<TensorReadResult>> {
129 let mut results = Vec::new();
130 results.try_reserve_exact(tensor_infos.len()).map_err(|_| {
131 GGUFError::InvalidTensorData("Unable to allocate tensor read results".to_string())
132 })?;
133
134 for tensor_info in tensor_infos {
135 let result = self.read_tensor_data_with_options(tensor_info, options)?;
136 results.push(result);
137 }
138
139 Ok(results)
140 }
141
142 pub fn read_tensor_chunked(
144 &mut self,
145 tensor_info: &TensorInfo,
146 chunk_size: usize,
147 mut callback: impl FnMut(&[u8]) -> Result<()>,
148 ) -> Result<()> {
149 let total_size =
150 usize::try_from(tensor_info.checked_expected_data_size()?).map_err(|_| {
151 GGUFError::InvalidTensorData(format!(
152 "Tensor '{}' size does not fit this platform",
153 tensor_info.name()
154 ))
155 })?;
156 if total_size > 0 && chunk_size == 0 {
157 return Err(GGUFError::InvalidTensorData(
158 "Tensor read chunk size must be greater than zero".to_string(),
159 ));
160 }
161 let mut remaining = total_size;
162 let buffer_size = chunk_size.min(remaining).min(MAX_READ_CHUNK_SIZE);
163 let mut buffer = Vec::new();
164 buffer.try_reserve_exact(buffer_size).map_err(|_| {
165 GGUFError::InvalidTensorData("Unable to allocate tensor read chunk".to_string())
166 })?;
167 buffer.resize(buffer_size, 0);
168
169 while remaining > 0 {
170 let to_read = buffer.len().min(remaining);
171 self.read_exact_tracking(&mut buffer[..to_read])?;
172
173 callback(&buffer[..to_read])?;
174 remaining -= to_read;
175 }
176
177 Ok(())
178 }
179
180 fn validate_tensor_alignment(&self, tensor_info: &TensorInfo) -> Result<()> {
182 const MIN_GGUF_ALIGNMENT: u64 = 8;
186 if !tensor_info.data_offset().is_multiple_of(MIN_GGUF_ALIGNMENT) {
187 return Err(GGUFError::InvalidTensorData(format!(
188 "Tensor '{}' offset is not aligned to at least {} bytes",
189 tensor_info.name(),
190 MIN_GGUF_ALIGNMENT
191 )));
192 }
193
194 Ok(())
195 }
196
197 pub fn position(&self) -> u64 {
199 self.position
200 }
201
202 pub fn reset_position(&mut self) {
204 self.position = 0;
205 }
206
207 pub fn skip_bytes(&mut self, count: usize) -> Result<()> {
209 let mut buffer = [0u8; 8192];
210 let mut remaining = count;
211
212 while remaining > 0 {
213 let to_skip = remaining.min(buffer.len());
214 self.read_exact_tracking(&mut buffer[..to_skip])?;
215 remaining -= to_skip;
216 }
217
218 Ok(())
219 }
220
221 fn read_exact_owned_tracking(&mut self, size: usize, buffer_size: usize) -> Result<Vec<u8>> {
222 if size == 0 {
223 return Ok(Vec::new());
224 }
225 if buffer_size == 0 {
226 return Err(GGUFError::InvalidTensorData(
227 "Tensor read buffer size must be greater than zero".to_string(),
228 ));
229 }
230
231 let chunk_size = buffer_size.min(size).min(MAX_READ_CHUNK_SIZE);
232 let mut chunk = Vec::new();
233 chunk.try_reserve_exact(chunk_size).map_err(|_| {
234 GGUFError::InvalidTensorData("Unable to allocate tensor read chunk".to_string())
235 })?;
236 chunk.resize(chunk_size, 0);
237 let mut data = Vec::new();
238 let mut remaining = size;
239 while remaining > 0 {
240 let to_read = remaining.min(chunk.len());
241 self.read_exact_tracking(&mut chunk[..to_read])?;
242 data.try_reserve(to_read).map_err(|_| {
243 GGUFError::InvalidTensorData("Unable to allocate tensor data buffer".to_string())
244 })?;
245 data.extend_from_slice(&chunk[..to_read]);
246 remaining -= to_read;
247 }
248 Ok(data)
249 }
250
251 fn read_exact_tracking(&mut self, mut buffer: &mut [u8]) -> Result<()> {
252 while !buffer.is_empty() {
253 match self.reader.read(buffer) {
254 Ok(0) => return Err(GGUFError::UnexpectedEof),
255 Ok(bytes_read) => {
256 self.advance(bytes_read)?;
257 let (_, remaining) = buffer.split_at_mut(bytes_read);
258 buffer = remaining;
259 }
260 Err(error) if error.kind() == std::io::ErrorKind::Interrupted => {}
261 Err(error) => return Err(error.into()),
262 }
263 }
264 Ok(())
265 }
266
267 fn advance(&mut self, bytes: usize) -> Result<()> {
268 let bytes = u64::try_from(bytes)
269 .map_err(|_| GGUFError::InvalidTensorData("Read size does not fit u64".to_string()))?;
270 self.position = self.position.checked_add(bytes).ok_or_else(|| {
271 GGUFError::InvalidTensorData("Tensor reader position overflows u64".to_string())
272 })?;
273 Ok(())
274 }
275}
276
277impl<R: Read + Seek> TensorReader<R> {
278 pub fn with_seek(reader: R) -> Self {
280 Self { reader, position: 0 }
281 }
282
283 pub fn seek(&mut self, pos: SeekFrom) -> Result<u64> {
285 let new_pos = self.reader.seek(pos)?;
286 self.position = new_pos;
287 Ok(new_pos)
288 }
289
290 pub fn read_tensor_at_offset(
292 &mut self,
293 tensor_info: &TensorInfo,
294 offset: u64,
295 options: &TensorReadOptions,
296 ) -> Result<TensorReadResult> {
297 self.seek(SeekFrom::Start(offset))?;
298 self.read_tensor_data_with_options(tensor_info, options)
299 }
300
301 pub fn read_tensors_by_offset(
303 &mut self,
304 tensors: &[(u64, &TensorInfo)], options: &TensorReadOptions,
306 ) -> Result<Vec<TensorReadResult>> {
307 let mut results = Vec::new();
308 results.try_reserve_exact(tensors.len()).map_err(|_| {
309 GGUFError::InvalidTensorData("Unable to allocate tensor read results".to_string())
310 })?;
311
312 for &(offset, tensor_info) in tensors {
313 let result = self.read_tensor_at_offset(tensor_info, offset, options)?;
314 results.push(result);
315 }
316
317 Ok(results)
318 }
319}
320
321pub struct TensorReadUtils;
323
324impl TensorReadUtils {
325 pub fn optimal_buffer_size(tensor_info: &TensorInfo) -> usize {
327 let tensor_size = tensor_info
328 .checked_expected_data_size()
329 .ok()
330 .and_then(|size| usize::try_from(size).ok())
331 .unwrap_or(usize::MAX);
332
333 let base_size = match tensor_size {
335 0..=4096 => 4096, 4097..=65_536 => 16_384, 65_537..=1_048_576 => 65_536, _ => 262_144, };
340
341 base_size.min(tensor_size)
342 }
343
344 pub fn should_read_chunked(tensor_info: &TensorInfo, chunk_threshold: usize) -> bool {
346 tensor_info
347 .checked_expected_data_size()
348 .ok()
349 .and_then(|size| usize::try_from(size).ok())
350 .is_none_or(|size| size > chunk_threshold)
351 }
352
353 pub fn calculate_memory_requirements(tensor_infos: &[&TensorInfo]) -> TensorMemoryRequirements {
355 let mut total_size = 0u64;
356 let mut max_tensor_size = 0u64;
357 let mut quantized_count = 0;
358 let mut non_quantized_count = 0;
359
360 for tensor_info in tensor_infos {
361 let size = tensor_info.expected_data_size();
362 total_size = total_size.saturating_add(size);
363 max_tensor_size = max_tensor_size.max(size);
364
365 if tensor_info.tensor_type().is_quantized() {
366 quantized_count += 1;
367 } else {
368 non_quantized_count += 1;
369 }
370 }
371
372 TensorMemoryRequirements {
373 total_size: usize::try_from(total_size).unwrap_or(usize::MAX),
374 max_tensor_size: usize::try_from(max_tensor_size).unwrap_or(usize::MAX),
375 tensor_count: tensor_infos.len(),
376 quantized_tensor_count: quantized_count,
377 non_quantized_tensor_count: non_quantized_count,
378 recommended_buffer_size: tensor_infos
379 .iter()
380 .max_by_key(|tensor| tensor.expected_data_size())
381 .map_or(0, |tensor| Self::optimal_buffer_size(tensor)),
382 }
383 }
384}
385
386#[derive(Debug, Clone, PartialEq, Eq)]
388pub struct TensorMemoryRequirements {
389 pub total_size: usize,
391 pub max_tensor_size: usize,
393 pub tensor_count: usize,
395 pub quantized_tensor_count: usize,
397 pub non_quantized_tensor_count: usize,
399 pub recommended_buffer_size: usize,
401}
402
403impl TensorMemoryRequirements {
404 pub fn average_tensor_size(&self) -> usize {
406 self.total_size.checked_div(self.tensor_count).unwrap_or(0)
407 }
408
409 pub fn is_reasonable(&self, available_memory: usize) -> bool {
411 self.total_size <= available_memory.saturating_mul(4) / 5
413 }
414}
415
416impl std::fmt::Display for TensorReadResult {
417 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
418 write!(
419 f,
420 "TensorReadResult {{ bytes: {}, decompressed: {}{}}}",
421 self.bytes_read,
422 self.was_decompressed,
423 if let Some(checksum) = self.checksum {
424 format!(", checksum: 0x{:08x}", checksum)
425 } else {
426 String::new()
427 }
428 )
429 }
430}
431
432impl std::fmt::Display for TensorMemoryRequirements {
433 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
434 write!(
435 f,
436 "TensorMemoryRequirements {{ total: {} bytes, max: {} bytes, count: {} ({} quantized, {} non-quantized), avg: {} bytes }}",
437 self.total_size,
438 self.max_tensor_size,
439 self.tensor_count,
440 self.quantized_tensor_count,
441 self.non_quantized_tensor_count,
442 self.average_tensor_size()
443 )
444 }
445}
446
447#[cfg(all(test, feature = "std"))]
448mod tests {
449 use super::*;
450 use crate::tensor::{TensorShape, TensorType};
451 use std::io::Cursor;
452
453 fn create_test_tensor_info(name: &str, shape: Vec<u64>, tensor_type: TensorType) -> TensorInfo {
454 let shape = TensorShape::new(shape).unwrap();
455 TensorInfo::new(name.to_string(), shape, tensor_type, 0)
456 }
457
458 #[test]
459 fn test_tensor_reader_creation() {
460 let data = vec![1, 2, 3, 4, 5, 6, 7, 8];
461 let cursor = Cursor::new(data);
462 let reader = TensorReader::new(cursor);
463
464 assert_eq!(reader.position(), 0);
465 }
466
467 #[test]
468 fn test_read_tensor_data() {
469 let data = vec![0u8; 16]; let cursor = Cursor::new(data);
471 let mut reader = TensorReader::new(cursor);
472
473 let tensor_info = create_test_tensor_info("test", vec![4], TensorType::F32);
474 let options = TensorReadOptions {
475 validate_alignment: false, ..Default::default()
477 };
478 let result = reader.read_tensor_data_with_options(&tensor_info, &options).unwrap();
479
480 assert_eq!(result.bytes_read, 16);
481 assert_eq!(result.data.len(), 16);
482 assert!(!result.was_decompressed);
483 }
484
485 #[test]
486 fn test_read_tensor_with_options() {
487 let data = vec![0u8; 8]; let cursor = Cursor::new(data);
489 let mut reader = TensorReader::new(cursor);
490
491 let tensor_info = create_test_tensor_info("test", vec![2], TensorType::F32);
492 let options = TensorReadOptions {
493 compute_checksum: true,
494 validate_alignment: false, ..Default::default()
496 };
497
498 let result = reader.read_tensor_data_with_options(&tensor_info, &options).unwrap();
499 assert!(result.checksum.is_some());
500 }
501
502 #[test]
503 fn test_read_multiple_tensors() {
504 let data = vec![0u8; 24]; let cursor = Cursor::new(data);
506 let mut reader = TensorReader::new(cursor);
507
508 let tensor1 = create_test_tensor_info("tensor1", vec![4], TensorType::F32);
509 let tensor2 = create_test_tensor_info("tensor2", vec![2], TensorType::F32);
510 let tensor_infos = vec![&tensor1, &tensor2];
511
512 let options = TensorReadOptions {
513 validate_alignment: false, ..Default::default()
515 };
516 let results = reader.read_multiple_tensors(&tensor_infos, &options).unwrap();
517 assert_eq!(results.len(), 2);
518 assert_eq!(results[0].bytes_read, 16);
519 assert_eq!(results[1].bytes_read, 8);
520 }
521
522 #[test]
523 fn test_read_tensor_chunked() {
524 let data = vec![0u8; 16];
525 let cursor = Cursor::new(data);
526 let mut reader = TensorReader::new(cursor);
527
528 let tensor_info = create_test_tensor_info("test", vec![4], TensorType::F32);
529 let mut chunks = Vec::new();
530
531 reader
532 .read_tensor_chunked(&tensor_info, 8, |chunk| {
533 chunks.push(chunk.to_vec());
534 Ok(())
535 })
536 .unwrap();
537
538 assert_eq!(chunks.len(), 2);
539 assert_eq!(chunks[0].len(), 8);
540 assert_eq!(chunks[1].len(), 8);
541 }
542
543 #[test]
544 fn test_zero_sized_read_buffers_are_rejected() {
545 let tensor_info = create_test_tensor_info("test", vec![1], TensorType::F32);
546 let mut reader = TensorReader::new(Cursor::new(vec![0u8; 4]));
547 let options =
548 TensorReadOptions { validate_alignment: false, buffer_size: 0, ..Default::default() };
549 assert!(reader.read_tensor_data_with_options(&tensor_info, &options).is_err());
550
551 let mut reader = TensorReader::new(Cursor::new(vec![0u8; 4]));
552 assert!(reader.read_tensor_chunked(&tensor_info, 0, |_| Ok(())).is_err());
553 }
554
555 #[test]
556 fn test_unimplemented_decompression_is_reported() {
557 let tensor_info = create_test_tensor_info("test", vec![32], TensorType::Q4_0);
558 let mut reader = TensorReader::new(Cursor::new(vec![0u8; 18]));
559 let options = TensorReadOptions {
560 validate_alignment: false,
561 decompress_quantized: true,
562 ..Default::default()
563 };
564 assert!(matches!(
565 reader.read_tensor_data_with_options(&tensor_info, &options),
566 Err(GGUFError::FeatureUnavailable(_))
567 ));
568 assert_eq!(reader.position(), 0);
569 }
570
571 #[test]
572 fn invalid_alignment_is_rejected_before_reading() {
573 let mut tensor_info = create_test_tensor_info("test", vec![1], TensorType::F32);
574 tensor_info.data_offset = 1;
575 let mut reader = TensorReader::new(Cursor::new(vec![0u8; 4]));
576
577 assert!(reader.read_tensor_data(&tensor_info).is_err());
578 assert_eq!(reader.position(), 0);
579 }
580
581 #[test]
582 fn test_size_limit() {
583 let data = vec![0u8; 16];
584 let cursor = Cursor::new(data);
585 let mut reader = TensorReader::new(cursor);
586
587 let tensor_info = create_test_tensor_info("test", vec![4], TensorType::F32);
588 let options = TensorReadOptions {
589 max_tensor_size: 8, ..Default::default()
591 };
592
593 let result = reader.read_tensor_data_with_options(&tensor_info, &options);
594 assert!(result.is_err());
595 }
596
597 #[test]
598 fn test_tensor_read_utils() {
599 let small_tensor = create_test_tensor_info("small", vec![10], TensorType::F32);
600 let large_tensor = create_test_tensor_info("large", vec![100000], TensorType::F32);
601
602 let small_buffer = TensorReadUtils::optimal_buffer_size(&small_tensor);
603 let large_buffer = TensorReadUtils::optimal_buffer_size(&large_tensor);
604
605 assert!(small_buffer <= large_buffer);
606
607 assert!(!TensorReadUtils::should_read_chunked(&small_tensor, 1000));
608 assert!(TensorReadUtils::should_read_chunked(&large_tensor, 1000));
609 }
610
611 #[test]
612 fn test_memory_requirements() {
613 let tensor1 = create_test_tensor_info("t1", vec![100], TensorType::F32);
614 let tensor2 = create_test_tensor_info("t2", vec![200], TensorType::Q4_0);
615 let tensors = vec![&tensor1, &tensor2];
616
617 let req = TensorReadUtils::calculate_memory_requirements(&tensors);
618 assert_eq!(req.tensor_count, 2);
619 assert_eq!(req.quantized_tensor_count, 1);
620 assert_eq!(req.non_quantized_tensor_count, 1);
621 assert!(req.total_size > 0);
622 assert!(req.max_tensor_size >= req.average_tensor_size());
623 }
624
625 #[test]
626 fn test_seeking_reader() {
627 let data = vec![0u8; 32];
628 let cursor = Cursor::new(data);
629 let mut reader = TensorReader::with_seek(cursor);
630
631 let pos = reader.seek(SeekFrom::Start(16)).unwrap();
633 assert_eq!(pos, 16);
634 assert_eq!(reader.position(), 16);
635 }
636
637 #[test]
638 fn test_skip_bytes() {
639 let data = vec![0u8; 32];
640 let cursor = Cursor::new(data);
641 let mut reader = TensorReader::new(cursor);
642
643 reader.skip_bytes(10).unwrap();
644 assert_eq!(reader.position(), 10);
645
646 reader.skip_bytes(5).unwrap();
647 assert_eq!(reader.position(), 15);
648 }
649
650 #[test]
651 fn test_display_implementations() {
652 let result = TensorReadResult {
653 data: TensorData::new_owned(vec![1, 2, 3, 4]),
654 bytes_read: 4,
655 was_decompressed: false,
656 checksum: Some(0x12345678),
657 };
658
659 let display_str = format!("{}", result);
660 assert!(display_str.contains("4"));
661 assert!(display_str.contains("0x12345678"));
662
663 let req = TensorMemoryRequirements {
664 total_size: 1000,
665 max_tensor_size: 500,
666 tensor_count: 2,
667 quantized_tensor_count: 1,
668 non_quantized_tensor_count: 1,
669 recommended_buffer_size: 256,
670 };
671
672 let req_str = format!("{}", req);
673 assert!(req_str.contains("1000"));
674 assert!(req_str.contains("500"));
675 }
676
677 #[test]
678 fn test_memory_requirements_reasonable() {
679 let req = TensorMemoryRequirements {
680 total_size: 800,
681 max_tensor_size: 400,
682 tensor_count: 2,
683 quantized_tensor_count: 1,
684 non_quantized_tensor_count: 1,
685 recommended_buffer_size: 256,
686 };
687
688 assert!(req.is_reasonable(1000)); assert!(!req.is_reasonable(900)); assert_eq!(req.average_tensor_size(), 400); }
692}