ipfrs_interface/
safetensors.rs1use bytes::Bytes;
10use ipfrs_core::error::{Error, Result};
11use serde::{Deserialize, Serialize};
12use std::collections::HashMap;
13
14#[derive(Debug)]
16pub struct SafetensorsFile {
17 header: SafetensorsHeader,
19 data: Bytes,
21 header_size: usize,
23}
24
25#[derive(Debug, Clone, Serialize, Deserialize)]
27pub struct SafetensorsHeader {
28 #[serde(flatten)]
30 pub tensors: HashMap<String, TensorInfo>,
31}
32
33#[derive(Debug, Clone, Serialize, Deserialize)]
35pub struct TensorInfo {
36 pub dtype: String,
38 pub shape: Vec<usize>,
40 pub data_offsets: [usize; 2], }
43
44impl SafetensorsFile {
45 pub fn from_bytes(data: Bytes) -> Result<Self> {
47 if data.len() < 8 {
48 return Err(Error::InvalidInput(
49 "Data too short for safetensors format".to_string(),
50 ));
51 }
52
53 let header_len = u64::from_le_bytes(
55 data[0..8]
56 .try_into()
57 .expect("data[0..8] is exactly 8 bytes after bounds check"),
58 ) as usize;
59
60 if data.len() < 8 + header_len {
61 return Err(Error::InvalidInput(
62 "Incomplete safetensors header".to_string(),
63 ));
64 }
65
66 let header_bytes = &data[8..8 + header_len];
68 let header: SafetensorsHeader = serde_json::from_slice(header_bytes).map_err(|e| {
69 Error::InvalidInput(format!("Failed to parse safetensors header: {}", e))
70 })?;
71
72 Self::validate_header(&header, data.len() - 8 - header_len)?;
74
75 Ok(SafetensorsFile {
76 header,
77 data,
78 header_size: 8 + header_len,
79 })
80 }
81
82 fn validate_header(header: &SafetensorsHeader, data_section_size: usize) -> Result<()> {
84 for (name, info) in &header.tensors {
85 let [start, end] = info.data_offsets;
86
87 if start >= end {
88 return Err(Error::InvalidInput(format!(
89 "Invalid offsets for tensor '{}': start={}, end={}",
90 name, start, end
91 )));
92 }
93
94 if end > data_section_size {
95 return Err(Error::InvalidInput(format!(
96 "Tensor '{}' offset {} exceeds data section size {}",
97 name, end, data_section_size
98 )));
99 }
100
101 let expected_size = Self::calculate_tensor_size(&info.shape, &info.dtype);
103 let actual_size = end - start;
104
105 if actual_size != expected_size {
106 return Err(Error::InvalidInput(format!(
107 "Tensor '{}' size mismatch: expected {}, got {}",
108 name, expected_size, actual_size
109 )));
110 }
111 }
112
113 Ok(())
114 }
115
116 fn calculate_tensor_size(shape: &[usize], dtype: &str) -> usize {
118 let num_elements: usize = shape.iter().product();
119 let element_size = Self::dtype_size(dtype);
120 num_elements * element_size
121 }
122
123 fn dtype_size(dtype: &str) -> usize {
125 match dtype {
126 "F16" | "BF16" => 2,
127 "F32" | "I32" | "U32" => 4,
128 "F64" | "I64" | "U64" => 8,
129 "I8" | "U8" => 1,
130 "I16" | "U16" => 2,
131 "BOOL" => 1,
132 _ => 4, }
134 }
135
136 pub fn get_tensor(&self, name: &str) -> Result<TensorData> {
138 let info = self.header.tensors.get(name).ok_or_else(|| {
139 Error::NotFound(format!("Tensor '{}' not found in safetensors file", name))
140 })?;
141
142 let [start, end] = info.data_offsets;
143 let data_start = self.header_size + start;
144 let data_end = self.header_size + end;
145
146 if data_end > self.data.len() {
147 return Err(Error::InvalidInput(format!(
148 "Tensor data range {}..{} exceeds file size {}",
149 data_start,
150 data_end,
151 self.data.len()
152 )));
153 }
154
155 Ok(TensorData {
156 dtype: info.dtype.clone(),
157 shape: info.shape.clone(),
158 data: self.data.slice(data_start..data_end),
159 })
160 }
161
162 pub fn tensor_names(&self) -> Vec<String> {
164 self.header
165 .tensors
166 .keys()
167 .filter(|k| k.as_str() != "__metadata__")
168 .cloned()
169 .collect()
170 }
171
172 pub fn get_tensor_info(&self, name: &str) -> Option<&TensorInfo> {
174 self.header.tensors.get(name)
175 }
176
177 pub fn header(&self) -> &SafetensorsHeader {
179 &self.header
180 }
181
182 pub fn raw_data(&self) -> &Bytes {
184 &self.data
185 }
186}
187
188#[derive(Debug, Clone)]
190pub struct TensorData {
191 pub dtype: String,
193 pub shape: Vec<usize>,
195 pub data: Bytes,
197}
198
199impl TensorData {
200 pub fn num_elements(&self) -> usize {
202 self.shape.iter().product()
203 }
204
205 pub fn size_bytes(&self) -> usize {
207 self.data.len()
208 }
209
210 pub fn element_size(&self) -> usize {
212 if self.num_elements() == 0 {
213 return 0;
214 }
215 self.size_bytes() / self.num_elements()
216 }
217}
218
219#[cfg(test)]
220mod tests {
221 use super::*;
222
223 #[test]
224 fn test_dtype_size() {
225 assert_eq!(SafetensorsFile::dtype_size("F32"), 4);
226 assert_eq!(SafetensorsFile::dtype_size("F64"), 8);
227 assert_eq!(SafetensorsFile::dtype_size("F16"), 2);
228 assert_eq!(SafetensorsFile::dtype_size("I32"), 4);
229 assert_eq!(SafetensorsFile::dtype_size("U8"), 1);
230 assert_eq!(SafetensorsFile::dtype_size("BOOL"), 1);
231 }
232
233 #[test]
234 fn test_calculate_tensor_size() {
235 assert_eq!(
236 SafetensorsFile::calculate_tensor_size(&[10, 20], "F32"),
237 10 * 20 * 4
238 );
239 assert_eq!(
240 SafetensorsFile::calculate_tensor_size(&[5, 5, 5], "F64"),
241 5 * 5 * 5 * 8
242 );
243 }
244
245 #[test]
246 fn test_tensor_data_num_elements() {
247 let data = TensorData {
248 dtype: "F32".to_string(),
249 shape: vec![2, 3],
250 data: Bytes::from(vec![0u8; 24]), };
252 assert_eq!(data.num_elements(), 6);
253 assert_eq!(data.size_bytes(), 24);
254 assert_eq!(data.element_size(), 4);
255 }
256}