Skip to main content

ipfrs_interface/
tensor.rs

1//! Zero-Copy Tensor API
2//!
3//! Provides high-performance tensor access with:
4//! - Zero-copy streaming
5//! - Memory-mapped responses
6//! - Partial tensor retrieval
7//! - Range request support
8
9use axum::{
10    body::Body,
11    extract::{Path, Query, State},
12    http::{header, HeaderMap, StatusCode},
13    response::{IntoResponse, Response},
14    Json,
15};
16use ipfrs_core::Cid;
17use ipfrs_storage::BlockStoreTrait;
18use serde::{Deserialize, Serialize};
19use std::path::PathBuf;
20use std::sync::Arc;
21
22use crate::gateway::GatewayState;
23use crate::middleware::{
24    add_caching_headers, check_etag_match, not_modified_response, CacheConfig,
25};
26use crate::mmap::{MmapCache, MmapError};
27
28// ============================================================================
29// Tensor Metadata
30// ============================================================================
31
32/// Tensor shape and type information
33#[derive(Debug, Clone, Serialize, Deserialize)]
34pub struct TensorMetadata {
35    /// Tensor shape (dimensions)
36    pub shape: Vec<usize>,
37    /// Data type (e.g., "f32", "f64", "i32", "u8")
38    pub dtype: String,
39    /// Total number of elements
40    pub num_elements: usize,
41    /// Size in bytes
42    pub size_bytes: usize,
43    /// Layout (row-major or column-major)
44    pub layout: TensorLayout,
45}
46
47/// Tensor memory layout
48#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
49#[serde(rename_all = "lowercase")]
50pub enum TensorLayout {
51    /// Row-major (C-style)
52    RowMajor,
53    /// Column-major (Fortran-style)
54    ColumnMajor,
55}
56
57impl TensorMetadata {
58    /// Create metadata from safetensors format
59    pub fn from_safetensors_data(data: &[u8]) -> Result<Self, String> {
60        // Safetensors format: first 8 bytes = header length
61        if data.len() < 8 {
62            return Err("Data too short for safetensors format".to_string());
63        }
64
65        let header_len = u64::from_le_bytes(
66            data[0..8]
67                .try_into()
68                .expect("data[0..8] is exactly 8 bytes after bounds check"),
69        ) as usize;
70        if data.len() < 8 + header_len {
71            return Err("Incomplete safetensors header".to_string());
72        }
73
74        // Parse JSON header
75        let header_bytes = &data[8..8 + header_len];
76        let header: serde_json::Value = serde_json::from_slice(header_bytes)
77            .map_err(|e| format!("Failed to parse safetensors header: {}", e))?;
78
79        // Extract first tensor metadata
80        if let Some(tensors) = header.as_object() {
81            if let Some((_name, tensor_info)) =
82                tensors.iter().find(|(k, _)| k.as_str() != "__metadata__")
83            {
84                if let Some(shape) = tensor_info.get("shape").and_then(|s| s.as_array()) {
85                    let shape: Vec<usize> = shape
86                        .iter()
87                        .filter_map(|v| v.as_u64().map(|n| n as usize))
88                        .collect();
89
90                    let dtype = tensor_info
91                        .get("dtype")
92                        .and_then(|d| d.as_str())
93                        .unwrap_or("f32")
94                        .to_string();
95
96                    let num_elements = shape.iter().product();
97                    let element_size = Self::dtype_size(&dtype);
98                    let size_bytes = num_elements * element_size;
99
100                    return Ok(TensorMetadata {
101                        shape,
102                        dtype,
103                        num_elements,
104                        size_bytes,
105                        layout: TensorLayout::RowMajor, // Default
106                    });
107                }
108            }
109        }
110
111        Err("No tensor found in safetensors data".to_string())
112    }
113
114    /// Get size of a data type in bytes
115    fn dtype_size(dtype: &str) -> usize {
116        match dtype {
117            "f16" | "bf16" => 2,
118            "f32" | "i32" | "u32" => 4,
119            "f64" | "i64" | "u64" => 8,
120            "i8" | "u8" => 1,
121            "i16" | "u16" => 2,
122            _ => 4, // Default to 4 bytes
123        }
124    }
125
126    /// Create metadata from raw tensor data
127    pub fn from_raw(shape: Vec<usize>, dtype: String) -> Self {
128        let num_elements = shape.iter().product();
129        let element_size = Self::dtype_size(&dtype);
130        let size_bytes = num_elements * element_size;
131
132        TensorMetadata {
133            shape,
134            dtype,
135            num_elements,
136            size_bytes,
137            layout: TensorLayout::RowMajor,
138        }
139    }
140}
141
142// ============================================================================
143// Tensor Query Parameters
144// ============================================================================
145
146/// Query parameters for tensor retrieval
147#[derive(Debug, Deserialize)]
148pub struct TensorQuery {
149    /// Retrieve only metadata (no data)
150    pub metadata_only: Option<bool>,
151    /// Slice specification (e.g., "0:10,5:15" for 2D tensor)
152    pub slice: Option<String>,
153    /// Format: "raw" or "safetensors" (default: auto-detect)
154    pub format: Option<String>,
155}
156
157/// Tensor slice specification
158#[derive(Debug)]
159pub struct TensorSlice {
160    /// Slice ranges for each dimension (start, end)
161    pub ranges: Vec<(usize, Option<usize>)>,
162}
163
164impl TensorSlice {
165    /// Extract a slice from tensor data
166    ///
167    /// This performs actual data slicing for row-major tensors.
168    /// For multi-dimensional tensors, this extracts a contiguous region.
169    pub fn extract_data(&self, data: &[u8], metadata: &TensorMetadata) -> Result<Vec<u8>, String> {
170        if self.ranges.len() != metadata.shape.len() {
171            return Err(format!(
172                "Slice dimensions ({}) don't match tensor dimensions ({})",
173                self.ranges.len(),
174                metadata.shape.len()
175            ));
176        }
177
178        let element_size = TensorMetadata::dtype_size(&metadata.dtype);
179
180        // Dispatch to specialised implementations for 1D/2D, then fall through to the
181        // general N-D path which handles any number of dimensions ≥ 3.
182        match metadata.shape.len() {
183            1 => self.extract_1d(data, &metadata.shape, element_size),
184            2 => self.extract_2d(data, &metadata.shape, element_size),
185            _ => self.extract_nd(data, &metadata.shape, element_size),
186        }
187    }
188
189    /// Extract an N-dimensional slice for tensors with 3 or more dimensions.
190    ///
191    /// The tensor is assumed to be stored in row-major (C) order.
192    /// For each dimension `d`, the stride is `product(shape[d+1..]) * element_size`.
193    /// We iterate over the Cartesian product of all slice ranges and copy
194    /// one element at a time, so no contiguous-memory assumption is required.
195    fn extract_nd(
196        &self,
197        data: &[u8],
198        shape: &[usize],
199        element_size: usize,
200    ) -> Result<Vec<u8>, String> {
201        let ndim = shape.len();
202
203        // Validate ranges and compute concrete (start, end) pairs.
204        let mut starts = Vec::with_capacity(ndim);
205        let mut ends = Vec::with_capacity(ndim);
206        for (dim, &(start, end_opt)) in self.ranges.iter().enumerate() {
207            let end = end_opt.unwrap_or(shape[dim]);
208            if start >= shape[dim] {
209                return Err(format!(
210                    "Slice start {} out of bounds for dimension {} (size {})",
211                    start, dim, shape[dim]
212                ));
213            }
214            if end > shape[dim] {
215                return Err(format!(
216                    "Slice end {} out of bounds for dimension {} (size {})",
217                    end, dim, shape[dim]
218                ));
219            }
220            if start >= end {
221                return Err(format!(
222                    "Slice start {} >= end {} for dimension {}",
223                    start, end, dim
224                ));
225            }
226            starts.push(start);
227            ends.push(end);
228        }
229
230        // Compute row-major strides (in elements, not bytes).
231        let mut strides = vec![1usize; ndim];
232        for d in (0..ndim - 1).rev() {
233            strides[d] = strides[d + 1] * shape[d + 1];
234        }
235
236        // Pre-compute the total number of output elements.
237        let out_elements: usize = starts
238            .iter()
239            .zip(ends.iter())
240            .map(|(&s, &e)| e - s)
241            .product();
242        let mut result = vec![0u8; out_elements * element_size];
243
244        // Iterate over the Cartesian product of all slice ranges using a
245        // multi-dimensional counter (indices relative to tensor origin).
246        let mut indices = starts.clone();
247        for out_elem in 0..out_elements {
248            // Compute the flat source offset.
249            let src_elem: usize = indices
250                .iter()
251                .zip(strides.iter())
252                .map(|(&i, &s)| i * s)
253                .sum();
254            let src_byte = src_elem * element_size;
255            if src_byte + element_size > data.len() {
256                return Err(format!(
257                    "Source byte range {}..{} exceeds data length {}",
258                    src_byte,
259                    src_byte + element_size,
260                    data.len()
261                ));
262            }
263            let dst_byte = out_elem * element_size;
264            result[dst_byte..dst_byte + element_size]
265                .copy_from_slice(&data[src_byte..src_byte + element_size]);
266
267            // Advance the multi-dimensional counter (last dimension increments fastest).
268            let mut carry = true;
269            for d in (0..ndim).rev() {
270                if carry {
271                    indices[d] += 1;
272                    if indices[d] >= ends[d] {
273                        indices[d] = starts[d];
274                        // carry propagates
275                    } else {
276                        carry = false;
277                    }
278                }
279            }
280        }
281
282        Ok(result)
283    }
284
285    /// Extract 1D slice
286    fn extract_1d(
287        &self,
288        data: &[u8],
289        shape: &[usize],
290        element_size: usize,
291    ) -> Result<Vec<u8>, String> {
292        let (start, end) = (self.ranges[0].0, self.ranges[0].1.unwrap_or(shape[0]));
293
294        if start >= shape[0] || end > shape[0] || start >= end {
295            return Err(format!(
296                "Invalid 1D slice range [{}:{}] for shape [{}]",
297                start, end, shape[0]
298            ));
299        }
300
301        let byte_start = start * element_size;
302        let byte_end = end * element_size;
303
304        if byte_end > data.len() {
305            return Err(format!(
306                "Slice range {}..{} exceeds data length {}",
307                byte_start,
308                byte_end,
309                data.len()
310            ));
311        }
312
313        Ok(data[byte_start..byte_end].to_vec())
314    }
315
316    /// Extract 2D slice (row-major layout)
317    fn extract_2d(
318        &self,
319        data: &[u8],
320        shape: &[usize],
321        element_size: usize,
322    ) -> Result<Vec<u8>, String> {
323        let rows = shape[0];
324        let cols = shape[1];
325
326        let (row_start, row_end) = (self.ranges[0].0, self.ranges[0].1.unwrap_or(rows));
327        let (col_start, col_end) = (self.ranges[1].0, self.ranges[1].1.unwrap_or(cols));
328
329        if row_start >= rows || row_end > rows || row_start >= row_end {
330            return Err(format!(
331                "Invalid row range [{}:{}] for shape [{}, {}]",
332                row_start, row_end, rows, cols
333            ));
334        }
335
336        if col_start >= cols || col_end > cols || col_start >= col_end {
337            return Err(format!(
338                "Invalid column range [{}:{}] for shape [{}, {}]",
339                col_start, col_end, rows, cols
340            ));
341        }
342
343        let mut result = Vec::new();
344        let row_size = cols * element_size;
345
346        for row in row_start..row_end {
347            let row_offset = row * row_size;
348            let slice_start = row_offset + col_start * element_size;
349            let slice_end = row_offset + col_end * element_size;
350
351            if slice_end > data.len() {
352                return Err(format!(
353                    "Row {} slice range {}..{} exceeds data length {}",
354                    row,
355                    slice_start,
356                    slice_end,
357                    data.len()
358                ));
359            }
360
361            result.extend_from_slice(&data[slice_start..slice_end]);
362        }
363
364        Ok(result)
365    }
366
367    /// Parse slice string (e.g., "0:10,5:15")
368    pub fn parse(slice_str: &str) -> Result<Self, String> {
369        let ranges: Result<Vec<_>, String> = slice_str
370            .split(',')
371            .map(|part| {
372                let parts: Vec<&str> = part.split(':').collect();
373                match parts.len() {
374                    1 => {
375                        let idx = parts[0]
376                            .parse::<usize>()
377                            .map_err(|e| format!("Invalid slice index: {}", e))?;
378                        Ok((idx, Some(idx + 1)))
379                    }
380                    2 => {
381                        let start = parts[0]
382                            .parse::<usize>()
383                            .map_err(|e| format!("Invalid slice start: {}", e))?;
384                        let end = if parts[1].is_empty() {
385                            None
386                        } else {
387                            Some(
388                                parts[1]
389                                    .parse::<usize>()
390                                    .map_err(|e| format!("Invalid slice end: {}", e))?,
391                            )
392                        };
393                        Ok((start, end))
394                    }
395                    _ => Err(format!("Invalid slice format: {}", part)),
396                }
397            })
398            .collect();
399
400        Ok(TensorSlice { ranges: ranges? })
401    }
402
403    /// Calculate the slice size in bytes
404    pub fn calculate_size(&self, metadata: &TensorMetadata) -> Result<usize, String> {
405        if self.ranges.len() != metadata.shape.len() {
406            return Err(format!(
407                "Slice dimensions ({}) don't match tensor dimensions ({})",
408                self.ranges.len(),
409                metadata.shape.len()
410            ));
411        }
412
413        let mut slice_elements = 1;
414        for (i, (start, end)) in self.ranges.iter().enumerate() {
415            let dim_size = metadata.shape[i];
416            let actual_end = end.unwrap_or(dim_size);
417
418            if *start >= dim_size || actual_end > dim_size || *start >= actual_end {
419                return Err(format!(
420                    "Invalid slice range [{}:{}] for dimension {} of size {}",
421                    start, actual_end, i, dim_size
422                ));
423            }
424
425            slice_elements *= actual_end - start;
426        }
427
428        let element_size = TensorMetadata::dtype_size(&metadata.dtype);
429        Ok(slice_elements * element_size)
430    }
431}
432
433// ============================================================================
434// Tensor Responses
435// ============================================================================
436
437/// Tensor metadata response
438#[derive(Debug, Serialize)]
439pub struct TensorInfoResponse {
440    pub cid: String,
441    pub metadata: TensorMetadata,
442}
443
444// ============================================================================
445// Tensor Endpoints
446// ============================================================================
447
448/// Get tensor with zero-copy streaming
449///
450/// GET /v1/tensor/{cid}
451///
452/// Retrieves tensor data with optional range requests for partial loading.
453/// Supports both safetensors and raw binary formats.
454pub async fn get_tensor(
455    State(state): State<GatewayState>,
456    Path(cid_str): Path<String>,
457    Query(query): Query<TensorQuery>,
458    headers: HeaderMap,
459) -> Result<Response, TensorError> {
460    let cid: Cid = cid_str
461        .parse()
462        .map_err(|_| TensorError::InvalidCid(cid_str.clone()))?;
463
464    // Check if cached (ETag)
465    let cache_config = CacheConfig::default();
466    if check_etag_match(&headers, &cid_str) {
467        return Ok(not_modified_response(&cid_str, &cache_config));
468    }
469
470    // Get the block
471    let block = state
472        .store
473        .get(&cid)
474        .await
475        .map_err(|e| TensorError::Storage(e.to_string()))?
476        .ok_or_else(|| TensorError::NotFound(cid_str.clone()))?;
477
478    let data = block.data();
479
480    // Try to parse metadata (safetensors format or assume raw)
481    let metadata = TensorMetadata::from_safetensors_data(data).ok();
482
483    // If metadata_only requested, return just metadata
484    if query.metadata_only.unwrap_or(false) {
485        if let Some(metadata) = metadata {
486            return Ok(Json(TensorInfoResponse {
487                cid: cid_str,
488                metadata,
489            })
490            .into_response());
491        } else {
492            return Err(TensorError::InvalidFormat(
493                "Cannot extract metadata from tensor".to_string(),
494            ));
495        }
496    }
497
498    // Handle partial retrieval (slicing)
499    let (response_data, is_partial, metadata_for_response) = if let Some(slice_str) = query.slice {
500        let meta = metadata.ok_or_else(|| {
501            TensorError::InvalidFormat("Metadata required for slicing".to_string())
502        })?;
503
504        let slice = TensorSlice::parse(&slice_str)?;
505
506        // Extract the sliced data
507        let sliced_data = slice.extract_data(data, &meta)?;
508
509        (sliced_data, true, Some(meta))
510    } else {
511        // Return full tensor
512        (data.to_vec(), false, metadata)
513    };
514
515    // Build response
516    let mut response_builder = Response::builder();
517
518    if is_partial {
519        response_builder = response_builder.status(StatusCode::PARTIAL_CONTENT);
520    } else {
521        response_builder = response_builder.status(StatusCode::OK);
522    }
523
524    // Determine content type based on format
525    let content_type = match query.format.as_deref() {
526        Some("safetensors") | None if metadata_for_response.is_some() => {
527            "application/vnd.safetensors"
528        }
529        _ => "application/octet-stream",
530    };
531
532    let mut response = response_builder
533        .header(header::CONTENT_TYPE, content_type)
534        .header(header::CONTENT_LENGTH, response_data.len())
535        .header(
536            "X-Tensor-Format",
537            if metadata_for_response.is_some() {
538                "safetensors"
539            } else {
540                "raw"
541            },
542        )
543        .body(Body::from(response_data))
544        .expect("building HTTP response with valid headers and body is infallible");
545
546    // Add caching headers
547    add_caching_headers(response.headers_mut(), &cid_str, &cache_config);
548
549    // Add tensor metadata as headers if available
550    if let Some(ref meta) = metadata_for_response {
551        if let Ok(shape_json) = serde_json::to_string(&meta.shape) {
552            if let Ok(header_value) = header::HeaderValue::from_str(&shape_json) {
553                response
554                    .headers_mut()
555                    .insert("X-Tensor-Shape", header_value);
556            }
557        }
558        if let Ok(header_value) = header::HeaderValue::from_str(&meta.dtype) {
559            response
560                .headers_mut()
561                .insert("X-Tensor-Dtype", header_value);
562        }
563    }
564
565    Ok(response)
566}
567
568/// Get tensor metadata only
569///
570/// GET /v1/tensor/{cid}/info
571///
572/// Retrieves only tensor metadata without downloading the full data.
573pub async fn get_tensor_info(
574    State(state): State<GatewayState>,
575    Path(cid_str): Path<String>,
576) -> Result<Json<TensorInfoResponse>, TensorError> {
577    let cid: Cid = cid_str
578        .parse()
579        .map_err(|_| TensorError::InvalidCid(cid_str.clone()))?;
580
581    // Get the block
582    let block = state
583        .store
584        .get(&cid)
585        .await
586        .map_err(|e| TensorError::Storage(e.to_string()))?
587        .ok_or_else(|| TensorError::NotFound(cid_str.clone()))?;
588
589    let data = block.data();
590
591    // Parse metadata
592    let metadata = TensorMetadata::from_safetensors_data(data).map_err(|e| {
593        TensorError::InvalidFormat(format!("Failed to parse tensor metadata: {}", e))
594    })?;
595
596    Ok(Json(TensorInfoResponse {
597        cid: cid_str,
598        metadata,
599    }))
600}
601
602/// Get tensor in Apache Arrow IPC format
603///
604/// GET /v1/tensor/{cid}/arrow
605///
606/// Retrieves tensor data in Apache Arrow IPC Stream format for efficient
607/// data exchange with Arrow-compatible systems (Pandas, Polars, PyArrow, etc.)
608pub async fn get_tensor_arrow(
609    State(state): State<GatewayState>,
610    Path(cid_str): Path<String>,
611    Query(query): Query<TensorQuery>,
612) -> Result<Response, TensorError> {
613    let cid: Cid = cid_str
614        .parse()
615        .map_err(|_| TensorError::InvalidCid(cid_str.clone()))?;
616
617    // Get the block
618    let block = state
619        .store
620        .get(&cid)
621        .await
622        .map_err(|e| TensorError::Storage(e.to_string()))?
623        .ok_or_else(|| TensorError::NotFound(cid_str.clone()))?;
624
625    let data = block.data();
626
627    // Try to parse metadata (safetensors format)
628    let metadata = TensorMetadata::from_safetensors_data(data)
629        .map_err(|e| TensorError::InvalidFormat(format!("Cannot parse tensor metadata: {}", e)))?;
630
631    // Handle partial retrieval (slicing) if requested
632    let response_data = if let Some(slice_str) = query.slice {
633        let slice = TensorSlice::parse(&slice_str)?;
634        slice.extract_data(data, &metadata)?
635    } else {
636        // Return full tensor data (skip safetensors header)
637        let header_len = u64::from_le_bytes(
638            data[0..8]
639                .try_into()
640                .expect("data[0..8] is exactly 8 bytes"),
641        ) as usize;
642        data[8 + header_len..].to_vec()
643    };
644
645    // Convert to Arrow RecordBatch and serialize
646    let batch = crate::arrow::tensor_to_record_batch(&metadata, &response_data)
647        .map_err(|e| TensorError::Storage(format!("Failed to create Arrow batch: {}", e)))?;
648
649    let ipc_bytes = crate::arrow::record_batch_to_ipc_bytes(&batch)
650        .map_err(|e| TensorError::Storage(format!("Failed to serialize Arrow IPC: {}", e)))?;
651
652    // Build response
653    Response::builder()
654        .status(StatusCode::OK)
655        .header(header::CONTENT_TYPE, "application/vnd.apache.arrow.stream")
656        .header("X-Tensor-Shape", format!("{:?}", metadata.shape))
657        .header("X-Tensor-Dtype", &metadata.dtype)
658        .header("X-Tensor-Elements", metadata.num_elements.to_string())
659        .body(Body::from(ipc_bytes))
660        .map_err(|e| TensorError::Storage(format!("Failed to build response: {}", e)))
661}
662
663// ============================================================================
664// Memory-Mapped Tensor Serving
665// ============================================================================
666
667/// Get tensor using memory-mapped I/O (zero-copy from disk)
668///
669/// GET /v1/tensor/{cid}/mmap
670///
671/// Retrieves tensor data using memory-mapped I/O for maximum performance.
672/// This endpoint is optimized for serving large tensors directly from disk
673/// without loading them into memory.
674///
675/// # Performance
676///
677/// - **Zero-copy**: Data is served directly from disk via OS page cache
678/// - **Lazy loading**: Only requested pages are loaded into memory
679/// - **OS optimizations**: Leverages sendfile and similar system calls
680///
681/// # Limitations
682///
683/// - Only works for tensors stored on local filesystem
684/// - Requires tensor file path to be available
685/// - Not suitable for tensors stored in distributed storage
686pub async fn get_tensor_mmap(
687    Path(cid_str): Path<String>,
688    Query(query): Query<TensorQuery>,
689    headers: HeaderMap,
690    mmap_cache: Arc<MmapCache>,
691    tensor_storage_path: PathBuf,
692) -> Result<Response, TensorError> {
693    let _cid: Cid = cid_str
694        .parse()
695        .map_err(|_| TensorError::InvalidCid(cid_str.clone()))?;
696
697    // Check if cached (ETag)
698    let cache_config = CacheConfig::default();
699    if check_etag_match(&headers, &cid_str) {
700        return Ok(not_modified_response(&cid_str, &cache_config));
701    }
702
703    // Construct file path from CID
704    // In production, this would use the actual storage backend's file path
705    let file_path = tensor_storage_path.join(format!("{}.tensor", cid_str));
706
707    // Get or create memory-mapped file
708    let mmap_file = mmap_cache.get_or_create(&file_path).map_err(|e| match e {
709        MmapError::FileNotFound(_) => TensorError::NotFound(cid_str.clone()),
710        _ => TensorError::Storage(e.to_string()),
711    })?;
712
713    // Get file data
714    let data = mmap_file.bytes();
715
716    // Try to parse metadata (safetensors format)
717    let metadata = TensorMetadata::from_safetensors_data(&data).ok();
718
719    // If metadata_only requested, return just metadata
720    if query.metadata_only.unwrap_or(false) {
721        if let Some(metadata) = metadata {
722            return Ok(Json(TensorInfoResponse {
723                cid: cid_str,
724                metadata,
725            })
726            .into_response());
727        } else {
728            return Err(TensorError::InvalidFormat(
729                "Cannot extract metadata from tensor".to_string(),
730            ));
731        }
732    }
733
734    // Handle partial retrieval (slicing)
735    let (response_data, is_partial, metadata_for_response) = if let Some(slice_str) = query.slice {
736        let meta = metadata.ok_or_else(|| {
737            TensorError::InvalidFormat("Metadata required for slicing".to_string())
738        })?;
739
740        let slice = TensorSlice::parse(&slice_str)?;
741
742        // For mmap, we can efficiently retrieve just the slice
743        // by calculating the byte range
744        let sliced_data = slice.extract_data(&data, &meta)?;
745
746        (sliced_data, true, Some(meta))
747    } else {
748        // Return full tensor
749        (data.to_vec(), false, metadata)
750    };
751
752    // Build response
753    let mut response_builder = Response::builder();
754
755    if is_partial {
756        response_builder = response_builder.status(StatusCode::PARTIAL_CONTENT);
757    } else {
758        response_builder = response_builder.status(StatusCode::OK);
759    }
760
761    // Determine content type
762    let content_type = match query.format.as_deref() {
763        Some("safetensors") | None if metadata_for_response.is_some() => {
764            "application/vnd.safetensors"
765        }
766        _ => "application/octet-stream",
767    };
768
769    let mut response = response_builder
770        .header(header::CONTENT_TYPE, content_type)
771        .header(header::CONTENT_LENGTH, response_data.len())
772        .header("X-Served-By", "mmap")
773        .header(
774            "X-Tensor-Format",
775            if metadata_for_response.is_some() {
776                "safetensors"
777            } else {
778                "raw"
779            },
780        )
781        .body(Body::from(response_data))
782        .expect("building HTTP response with valid headers and body is infallible");
783
784    // Add caching headers
785    add_caching_headers(response.headers_mut(), &cid_str, &cache_config);
786
787    // Add tensor metadata as headers
788    if let Some(ref meta) = metadata_for_response {
789        if let Ok(shape_json) = serde_json::to_string(&meta.shape) {
790            if let Ok(header_value) = header::HeaderValue::from_str(&shape_json) {
791                response
792                    .headers_mut()
793                    .insert("X-Tensor-Shape", header_value);
794            }
795        }
796        if let Ok(header_value) = header::HeaderValue::from_str(&meta.dtype) {
797            response
798                .headers_mut()
799                .insert("X-Tensor-Dtype", header_value);
800        }
801    }
802
803    Ok(response)
804}
805
806/// Mmap-based tensor range request
807///
808/// Efficiently serves byte ranges from memory-mapped tensor files.
809/// Optimized for HTTP 206 Partial Content responses.
810#[allow(dead_code)]
811pub async fn get_tensor_mmap_range(
812    cid_str: String,
813    range: std::ops::Range<usize>,
814    mmap_cache: Arc<MmapCache>,
815    tensor_storage_path: PathBuf,
816) -> Result<Response, TensorError> {
817    let _cid: Cid = cid_str
818        .parse()
819        .map_err(|_| TensorError::InvalidCid(cid_str.clone()))?;
820
821    // Construct file path
822    let file_path = tensor_storage_path.join(format!("{}.tensor", cid_str));
823
824    // Get memory-mapped file
825    let mmap_file = mmap_cache.get_or_create(&file_path).map_err(|e| match e {
826        MmapError::FileNotFound(_) => TensorError::NotFound(cid_str.clone()),
827        _ => TensorError::Storage(e.to_string()),
828    })?;
829
830    // Get the requested range (zero-copy)
831    let range_data = mmap_file
832        .range(range.clone())
833        .map_err(|e| TensorError::Storage(e.to_string()))?;
834
835    // Build partial content response
836    let response = Response::builder()
837        .status(StatusCode::PARTIAL_CONTENT)
838        .header(header::CONTENT_TYPE, "application/octet-stream")
839        .header(header::CONTENT_LENGTH, range_data.len())
840        .header(
841            header::CONTENT_RANGE,
842            format!(
843                "bytes {}-{}/{}",
844                range.start,
845                range.end - 1,
846                mmap_file.size()
847            ),
848        )
849        .header("X-Served-By", "mmap")
850        .body(Body::from(range_data))
851        .expect("building PARTIAL_CONTENT response with valid headers and body is infallible");
852
853    Ok(response)
854}
855
856// ============================================================================
857// Error Types
858// ============================================================================
859
860/// Tensor operation errors
861#[derive(Debug)]
862pub enum TensorError {
863    InvalidCid(String),
864    NotFound(String),
865    InvalidFormat(String),
866    Storage(String),
867    NotImplemented(String),
868}
869
870impl IntoResponse for TensorError {
871    fn into_response(self) -> Response {
872        let (status, message) = match self {
873            TensorError::InvalidCid(cid) => {
874                (StatusCode::BAD_REQUEST, format!("Invalid CID: {}", cid))
875            }
876            TensorError::NotFound(cid) => {
877                (StatusCode::NOT_FOUND, format!("Tensor not found: {}", cid))
878            }
879            TensorError::InvalidFormat(msg) => (
880                StatusCode::BAD_REQUEST,
881                format!("Invalid tensor format: {}", msg),
882            ),
883            TensorError::Storage(msg) => (
884                StatusCode::INTERNAL_SERVER_ERROR,
885                format!("Storage error: {}", msg),
886            ),
887            TensorError::NotImplemented(msg) => (
888                StatusCode::NOT_IMPLEMENTED,
889                format!("Not implemented: {}", msg),
890            ),
891        };
892
893        (status, message).into_response()
894    }
895}
896
897impl From<String> for TensorError {
898    fn from(s: String) -> Self {
899        TensorError::InvalidFormat(s)
900    }
901}
902
903#[cfg(test)]
904mod tests {
905    use super::*;
906
907    #[test]
908    fn test_tensor_metadata_dtype_size() {
909        assert_eq!(TensorMetadata::dtype_size("f32"), 4);
910        assert_eq!(TensorMetadata::dtype_size("f64"), 8);
911        assert_eq!(TensorMetadata::dtype_size("i32"), 4);
912        assert_eq!(TensorMetadata::dtype_size("u8"), 1);
913        assert_eq!(TensorMetadata::dtype_size("f16"), 2);
914    }
915
916    #[test]
917    fn test_tensor_metadata_from_raw() {
918        let meta = TensorMetadata::from_raw(vec![10, 20, 30], "f32".to_string());
919        assert_eq!(meta.shape, vec![10, 20, 30]);
920        assert_eq!(meta.dtype, "f32");
921        assert_eq!(meta.num_elements, 6000);
922        assert_eq!(meta.size_bytes, 24000);
923    }
924
925    #[test]
926    fn test_tensor_slice_parse_single() {
927        let slice = TensorSlice::parse("5").expect("test: parse single index should succeed");
928        assert_eq!(slice.ranges, vec![(5, Some(6))]);
929    }
930
931    #[test]
932    fn test_tensor_slice_parse_range() {
933        let slice = TensorSlice::parse("10:20").expect("test: parse range slice should succeed");
934        assert_eq!(slice.ranges, vec![(10, Some(20))]);
935    }
936
937    #[test]
938    fn test_tensor_slice_parse_open_end() {
939        let slice = TensorSlice::parse("10:").expect("test: parse open-end slice should succeed");
940        assert_eq!(slice.ranges, vec![(10, None)]);
941    }
942
943    #[test]
944    fn test_tensor_slice_parse_multi_dim() {
945        let slice = TensorSlice::parse("0:10,5:15,2:8")
946            .expect("test: parse multi-dim slice should succeed");
947        assert_eq!(
948            slice.ranges,
949            vec![(0, Some(10)), (5, Some(15)), (2, Some(8))]
950        );
951    }
952
953    #[test]
954    fn test_tensor_slice_calculate_size() {
955        let meta = TensorMetadata::from_raw(vec![100, 100], "f32".to_string());
956        let slice = TensorSlice::parse("0:10,0:10").expect("test: parse 2D slice should succeed");
957
958        let size = slice
959            .calculate_size(&meta)
960            .expect("test: size calculation should succeed");
961        assert_eq!(size, 10 * 10 * 4); // 10x10 elements * 4 bytes
962    }
963
964    #[test]
965    fn test_tensor_slice_invalid_dimensions() {
966        let meta = TensorMetadata::from_raw(vec![100, 100], "f32".to_string());
967        let slice = TensorSlice::parse("0:10").expect("test: parse 1D slice should succeed"); // Only 1 dimension
968
969        let result = slice.calculate_size(&meta);
970        assert!(result.is_err());
971    }
972
973    #[test]
974    fn test_tensor_slice_out_of_bounds() {
975        let meta = TensorMetadata::from_raw(vec![100, 100], "f32".to_string());
976        let slice = TensorSlice::parse("0:200,0:10")
977            .expect("test: parse out-of-bounds slice should succeed");
978
979        let result = slice.calculate_size(&meta);
980        assert!(result.is_err());
981    }
982
983    #[test]
984    fn test_tensor_layout_serialization() {
985        let layout = TensorLayout::RowMajor;
986        let json =
987            serde_json::to_string(&layout).expect("test: RowMajor serialization should succeed");
988        assert_eq!(json, r#""rowmajor""#);
989
990        let layout = TensorLayout::ColumnMajor;
991        let json =
992            serde_json::to_string(&layout).expect("test: ColumnMajor serialization should succeed");
993        assert_eq!(json, r#""columnmajor""#);
994    }
995
996    #[test]
997    fn test_tensor_slice_extract_1d() {
998        // 1D tensor: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] (f32)
999        let data: Vec<u8> = (0..10).flat_map(|i| (i as f32).to_le_bytes()).collect();
1000
1001        let meta = TensorMetadata::from_raw(vec![10], "f32".to_string());
1002        let slice = TensorSlice::parse("2:5").expect("test: parse 1D range should succeed");
1003
1004        let result = slice
1005            .extract_data(&data, &meta)
1006            .expect("test: 1D slice extraction should succeed");
1007
1008        // Should extract elements 2, 3, 4 (3 elements * 4 bytes = 12 bytes)
1009        assert_eq!(result.len(), 12);
1010
1011        // Verify the extracted values
1012        let values: Vec<f32> = result
1013            .chunks_exact(4)
1014            .map(|chunk| {
1015                f32::from_le_bytes(
1016                    chunk
1017                        .try_into()
1018                        .expect("test: chunk to [u8;4] conversion should succeed"),
1019                )
1020            })
1021            .collect();
1022
1023        assert_eq!(values, vec![2.0, 3.0, 4.0]);
1024    }
1025
1026    #[test]
1027    fn test_tensor_slice_extract_2d() {
1028        // 2D tensor: 4x3 matrix (f32)
1029        // [[0, 1, 2],
1030        //  [3, 4, 5],
1031        //  [6, 7, 8],
1032        //  [9, 10, 11]]
1033        let data: Vec<u8> = (0..12).flat_map(|i| (i as f32).to_le_bytes()).collect();
1034
1035        let meta = TensorMetadata::from_raw(vec![4, 3], "f32".to_string());
1036        let slice =
1037            TensorSlice::parse("1:3,0:2").expect("test: parse 2D row/col slice should succeed"); // Rows 1-2, Cols 0-1
1038
1039        let result = slice
1040            .extract_data(&data, &meta)
1041            .expect("test: 2D slice extraction should succeed");
1042
1043        // Should extract:
1044        // [[3, 4],
1045        //  [6, 7]]
1046        // 2 rows * 2 cols * 4 bytes = 16 bytes
1047        assert_eq!(result.len(), 16);
1048
1049        let values: Vec<f32> = result
1050            .chunks_exact(4)
1051            .map(|chunk| {
1052                f32::from_le_bytes(
1053                    chunk
1054                        .try_into()
1055                        .expect("test: chunk to [u8;4] conversion should succeed"),
1056                )
1057            })
1058            .collect();
1059
1060        assert_eq!(values, vec![3.0, 4.0, 6.0, 7.0]);
1061    }
1062
1063    #[test]
1064    fn test_tensor_slice_extract_2d_single_row() {
1065        let data: Vec<u8> = (0..12).flat_map(|i| (i as f32).to_le_bytes()).collect();
1066
1067        let meta = TensorMetadata::from_raw(vec![4, 3], "f32".to_string());
1068        let slice =
1069            TensorSlice::parse("2:3,0:3").expect("test: parse single-row slice should succeed"); // Row 2, all columns
1070
1071        let result = slice
1072            .extract_data(&data, &meta)
1073            .expect("test: single-row extraction should succeed");
1074
1075        // Should extract: [6, 7, 8]
1076        assert_eq!(result.len(), 12);
1077
1078        let values: Vec<f32> = result
1079            .chunks_exact(4)
1080            .map(|chunk| {
1081                f32::from_le_bytes(
1082                    chunk
1083                        .try_into()
1084                        .expect("test: chunk to [u8;4] conversion should succeed"),
1085                )
1086            })
1087            .collect();
1088
1089        assert_eq!(values, vec![6.0, 7.0, 8.0]);
1090    }
1091
1092    #[test]
1093    fn test_tensor_slice_extract_invalid_dimension() {
1094        let data = vec![0u8; 40]; // 10 f32 elements
1095        let meta = TensorMetadata::from_raw(vec![10], "f32".to_string());
1096        let slice = TensorSlice::parse("2:5,0:2")
1097            .expect("test: parse 2D slice for 1D tensor should succeed"); // 2D slice for 1D tensor
1098
1099        let result = slice.extract_data(&data, &meta);
1100        assert!(result.is_err());
1101    }
1102
1103    #[test]
1104    fn test_tensor_slice_extract_out_of_bounds() {
1105        let data: Vec<u8> = (0..10).flat_map(|i| (i as f32).to_le_bytes()).collect();
1106
1107        let meta = TensorMetadata::from_raw(vec![10], "f32".to_string());
1108        let slice =
1109            TensorSlice::parse("8:12").expect("test: parse out-of-bounds range should succeed"); // Out of bounds
1110
1111        let result = slice.extract_data(&data, &meta);
1112        assert!(result.is_err());
1113    }
1114}