Skip to main content

proxima_primitives/
block_layout.rs

1//! A block layout describes how a run of stored numbers is packed: how many elements share one
2//! scale (and optional min), where each bit plane of an element's code lives, how sub-blocks of
3//! the block carry their own scale and min, and how a code becomes a value,
4//! `(multiplier * code - bias) * scale * sub_scale + min_multiplier * (min * sub_min)`.
5//!
6//! Teaching pointer: [`FloatLayout`] is the data of one stored number; this is the data of a
7//! block of them. One CPU walker (`proxima_gguf::quant::block_layout`) and one MSL emitter
8//! (`omega::msl::layout_render::block_unpack_msl`) read it, so a new block format is a constant
9//! here plus its parity fixture, not a decoder. Where the bits of a code live is an affine map
10//! from the element index: the index is split into mixed-radix digits and each digit moves the
11//! bit address by a stride, which expresses contiguous runs, nibble-split pairs, and the
12//! interleaved lanes of the K formats with one rule.
13
14use crate::float_layout::FloatLayout;
15
16/// One digit of an element index: `radix` values, each moving the bit address by `stride_bits`.
17#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
18pub struct Digit {
19    pub radix: u16,
20    pub stride_bits: u16,
21}
22
23/// The most digits a plane's address map uses (Q3_K needs four).
24pub const MAX_DIGITS: usize = 4;
25
26/// One bit plane of a code: `bits` wide, at the bit address
27/// `base_bits + sum(digit_value * stride_bits)`, with the index split least significant digit
28/// first. Unused digits have radix 1.
29#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
30pub struct Plane {
31    pub bits: u8,
32    pub base_bits: u32,
33    pub digits: [Digit; MAX_DIGITS],
34}
35
36impl Plane {
37    /// The bit address of the plane's value for element `index`.
38    #[must_use]
39    pub const fn address(&self, index: usize) -> usize {
40        let mut remaining = index;
41        let mut address = self.base_bits as usize;
42        let mut position = 0;
43        while position < MAX_DIGITS {
44            let digit = self.digits[position];
45            let radix = digit.radix as usize;
46            address += (remaining % radix) * digit.stride_bits as usize;
47            remaining /= radix;
48            position += 1;
49        }
50        address
51    }
52}
53
54/// A code assembled from one or two planes (the second holds the bits above the first).
55#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
56pub struct Coded {
57    /// Layout of the assembled code: unsigned for offset-binary codes, `TwosComplement` for i8.
58    pub code: FloatLayout,
59    pub low: Plane,
60    pub high: Option<Plane>,
61    /// Subtracted from the multiplied code.
62    pub bias: i16,
63}
64
65/// A scale or min stored once per block: its layout and byte offset.
66#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
67pub struct Field {
68    pub layout: FloatLayout,
69    pub byte_offset: u16,
70}
71
72/// Sub-blocks of the block, each with its own scale code and optionally a min code, indexed by
73/// `element / (elements / count)` and addressed by the planes of the codes.
74#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
75pub struct SubBlocks {
76    pub count: u16,
77    pub scale: Coded,
78    pub min: Option<Coded>,
79}
80
81/// The packing of one block of `elements` codes under a shared scale.
82#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
83pub struct BlockLayout {
84    /// Elements per block; 32 for Q4_0, 256 for the K formats.
85    pub elements: u16,
86    /// Bytes per block; 18 for Q4_0, 144 for Q4_K.
87    pub block_bytes: u16,
88    /// The element code; its `bias` is the offset-binary bias (8 for Q4_0, 32 for Q6_K).
89    pub element: Coded,
90    /// Multiplies the code before the bias: 2 for Q1_0, whose value is `(2 * code - 1) * scale`.
91    pub multiplier: i16,
92    pub scale: Field,
93    pub min: Option<Field>,
94    /// Applied to the min term: 1 where the min is added (Q4_1), -1 where it is subtracted (Q4_K).
95    pub min_multiplier: i8,
96    pub sub: Option<SubBlocks>,
97}
98
99const F16_SCALE: Field = field_at(FloatLayout::F16, 0);
100
101const fn field_at(layout: FloatLayout, byte_offset: u16) -> Field {
102    Field {
103        layout,
104        byte_offset,
105    }
106}
107
108const NO_DIGIT: Digit = Digit {
109    radix: 1,
110    stride_bits: 0,
111};
112
113const fn digit(radix: u16, stride_bits: u16) -> Digit {
114    Digit { radix, stride_bits }
115}
116
117const fn plane(bits: u8, base_bits: u32, digits: [Digit; MAX_DIGITS]) -> Plane {
118    Plane {
119        bits,
120        base_bits,
121        digits,
122    }
123}
124
125const fn coded(code: FloatLayout, low: Plane, high: Option<Plane>, bias: i16) -> Coded {
126    Coded {
127        code,
128        low,
129        high,
130        bias,
131    }
132}
133
134const fn nibbles(base_bits: u32, span: u16) -> Plane {
135    plane(
136        4,
137        base_bits,
138        [digit(span, 8), digit(2, 4), NO_DIGIT, NO_DIGIT],
139    )
140}
141
142const fn run(bits: u8, base_bits: u32, elements: u16) -> Plane {
143    plane(
144        bits,
145        base_bits,
146        [digit(elements, bits as u16), NO_DIGIT, NO_DIGIT, NO_DIGIT],
147    )
148}
149
150const fn linear(
151    elements: u16,
152    block_bytes: u16,
153    element: Coded,
154    multiplier: i16,
155    min: Option<Field>,
156) -> BlockLayout {
157    BlockLayout {
158        elements,
159        block_bytes,
160        element,
161        multiplier,
162        scale: F16_SCALE,
163        min,
164        min_multiplier: 1,
165        sub: None,
166    }
167}
168
169const QK_QS_PAIR_LANES: [Digit; MAX_DIGITS] = [digit(32, 8), digit(2, 4), digit(4, 256), NO_DIGIT];
170
171const K4_SCALE_BASE: u32 = 32;
172
173const fn k4_sub_blocks() -> SubBlocks {
174    let scale = coded(
175        FloatLayout::UINT6,
176        plane(
177            4,
178            K4_SCALE_BASE,
179            [digit(4, 8), digit(2, 64), NO_DIGIT, NO_DIGIT],
180        ),
181        Some(plane(
182            2,
183            K4_SCALE_BASE + 4,
184            [digit(4, 8), digit(2, 2), NO_DIGIT, NO_DIGIT],
185        )),
186        0,
187    );
188    let min = coded(
189        FloatLayout::UINT6,
190        plane(
191            4,
192            K4_SCALE_BASE + 32,
193            [digit(4, 8), digit(2, 36), NO_DIGIT, NO_DIGIT],
194        ),
195        Some(plane(
196            2,
197            K4_SCALE_BASE + 36,
198            [digit(4, 8), digit(2, 2), NO_DIGIT, NO_DIGIT],
199        )),
200        0,
201    );
202    SubBlocks {
203        count: 8,
204        scale,
205        min: Some(min),
206    }
207}
208
209impl BlockLayout {
210    pub const Q4_0: Self = linear(
211        32,
212        18,
213        coded(FloatLayout::UINT4, nibbles(16, 16), None, 8),
214        1,
215        None,
216    );
217    pub const Q4_1: Self = linear(
218        32,
219        20,
220        coded(FloatLayout::UINT4, nibbles(32, 16), None, 0),
221        1,
222        Some(field_at(FloatLayout::F16, 2)),
223    );
224    pub const Q5_0: Self = linear(
225        32,
226        22,
227        coded(
228            FloatLayout::UINT5,
229            nibbles(48, 16),
230            Some(run(1, 16, 32)),
231            16,
232        ),
233        1,
234        None,
235    );
236    pub const Q5_1: Self = linear(
237        32,
238        24,
239        coded(FloatLayout::UINT5, nibbles(64, 16), Some(run(1, 32, 32)), 0),
240        1,
241        Some(field_at(FloatLayout::F16, 2)),
242    );
243    pub const Q8_0: Self = linear(
244        32,
245        34,
246        coded(FloatLayout::INT8, run(8, 16, 32), None, 0),
247        1,
248        None,
249    );
250    pub const Q8_1: Self = linear(
251        32,
252        36,
253        coded(FloatLayout::INT8, run(8, 32, 32), None, 0),
254        1,
255        None,
256    );
257    pub const Q1_0: Self = linear(
258        128,
259        18,
260        coded(FloatLayout::UINT1, run(1, 16, 128), None, 1),
261        2,
262        None,
263    );
264    pub const Q2_0: Self = linear(
265        64,
266        18,
267        coded(FloatLayout::UINT2, run(2, 16, 64), None, 1),
268        1,
269        None,
270    );
271
272    pub const Q2_K: Self = Self {
273        elements: 256,
274        block_bytes: 84,
275        element: coded(
276            FloatLayout::UINT2,
277            plane(
278                2,
279                128,
280                [digit(16, 8), digit(2, 128), digit(4, 2), digit(2, 256)],
281            ),
282            None,
283            0,
284        ),
285        multiplier: 1,
286        scale: field_at(FloatLayout::F16, 80),
287        min: Some(field_at(FloatLayout::F16, 82)),
288        min_multiplier: -1,
289        sub: Some(SubBlocks {
290            count: 16,
291            scale: coded(
292                FloatLayout::UINT4,
293                plane(4, 0, [digit(16, 8), NO_DIGIT, NO_DIGIT, NO_DIGIT]),
294                None,
295                0,
296            ),
297            min: Some(coded(
298                FloatLayout::UINT4,
299                plane(4, 4, [digit(16, 8), NO_DIGIT, NO_DIGIT, NO_DIGIT]),
300                None,
301                0,
302            )),
303        }),
304    };
305    pub const Q3_K: Self = Self {
306        elements: 256,
307        block_bytes: 110,
308        element: coded(
309            FloatLayout::UINT3,
310            plane(
311                2,
312                256,
313                [digit(16, 8), digit(2, 128), digit(4, 2), digit(2, 256)],
314            ),
315            Some(plane(
316                1,
317                0,
318                [digit(16, 8), digit(2, 128), digit(4, 1), digit(2, 4)],
319            )),
320            4,
321        ),
322        multiplier: 1,
323        scale: field_at(FloatLayout::F16, 108),
324        min: None,
325        min_multiplier: 1,
326        sub: Some(SubBlocks {
327            count: 16,
328            scale: coded(
329                FloatLayout::UINT6,
330                plane(4, 768, [digit(4, 8), digit(2, 32), digit(2, 4), NO_DIGIT]),
331                Some(plane(
332                    2,
333                    832,
334                    [digit(4, 8), digit(4, 2), NO_DIGIT, NO_DIGIT],
335                )),
336                32,
337            ),
338            min: None,
339        }),
340    };
341    pub const Q4_K: Self = Self {
342        elements: 256,
343        block_bytes: 144,
344        element: coded(FloatLayout::UINT4, plane(4, 128, QK_QS_PAIR_LANES), None, 0),
345        multiplier: 1,
346        scale: F16_SCALE,
347        min: Some(field_at(FloatLayout::F16, 2)),
348        min_multiplier: -1,
349        sub: Some(k4_sub_blocks()),
350    };
351    pub const Q5_K: Self = Self {
352        elements: 256,
353        block_bytes: 176,
354        element: coded(
355            FloatLayout::UINT5,
356            plane(4, 384, QK_QS_PAIR_LANES),
357            Some(plane(
358                1,
359                128,
360                [digit(32, 8), digit(2, 1), digit(4, 2), NO_DIGIT],
361            )),
362            0,
363        ),
364        multiplier: 1,
365        scale: F16_SCALE,
366        min: Some(field_at(FloatLayout::F16, 2)),
367        min_multiplier: -1,
368        sub: Some(k4_sub_blocks()),
369    };
370    pub const Q6_K: Self = Self {
371        elements: 256,
372        block_bytes: 210,
373        element: coded(
374            FloatLayout::UINT6,
375            plane(
376                4,
377                0,
378                [digit(32, 8), digit(2, 256), digit(2, 4), digit(2, 512)],
379            ),
380            Some(plane(
381                2,
382                1024,
383                [digit(32, 8), digit(4, 2), digit(2, 256), NO_DIGIT],
384            )),
385            32,
386        ),
387        multiplier: 1,
388        scale: field_at(FloatLayout::F16, 208),
389        min: None,
390        min_multiplier: 1,
391        sub: Some(SubBlocks {
392            count: 16,
393            scale: coded(
394                FloatLayout::INT8,
395                plane(8, 1536, [digit(16, 8), NO_DIGIT, NO_DIGIT, NO_DIGIT]),
396                None,
397                0,
398            ),
399            min: None,
400        }),
401    };
402    pub const Q8_K: Self = Self {
403        elements: 256,
404        block_bytes: 292,
405        element: coded(FloatLayout::INT8, run(8, 32, 256), None, 0),
406        multiplier: 1,
407        scale: field_at(FloatLayout::BF32, 0),
408        min: None,
409        min_multiplier: 1,
410        sub: None,
411    };
412}
413
414/// Walks a plane's addresses for consecutive element indexes without dividing: the digits are an
415/// odometer, so each step adds one stride and carries.
416#[derive(Debug, Clone, Copy)]
417pub struct PlaneCursor {
418    plane: Plane,
419    counters: [u16; MAX_DIGITS],
420    address: usize,
421}
422
423impl Plane {
424    /// A cursor positioned at element 0.
425    #[must_use]
426    pub const fn cursor(&self) -> PlaneCursor {
427        PlaneCursor {
428            plane: *self,
429            counters: [0; MAX_DIGITS],
430            address: self.base_bits as usize,
431        }
432    }
433}
434
435impl PlaneCursor {
436    /// The bit address of the current element.
437    #[must_use]
438    pub const fn address(&self) -> usize {
439        self.address
440    }
441
442    /// Moves to the next element index.
443    pub const fn advance(&mut self) {
444        let mut position = 0;
445        while position < MAX_DIGITS {
446            let digit = self.plane.digits[position];
447            let next = self.counters[position] + 1;
448            if next < digit.radix {
449                self.counters[position] = next;
450                self.address += digit.stride_bits as usize;
451                return;
452            }
453            self.counters[position] = 0;
454            self.address -= (digit.radix as usize - 1) * digit.stride_bits as usize;
455            position += 1;
456        }
457    }
458}