Skip to main content

ruvector_temporal_tensor/
segment.rs

1//! Segment binary format: encode and decode.
2//!
3//! Format (little-endian):
4//!
5//! ```text
6//! [magic:4][version:1][bits:1][group_len:4][tensor_len:4][frames:4]
7//! [scale_count:4][scales:2*S][data_len:4][data:D]
8//! ```
9//!
10//! Magic: `0x43545154` ("TQTC" in LE). Header is 26 bytes before scales.
11
12use crate::quantizer;
13
14/// Segment magic number: `"TQTC"` in little-endian.
15pub const MAGIC: u32 = 0x4354_5154;
16/// Current segment format version.
17pub const VERSION: u8 = 1;
18/// Minimum valid segment size in bytes (header fields + data_len, no scales/data).
19pub const HEADER_SIZE: usize = 26;
20
21/// Encode a segment from metadata, scales, and packed data.
22pub fn encode(
23    bits: u8,
24    group_len: u32,
25    tensor_len: u32,
26    frame_count: u32,
27    scales: &[u16],
28    data: &[u8],
29    out: &mut Vec<u8>,
30) {
31    out.clear();
32    let estimated = HEADER_SIZE + scales.len() * 2 + data.len();
33    out.reserve(estimated);
34
35    // Header
36    out.extend_from_slice(&MAGIC.to_le_bytes());
37    out.push(VERSION);
38    out.push(bits);
39    out.extend_from_slice(&group_len.to_le_bytes());
40    out.extend_from_slice(&tensor_len.to_le_bytes());
41    out.extend_from_slice(&frame_count.to_le_bytes());
42
43    // Scales
44    let scale_count = scales.len() as u32;
45    out.extend_from_slice(&scale_count.to_le_bytes());
46    for &s in scales {
47        out.extend_from_slice(&s.to_le_bytes());
48    }
49
50    // Data
51    let data_len = data.len() as u32;
52    out.extend_from_slice(&data_len.to_le_bytes());
53    out.extend_from_slice(data);
54}
55
56/// Decoded segment header.
57#[derive(Debug, Clone)]
58pub struct SegmentHeader {
59    pub bits: u8,
60    pub group_len: u32,
61    pub tensor_len: u32,
62    pub frame_count: u32,
63    pub scale_count: u32,
64}
65
66/// Decode a segment, returning all frames as f32 values.
67pub fn decode(segment: &[u8], out: &mut Vec<f32>) {
68    out.clear();
69    if segment.len() < HEADER_SIZE {
70        return;
71    }
72
73    let mut off = 0;
74
75    let magic = read_u32_le(segment, &mut off);
76    if magic != MAGIC {
77        return;
78    }
79
80    let version = segment[off];
81    off += 1;
82    if version != VERSION {
83        return;
84    }
85
86    let bits = segment[off];
87    off += 1;
88
89    let group_len = read_u32_le(segment, &mut off);
90    let tensor_len = read_u32_le(segment, &mut off);
91    let frame_count = read_u32_le(segment, &mut off);
92    let scale_count = read_u32_le(segment, &mut off);
93
94    // Read scales
95    let scales_end = off + (scale_count as usize) * 2;
96    if scales_end > segment.len() {
97        return;
98    }
99    let mut scales = Vec::with_capacity(scale_count as usize);
100    for _ in 0..scale_count {
101        scales.push(read_u16_le(segment, &mut off));
102    }
103
104    // Read data
105    if off + 4 > segment.len() {
106        return;
107    }
108    let data_len = read_u32_le(segment, &mut off) as usize;
109    if off + data_len > segment.len() {
110        return;
111    }
112    let data = &segment[off..off + data_len];
113
114    // Convert scales to f32 once, then dequantize via the optimized path
115    let scales_f32 = quantizer::scales_to_f32(&scales);
116    quantizer::dequantize_f32(
117        data,
118        &scales_f32,
119        group_len as usize,
120        bits,
121        tensor_len as usize,
122        frame_count as usize,
123        out,
124    );
125}
126
127/// Parse only the segment header (no data decoding).
128pub fn parse_header(segment: &[u8]) -> Option<SegmentHeader> {
129    if segment.len() < HEADER_SIZE {
130        return None;
131    }
132    let mut off = 0;
133    let magic = read_u32_le(segment, &mut off);
134    if magic != MAGIC {
135        return None;
136    }
137    let version = segment[off];
138    off += 1;
139    if version != VERSION {
140        return None;
141    }
142    let bits = segment[off];
143    off += 1;
144    let group_len = read_u32_le(segment, &mut off);
145    let tensor_len = read_u32_le(segment, &mut off);
146    let frame_count = read_u32_le(segment, &mut off);
147    let scale_count = read_u32_le(segment, &mut off);
148
149    Some(SegmentHeader {
150        bits,
151        group_len,
152        tensor_len,
153        frame_count,
154        scale_count,
155    })
156}
157
158/// Compute the compression ratio for a segment: raw f32 bytes / segment bytes.
159///
160/// Returns `0.0` if the segment is empty or has no frames.
161pub fn compression_ratio(segment: &[u8]) -> f32 {
162    match parse_header(segment) {
163        Some(h) if h.frame_count > 0 => {
164            let raw = h.tensor_len as usize * h.frame_count as usize * 4;
165            raw as f32 / segment.len() as f32
166        }
167        _ => 0.0,
168    }
169}
170
171/// Decode a single frame by index from a segment.
172///
173/// Returns `None` if the segment is invalid or `frame_idx` is out of range.
174pub fn decode_single_frame(segment: &[u8], frame_idx: usize) -> Option<Vec<f32>> {
175    let header = parse_header(segment)?;
176    if frame_idx >= header.frame_count as usize {
177        return None;
178    }
179
180    // Skip past the fixed header fields (magic + version + bits + group_len +
181    // tensor_len + frame_count + scale_count = 4+1+1+4+4+4+4 = 22 bytes).
182    let mut off = 22usize;
183    let scale_count = header.scale_count as usize;
184
185    // Read scales
186    let scales_end = off + scale_count * 2;
187    if scales_end > segment.len() {
188        return None;
189    }
190    let mut scales_f16 = Vec::with_capacity(scale_count);
191    for _ in 0..scale_count {
192        scales_f16.push(read_u16_le(segment, &mut off));
193    }
194    let scales_f32 = quantizer::scales_to_f32(&scales_f16);
195
196    // Read data section
197    if off + 4 > segment.len() {
198        return None;
199    }
200    let data_len = read_u32_le(segment, &mut off) as usize;
201    if off + data_len > segment.len() {
202        return None;
203    }
204    let data = &segment[off..off + data_len];
205
206    // Compute byte offset for the requested frame
207    let tensor_len = header.tensor_len as usize;
208    let bits = header.bits;
209    let bits_per_frame = tensor_len * bits as usize;
210    let bytes_per_frame = bits_per_frame.div_ceil(8);
211
212    let frame_start = frame_idx * bytes_per_frame;
213    if frame_start + bytes_per_frame > data.len() {
214        return None;
215    }
216    let frame_data = &data[frame_start..frame_start + bytes_per_frame];
217
218    let mut out = Vec::new();
219    quantizer::dequantize_f32(
220        frame_data,
221        &scales_f32,
222        header.group_len as usize,
223        bits,
224        tensor_len,
225        1,
226        &mut out,
227    );
228    Some(out)
229}
230
231#[inline]
232fn read_u32_le(bytes: &[u8], offset: &mut usize) -> u32 {
233    let o = *offset;
234    let arr = [bytes[o], bytes[o + 1], bytes[o + 2], bytes[o + 3]];
235    *offset = o + 4;
236    u32::from_le_bytes(arr)
237}
238
239fn read_u16_le(bytes: &[u8], offset: &mut usize) -> u16 {
240    let o = *offset;
241    let arr = [bytes[o], bytes[o + 1]];
242    *offset = o + 2;
243    u16::from_le_bytes(arr)
244}
245
246#[cfg(test)]
247mod tests {
248    use super::*;
249    use crate::quantizer;
250
251    #[test]
252    fn test_encode_decode_roundtrip() {
253        let frame: Vec<f32> = (0..128).map(|i| (i as f32 - 64.0) * 0.1).collect();
254        let group_len = 64usize;
255        let bits = 8u8;
256
257        let scales = quantizer::compute_scales(&frame, group_len, bits);
258        let mut packed = Vec::new();
259        quantizer::quantize_and_pack(&frame, &scales, group_len, bits, &mut packed);
260
261        let mut seg = Vec::new();
262        encode(
263            bits,
264            group_len as u32,
265            frame.len() as u32,
266            1,
267            &scales,
268            &packed,
269            &mut seg,
270        );
271
272        let mut decoded = Vec::new();
273        decode(&seg, &mut decoded);
274
275        assert_eq!(decoded.len(), frame.len());
276        for (i, (&orig, &dec)) in frame.iter().zip(decoded.iter()).enumerate() {
277            let err = (orig - dec).abs();
278            assert!(err < 0.1, "i={i} orig={orig} dec={dec} err={err}");
279        }
280    }
281
282    #[test]
283    fn test_magic_validation() {
284        let mut decoded = Vec::new();
285        decode(&[0, 0, 0, 0], &mut decoded);
286        assert!(decoded.is_empty()); // Wrong magic
287    }
288
289    #[test]
290    fn test_parse_header() {
291        let frame = vec![1.0f32; 64];
292        let scales = quantizer::compute_scales(&frame, 64, 7);
293        let mut packed = Vec::new();
294        quantizer::quantize_and_pack(&frame, &scales, 64, 7, &mut packed);
295
296        let mut seg = Vec::new();
297        encode(7, 64, 64, 1, &scales, &packed, &mut seg);
298
299        let header = parse_header(&seg).unwrap();
300        assert_eq!(header.bits, 7);
301        assert_eq!(header.group_len, 64);
302        assert_eq!(header.tensor_len, 64);
303        assert_eq!(header.frame_count, 1);
304    }
305
306    #[test]
307    fn test_multi_frame_roundtrip() {
308        let group_len = 32usize;
309        let bits = 5u8;
310        let tensor_len = 64;
311
312        let frame1: Vec<f32> = (0..tensor_len).map(|i| (i as f32) * 0.1).collect();
313        let frame2: Vec<f32> = (0..tensor_len).map(|i| (i as f32) * 0.09).collect();
314
315        let scales = quantizer::compute_scales(&frame1, group_len, bits);
316        let mut packed = Vec::new();
317        quantizer::quantize_and_pack(&frame1, &scales, group_len, bits, &mut packed);
318        quantizer::quantize_and_pack(&frame2, &scales, group_len, bits, &mut packed);
319
320        let mut seg = Vec::new();
321        encode(
322            bits,
323            group_len as u32,
324            tensor_len as u32,
325            2,
326            &scales,
327            &packed,
328            &mut seg,
329        );
330
331        let mut decoded = Vec::new();
332        decode(&seg, &mut decoded);
333        assert_eq!(decoded.len(), tensor_len * 2);
334    }
335}