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}