Skip to main content

uor_matmul_codec/
tier.rs

1//! The one trait every tier instantiates (§6.1).
2
3use uor_matmul_core::{Alphabet, Bound, Element};
4
5/// A decode from a stored code to alphabet elements.
6///
7/// `Bd` being a parameter of the trait is what makes the alphabet bound a
8/// type-level fact the kernels can rely on without rechecking: a codec's table
9/// is `&[Alphabet<E, Bd>]`, so its image is in the alphabet by construction and
10/// there is nothing for a constructor to validate or reject (§6.2).
11///
12/// `E` is any element type. An integer codec's alphabet is a magnitude bound;
13/// a float codec's is the codebook itself, declared as `Whole<E>` --- the
14/// arena tier is the one instantiation of that, and no tier branches on the
15/// difference.
16pub trait Codec<E: Element, Bd: Bound>: Send + Sync {
17    /// The stored code type.
18    ///
19    /// Any `Copy` type: `i8` for identity, `u8` for a nibble pair or a
20    /// 256-entry codebook's index --- which is what `Book<256, 8, u8>` stores
21    /// --- `u16` for a 65536-entry codebook. The library carries no hardcoded
22    /// code width.
23    type Code: Copy + Send + Sync + 'static;
24
25    /// The most alphabet elements one code can produce.
26    ///
27    /// 1 for scalar, 2 for nibble-packed, 8 for E8, arbitrary in general. This
28    /// is the const that makes a scalar codec the block codec of its
29    /// singletons, so that one identity and one kernel cover both (`CL-MM02`).
30    ///
31    /// It is a *maximum*, not a fixed width: a variable-length codec ---
32    /// a run codec, for one --- produces fewer than `MAX_BLOCK` elements for
33    /// some codes, and [`Codec::decode_into`] returns how many it produced. A
34    /// fixed width would have made run coding a second algorithm rather than a
35    /// tier (S4, S5b).
36    const MAX_BLOCK: usize;
37
38    /// Which tier this is, for reports and for the kappa manifest.
39    const TIER: TierId;
40
41    /// Decode element `i` of the block `code` names. Total for every `i`.
42    ///
43    /// This is the trait's one required method rather than the block decode,
44    /// so that a composing tier --- [`crate::Offset`], [`crate::Runs`],
45    /// [`crate::Transcode`] --- can defer to its inner codec without owning a
46    /// scratch buffer the size of a block. No allocation is possible anywhere
47    /// in this crate, so a required block decode would have forced either a
48    /// hardcoded maximum block size or an `alloc` dependency, and both are
49    /// arbitrary limitations (R7, R8).
50    ///
51    /// There is no code value of `Self::Code` and no `i` for which this fails:
52    /// a codec whose table did not cover its code space could not have been
53    /// constructed, and `i >= BLOCK` is answered by the block's own padding.
54    fn decode_element(&self, code: Self::Code, i: usize) -> Alphabet<E, Bd>;
55
56    /// Is `decode_len` always `MAX_BLOCK`?
57    ///
58    /// True for every fixed-width tier, which is all of them except a run
59    /// codec. It is what lets [`crate::CodedMatrix`] find the codes of row `r`
60    /// by arithmetic rather than by walking, and the difference is not a
61    /// constant factor: a walk makes random access O(row length), and a driver
62    /// that reads one element at a time then runs in O(k^2 n) instead of
63    /// O(m k n). No driver here does: `decode_row_into` and
64    /// [`crate::CodedMatrix::column_walk`] each walk once and carry the cursor,
65    /// and the coded traversal uses the second --- measured at 215x on a 512-row
66    /// run matrix, a factor that grows with the row count.
67    const IS_FIXED_WIDTH: bool = true;
68
69    /// How many elements `code` actually produces.
70    ///
71    /// `MAX_BLOCK` for a fixed-width tier, and possibly fewer for a
72    /// variable-length one. `CK-06` asserts that these counts sum to the
73    /// declared row width on every row, which is the invariant that lets a run
74    /// codec live inside this trait rather than beside it.
75    fn decode_len(&self, _code: Self::Code) -> usize {
76        Self::MAX_BLOCK
77    }
78
79    /// Decode one code, returning how many elements were written.
80    ///
81    /// `out.len() >= decode_len(code)`. Total, side-effect free, and
82    /// allocation-free. The default loops [`Codec::decode_element`]; a tier
83    /// overrides it only when it can produce the same bytes faster, never
84    /// differently.
85    fn decode_into(&self, code: Self::Code, out: &mut [Alphabet<E, Bd>]) -> usize {
86        let n = self.decode_len(code).min(out.len());
87        for (i, slot) in out.iter_mut().enumerate().take(n) {
88            *slot = self.decode_element(code, i);
89        }
90        n
91    }
92
93    /// Bulk path, returning how many elements were written in total.
94    fn decode_seq(&self, codes: &[Self::Code], out: &mut [Alphabet<E, Bd>]) -> usize {
95        let mut at = 0usize;
96        for &code in codes {
97            at += self.decode_into(code, &mut out[at..]);
98        }
99        at
100    }
101}
102
103/// A stored code stream, read as the index stream it already is.
104///
105/// The two widths a borrowed stream can be. The tabulated gather is
106/// monomorphic in the code word --- one dispatch at the traversal's boundary,
107/// never a per-code branch --- so the stream names its width in the type
108/// rather than asking the gather to discover it. Both variants make the same
109/// claim: `index_of(c) == (c as usize) & (CODE_SPACE - 1)` for every `c`,
110/// which `CK-09` asserts of any codec that answers `Some`.
111#[derive(Clone, Copy, PartialEq, Eq, Debug)]
112pub enum IndexStream<'a> {
113    /// A byte stream at a code space no wider than 256.
114    U8(&'a [u8]),
115    /// A two-byte stream, the original width.
116    U16(&'a [u16]),
117}
118
119/// A codec whose code space can be enumerated.
120///
121/// [`Codec`] decodes *from* a code. That is the whole of what a
122/// decode-then-multiply driver needs, and it is why a table indexed by code
123/// cannot be written against [`Codec`] alone: such a table requires the *set* of
124/// codes, and nothing in [`Codec`] hands it over. This trait is that set.
125///
126/// # Why this is separate from [`Codec`]
127///
128/// Not every codec should be tabulated. [`crate::Identity`] over `i32` has a
129/// code space of `2^32`, and a trait that admitted it would invite a table
130/// nobody can hold. Requiring this trait at the tabulated traversal's boundary
131/// makes "this codec cannot be tabulated" a compile-time fact rather than a
132/// runtime refusal.
133///
134/// # Laws
135///
136/// 1. `index_of(code_at(i)) == i` for every `i < CODE_SPACE`.
137/// 2. `index_of` is total: every value of [`Codec::Code`] the codec can hold
138///    lands strictly below `CODE_SPACE`. This is what makes the tabulated
139///    traversal total --- no code can miss the table (`CT-07`).
140///
141/// Both are asserted by `CK-09`. Note what is *not* a law: `code_at` need not be
142/// injective *on decodes*. Two indices may decode alike, in which case the table
143/// carries a dead entry. That is not an error, it is a cost, and `CG-10` reports
144/// the ratio.
145///
146/// # The enumeration is stateless
147///
148/// Both methods are associated functions rather than methods on `&self`, because
149/// the enumeration is a property of the code *space* and not of any particular
150/// table. A composing tier defers to its inner codec's enumeration without
151/// holding an instance of it.
152pub trait Enumerable<E: Element, Bd: Bound>: Codec<E, Bd> {
153    /// The number of distinct codes.
154    ///
155    /// `N` for an `N`-entry [`crate::Grid`] or [`crate::Book`], and the product
156    /// of the sub-spaces for a [`crate::Packed`] byte. Never larger than the
157    /// code type can address: an enumeration wider than its own code type would
158    /// name codes that cannot be stored.
159    const CODE_SPACE: usize;
160
161    /// Whether the decoded book is the sign bit-decomposition:
162    /// `CODE_SPACE` is a power of two and `book[c][t] == 2 * bit(c, t) - 1`
163    /// for every code `c` and every `t` below its log.
164    ///
165    /// The tabulated driver's one reader is the Gray-walk table build, which
166    /// derives the signs from the code index and reads no book at all; an
167    /// answer of `true` is what makes that build a factorization of *this*
168    /// codec's table rather than of some other table with the same declared
169    /// bound. `Sign` answers true and everything else leaves the default ---
170    /// `Ternary`'s book is a bound-1 book too, and it is not this one.
171    const SIGN_BIT_BOOK: bool = false;
172
173    /// The `index`-th code. Total for `index < CODE_SPACE`.
174    fn code_at(index: usize) -> Self::Code;
175
176    /// Where `code` sits in the enumeration.
177    ///
178    /// Total, and total *into the enumeration*: for every value of
179    /// [`Codec::Code`], including values no encoder would produce, the answer is
180    /// below [`Self::CODE_SPACE`]. Both halves are law, and `CK-09` asserts
181    /// them.
182    ///
183    /// That is not politeness. The tabulated traversal reads its table with no
184    /// bounds check and no branch, and this is what makes the read correct: the
185    /// mask that makes it *safe* holds unconditionally, and this is what makes
186    /// the entry it lands on the right one.
187    fn index_of(code: Self::Code) -> usize;
188
189    /// The stored code stream, read as the index stream it already is.
190    ///
191    /// `Some` when the code *addresses* the enumeration: when
192    /// `index_of(c) == (c as usize) & (CODE_SPACE - 1)` for every `c`, which
193    /// needs `CODE_SPACE` to be a power of two and the enumeration to be the
194    /// code type's own order. `None` otherwise --- a [`crate::Packed`] byte, for
195    /// one, whose index is a mixed-radix decomposition of it and not the byte.
196    /// The variant names the code stream's own width: a codec whose codes are
197    /// bytes answers [`IndexStream::U8`], and the traversal dispatches on it
198    /// once, not per code.
199    ///
200    /// This is the same rule [`uor_matmul_core::MatView::row_block`] follows on
201    /// the dense side: *borrow when the layout already holds what is wanted,
202    /// copy otherwise*. A tabulated traversal addresses its table from an index
203    /// stream, and when the operand's own memory is one there is nothing to
204    /// build. Measured at a one-row tile, where the index a traversal would
205    /// materialize is as wide as the table entry it addresses, that pass was
206    /// two thirds of the work.
207    ///
208    /// The default is `None`, so a codec says nothing by saying nothing and the
209    /// traversal builds the stream. `CK-09` asserts the claim of any codec that
210    /// does answer `Some`.
211    fn as_index_stream(codes: &[Self::Code]) -> Option<IndexStream<'_>> {
212        let _ = codes;
213        None
214    }
215}
216
217/// Which tier a codec is.
218///
219/// A label, never a dispatch key on the *answer*: two codecs with different
220/// `TierId`s and equal decodes produce byte-identical output (`CK-05`), and
221/// nothing in the library reads this to decide a value.
222///
223/// One derivation reads it at all --- [`crate::Addressing::of`], asking whether
224/// the tier is one of the two with nothing between a code and an element --- and
225/// what that decides is which *factorizations* exist, which `CD-13` already
226/// holds to the same bytes either way.
227#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
228#[non_exhaustive]
229pub enum TierId {
230    /// Decoding is a validated copy.
231    Identity,
232    /// A lookup table of any code width.
233    Grid,
234    /// Sub-codes unpacked from one stored byte.
235    Packed,
236    /// A codebook of any entry count and any block size.
237    Book,
238    /// Weights in `{-1, +1}`, one bit per element: the codebook is the constant.
239    ///
240    /// `Sign` and the `Packed<Grid<2>,8>` spelling decode the same stream
241    /// (`CK-11`) yet carry different manifest identities --- and that is the
242    /// intended semantics of the kappa label (§"canonical weight manifest"):
243    /// two artifacts that decode alike are still two artifacts.
244    Sign,
245    /// Weights in `{-1, 0, +1}`, two bits per element: the codebook is the
246    /// constant.
247    ///
248    /// `Ternary` and the `Packed<Grid<4>,4>` spelling decode the same stream
249    /// (`CK-12`) yet carry different manifest identities --- and that is the
250    /// intended semantics of the kappa label (§"canonical weight manifest"):
251    /// two artifacts that decode alike are still two artifacts.
252    Ternary,
253    /// `d(c) - z`: asymmetric quantization as a codec composition.
254    Offset,
255    /// Sparse storage as a codec.
256    Runs,
257    /// The composite of two codecs.
258    Transcode,
259    /// A codebook of one artifact's distinct bit patterns, canonicalized.
260    Arena,
261}
262
263impl TierId {
264    /// The token used in the kappa manifest and in reports.
265    pub const fn as_str(self) -> &'static str {
266        match self {
267            Self::Identity => "Identity",
268            Self::Grid => "Grid",
269            Self::Packed => "Packed",
270            Self::Book => "Book",
271            Self::Sign => "Sign",
272            Self::Ternary => "Ternary",
273            Self::Offset => "Offset",
274            Self::Runs => "Runs",
275            Self::Transcode => "Transcode",
276            Self::Arena => "Arena",
277        }
278    }
279}