1use super::{
9 align_64, AprV2Flags, AprV2Header, AprV2Metadata, TensorDType, TensorIndexEntry, V2FormatError,
10 HEADER_SIZE_V2,
11};
12use crate::f16::f32_to_f16;
13use std::io::{Read, Write};
14
15#[allow(missing_debug_implementations)]
30pub struct AprV2StreamingWriter {
31 header: AprV2Header,
32 metadata: AprV2Metadata,
33 index_entries: Vec<TensorIndexEntry>,
35 data_writer: std::io::BufWriter<std::fs::File>,
37 data_offset: u64,
39}
40
41impl AprV2StreamingWriter {
42 pub fn new(metadata: AprV2Metadata) -> Result<Self, V2FormatError> {
48 let mut header = AprV2Header::new();
49 header.flags = header.flags.with(AprV2Flags::LAYOUT_ROW_MAJOR);
50
51 let data_file = tempfile::tempfile()
52 .map_err(|e| V2FormatError::IoError(format!("Failed to create temp file: {e}")))?;
53
54 Ok(Self {
55 header,
56 metadata,
57 index_entries: Vec::new(),
58 data_writer: std::io::BufWriter::new(data_file),
59 data_offset: 0,
60 })
61 }
62
63 pub fn add_tensor(
72 &mut self,
73 name: impl Into<String>,
74 dtype: TensorDType,
75 shape: Vec<usize>,
76 data: &[u8],
77 ) -> Result<(), V2FormatError> {
78 let entry = TensorIndexEntry::new(name, dtype, shape, self.data_offset, data.len() as u64);
79 self.index_entries.push(entry);
80
81 self.data_writer
83 .write_all(data)
84 .map_err(|e| V2FormatError::IoError(e.to_string()))?;
85
86 let padded_size = align_64(data.len());
87 let padding = padded_size - data.len();
88 if padding > 0 {
89 self.data_writer
90 .write_all(&vec![0u8; padding])
91 .map_err(|e| V2FormatError::IoError(e.to_string()))?;
92 }
93
94 self.data_offset += padded_size as u64;
95 Ok(())
96 }
97
98 pub fn add_f32_tensor(
104 &mut self,
105 name: impl Into<String>,
106 shape: Vec<usize>,
107 data: &[f32],
108 ) -> Result<(), V2FormatError> {
109 let bytes: Vec<u8> = data.iter().flat_map(|f| f.to_le_bytes()).collect();
110 self.add_tensor(name, TensorDType::F32, shape, &bytes)
111 }
112
113 pub fn add_raw_f16_tensor(
119 &mut self,
120 name: impl Into<String>,
121 shape: Vec<usize>,
122 data: &[u8],
123 is_bf16: bool,
124 ) -> Result<(), V2FormatError> {
125 let dtype = if is_bf16 {
126 TensorDType::BF16
127 } else {
128 TensorDType::F16
129 };
130 self.add_tensor(name, dtype, shape, data)
131 }
132
133 pub fn add_f16_tensor(
141 &mut self,
142 name: impl Into<String>,
143 shape: Vec<usize>,
144 data: &[f32],
145 ) -> Result<(), V2FormatError> {
146 let bytes: Vec<u8> = data
147 .iter()
148 .flat_map(|&f| f32_to_f16(f).to_le_bytes())
149 .collect();
150 self.add_tensor(name, TensorDType::F16, shape, &bytes)
151 }
152
153 pub fn add_q8_tensor(
162 &mut self,
163 name: impl Into<String>,
164 shape: Vec<usize>,
165 data: &[f32],
166 ) -> Result<(), V2FormatError> {
167 if data.is_empty() {
168 return self.add_tensor(name, TensorDType::AprQ8, shape, &[]);
169 }
170 let max_abs = data.iter().map(|v| v.abs()).fold(0.0f32, f32::max);
171 let scale = if max_abs == 0.0 { 1.0 } else { max_abs / 127.0 };
172 let mut bytes = Vec::with_capacity(4 + data.len());
173 bytes.extend_from_slice(&scale.to_le_bytes());
174 for &v in data {
175 let q = (v / scale).round().clamp(-127.0, 127.0) as i8;
176 bytes.push(q as u8);
177 }
178 self.add_tensor(name, TensorDType::AprQ8, shape, &bytes)
179 }
180
181 pub fn add_q4_tensor(
191 &mut self,
192 name: impl Into<String>,
193 shape: Vec<usize>,
194 data: &[f32],
195 ) -> Result<(), V2FormatError> {
196 const BLOCK_SIZE: usize = 32;
197 if data.is_empty() {
198 return self.add_tensor(name, TensorDType::AprQ4, shape, &[]);
199 }
200 let num_blocks = data.len().div_ceil(BLOCK_SIZE);
201 let mut bytes = Vec::with_capacity(num_blocks * 18);
202 for block_start in (0..data.len()).step_by(BLOCK_SIZE) {
203 let block_end = (block_start + BLOCK_SIZE).min(data.len());
204 let block = &data[block_start..block_end];
205 let max_abs = block.iter().map(|v| v.abs()).fold(0.0f32, f32::max);
206 let scale = if max_abs == 0.0 { 1.0 } else { max_abs / 7.0 };
207 bytes.extend_from_slice(&f32_to_f16(scale).to_le_bytes());
208 let mut packed_buf = [0u8; 16];
209 let mut packed_idx = 0;
210 for (i, &v) in block.iter().enumerate() {
211 let q = (v / scale).round().clamp(-8.0, 7.0) as i8;
212 let nibble = ((q + 8) as u8) & 0x0F;
213 if i % 2 == 0 {
214 packed_buf[packed_idx] = nibble;
215 } else {
216 packed_buf[packed_idx] |= nibble << 4;
217 packed_idx += 1;
218 }
219 }
220 bytes.extend_from_slice(&packed_buf);
221 }
222 self.add_tensor(name, TensorDType::AprQ4, shape, &bytes)
223 }
224
225 pub fn add_q4k_raw_tensor(
233 &mut self,
234 name: impl Into<String>,
235 shape: Vec<usize>,
236 raw_data: &[u8],
237 ) -> Result<(), V2FormatError> {
238 self.add_tensor(name, TensorDType::Q4K, shape, raw_data)
239 }
240
241 #[must_use]
243 pub fn tensor_count(&self) -> usize {
244 self.index_entries.len()
245 }
246
247 #[must_use]
249 pub fn data_bytes_written(&self) -> u64 {
250 self.data_offset
251 }
252
253 pub fn finalize(mut self, output_path: &std::path::Path) -> Result<(), V2FormatError> {
262 use std::io::{BufWriter, Seek, SeekFrom};
263
264 let metadata_bytes = self.metadata.to_json()?;
266 let metadata_padded_size = align_64(metadata_bytes.len());
267
268 self.index_entries.sort_by(|a, b| a.name.cmp(&b.name));
272
273 let mut tensor_index_bytes = Vec::new();
275 for entry in &self.index_entries {
276 tensor_index_bytes.extend_from_slice(&entry.to_bytes());
277 }
278 let tensor_index_padded_size = align_64(tensor_index_bytes.len());
279
280 let metadata_offset = HEADER_SIZE_V2;
282 let tensor_index_offset = metadata_offset + metadata_padded_size;
283 let data_section_offset = tensor_index_offset + tensor_index_padded_size;
284
285 self.header.tensor_count = self.index_entries.len() as u32;
287 self.header.metadata_offset = metadata_offset as u64;
288 self.header.metadata_size = metadata_bytes.len() as u32;
289 self.header.tensor_index_offset = tensor_index_offset as u64;
290 self.header.data_offset = data_section_offset as u64;
291 self.header.update_checksum();
292
293 self.data_writer
295 .flush()
296 .map_err(|e| V2FormatError::IoError(e.to_string()))?;
297 let mut data_file = self
298 .data_writer
299 .into_inner()
300 .map_err(|e| V2FormatError::IoError(e.to_string()))?;
301 data_file
302 .seek(SeekFrom::Start(0))
303 .map_err(|e| V2FormatError::IoError(e.to_string()))?;
304
305 let out_file = std::fs::File::create(output_path).map_err(|e| {
307 V2FormatError::IoError(format!("Failed to create {}: {e}", output_path.display()))
308 })?;
309 let mut out = BufWriter::new(out_file);
310
311 out.write_all(&self.header.to_bytes())
313 .map_err(|e| V2FormatError::IoError(e.to_string()))?;
314
315 out.write_all(&metadata_bytes)
317 .map_err(|e| V2FormatError::IoError(e.to_string()))?;
318 let metadata_pad = metadata_padded_size - metadata_bytes.len();
319 if metadata_pad > 0 {
320 out.write_all(&vec![0u8; metadata_pad])
321 .map_err(|e| V2FormatError::IoError(e.to_string()))?;
322 }
323
324 out.write_all(&tensor_index_bytes)
326 .map_err(|e| V2FormatError::IoError(e.to_string()))?;
327 let index_pad = tensor_index_padded_size - tensor_index_bytes.len();
328 if index_pad > 0 {
329 out.write_all(&vec![0u8; index_pad])
330 .map_err(|e| V2FormatError::IoError(e.to_string()))?;
331 }
332
333 let mut buf = vec![0u8; 256 * 1024];
335 loop {
336 let n = data_file
337 .read(&mut buf)
338 .map_err(|e| V2FormatError::IoError(e.to_string()))?;
339 if n == 0 {
340 break;
341 }
342 out.write_all(&buf[..n])
343 .map_err(|e| V2FormatError::IoError(e.to_string()))?;
344 }
345
346 out.flush()
348 .map_err(|e| V2FormatError::IoError(e.to_string()))?;
349 drop(out);
350
351 let footer_crc = streaming_crc32_file(output_path)?;
352
353 let mut file = std::fs::OpenOptions::new()
354 .append(true)
355 .open(output_path)
356 .map_err(|e| V2FormatError::IoError(e.to_string()))?;
357 file.write_all(&footer_crc.to_le_bytes())
358 .map_err(|e| V2FormatError::IoError(e.to_string()))?;
359
360 Ok(())
361 }
362}
363
364fn streaming_crc32_file(path: &std::path::Path) -> Result<u32, V2FormatError> {
366 const TABLE: [u32; 256] = {
367 let mut table = [0u32; 256];
368 let mut i = 0;
369 while i < 256 {
370 let mut c = i as u32;
371 let mut j = 0;
372 while j < 8 {
373 if c & 1 != 0 {
374 c = (c >> 1) ^ 0xEDB8_8320;
375 } else {
376 c >>= 1;
377 }
378 j += 1;
379 }
380 table[i] = c;
381 i += 1;
382 }
383 table
384 };
385
386 let mut file = std::fs::File::open(path).map_err(|e| V2FormatError::IoError(e.to_string()))?;
387 let mut crc = 0xFFFF_FFFF_u32;
388 let mut buf = vec![0u8; 256 * 1024];
389 loop {
390 let n = file
391 .read(&mut buf)
392 .map_err(|e| V2FormatError::IoError(e.to_string()))?;
393 if n == 0 {
394 break;
395 }
396 for &byte in &buf[..n] {
397 let idx = ((crc ^ u32::from(byte)) & 0xFF) as usize;
398 crc = (crc >> 8) ^ TABLE[idx];
399 }
400 }
401 Ok(!crc)
402}