Skip to main content

cubecl_tile/tile/kind/memory/view/
decoded.rs

1//! The decoding read: a tile's window served into registers a line at a time, each line decoded
2//! as [`dequantize_from`](Tile::dequantize_from) writes it ([`Tile::decoded`]).
3
4use std::marker::PhantomData;
5
6use cubecl::prelude::*;
7
8use crate::*;
9
10/// The lines of a tile's window at `O`, `W` values a line, each read decoded ([`Tile::decoded`]).
11///
12/// A line is named by its row, the window's lines grouped by every axis but the innermost and
13/// counted row-major, and its place in the row. Every line of a row shares the row's address and
14/// scale, so a reader taking several lines of one row computes them once.
15#[derive(CubeType, Clone)]
16pub struct Decoded<O: Float, W: Size> {
17    window: Tile,
18    #[cube(comptime)]
19    line: PhantomData<(O, W)>,
20}
21
22#[cube]
23impl<O: Float, W: Size> Decoded<O, W> {
24    /// The `line`-th line of row `row`, masked where it overhangs the operand.
25    pub fn read(&self, row: usize, line: usize) -> Vector<O, W> {
26        self.read_under(row, line, comptime!(Guard::Checked))
27    }
28
29    /// The `line`-th line of row `row`, untested: the reader proved it in bounds, so reading past
30    /// them is out of bounds, not masked. What a reader that tests each line itself takes, so the
31    /// read does not test it again.
32    ///
33    /// # Panics
34    ///
35    /// As the kernel expands, on a window with a [`Boundary::Clamp`](crate::kind::Boundary::Clamp)
36    /// axis, whose reads must be clamped.
37    pub fn read_unchecked(&self, row: usize, line: usize) -> Vector<O, W> {
38        let provable = self.window.guard_provable();
39        comptime!(assert!(
40            provable,
41            "Decoded::read_unchecked: a clamped window's reads are clamped, never untested"
42        ));
43        self.read_under(row, line, comptime!(Guard::Proved))
44    }
45
46    fn read_under(&self, row: usize, line: usize, #[comptime] guard: Guard) -> Vector<O, W> {
47        let packing = self.window.packing();
48        match comptime!(packing) {
49            Packing::Plain => read_plain_line::<O, W>(&self.window, row, line, guard),
50            Packing::Packed { .. } => read_packed_line::<O, W>(&self.window, row, line, guard),
51        }
52    }
53}
54
55#[cube]
56impl Tile {
57    /// This tile's window read into registers a line at a time, `W` values a line, each served
58    /// at `O` decoded as [`dequantize_from`](Tile::dequantize_from) writes it: its fields
59    /// unpacked, looked up in the table they index, under the scales they carry. What a unit
60    /// reading the lines it needs straight out of memory takes, with no stage to decode into.
61    ///
62    /// Plain values under no scale read as [`flat`](Tile::flat) reads them. Packed values are read
63    /// a word a load, so they are bound one word wide; a line takes the words it spans, or the
64    /// fields it needs of one, and their scale, which must not vary along the line.
65    ///
66    /// # Panics
67    ///
68    /// As the kernel expands: plain values under scales, which only a copy decodes; packed ones
69    /// bound wider than a word, or whose scales vary along a line.
70    pub fn decoded<O: Float, W: Size>(&self) -> Decoded<O, W> {
71        let (packing, scaled) = (self.packing(), self.factor.scaled());
72        comptime!(assert!(
73            packing != Packing::Plain || !scaled,
74            "Tile::decoded: plain values under scales are decoded by a copy (`dequantize_from`)"
75        ));
76        Decoded::<O, W> {
77            window: self.clone(),
78            line: PhantomData,
79        }
80    }
81}
82
83/// The `line`-th line of row `row` of a plain `window`, read under `guard`.
84#[cube]
85fn read_plain_line<O: Float, W: Size>(
86    window: &Tile,
87    row: usize,
88    line: usize,
89    #[comptime] guard: Guard,
90) -> Vector<O, W> {
91    let space = comptime!(window.place.space.clone());
92    let lines = comptime!(space.extent_at(space.rank() - 1)) / W::value();
93    let values = window.flat::<O, W>();
94    match comptime!(guard) {
95        Guard::Checked => values.read(row * lines + line),
96        Guard::Proved => values.read_unchecked(row * lines + line),
97    }
98}
99
100/// The `line`-th line of row `row` of a packed `window`, `W` values from the row's start: the
101/// words it spans, or the fields it needs of one, each field looked up in the table it indexes
102/// where it has one, and the line multiplied by its scale in `f32`, rounded to `O` once. Its
103/// words are read under `guard`.
104#[cube]
105fn read_packed_line<O: Float, W: Size>(
106    window: &Tile,
107    row: usize,
108    line: usize,
109    #[comptime] guard: Guard,
110) -> Vector<O, W> {
111    let space = comptime!(window.place.space.clone());
112    let rank = comptime!(space.rank());
113    let along = comptime!(space.axis_at(rank - 1));
114    let packing = window.packing();
115    let field = comptime!(match packing {
116        Packing::Packed { field } => field,
117        Packing::Plain => unreachable!("Tile::decoded serves plain values as they lie"),
118    });
119    let (bits, per_word) = comptime!((field.size_bits() as u32, field.per_word()));
120    let width = W::value();
121    let load = window.vector_tile();
122    comptime!(assert!(
123        load.extents() == [(along, per_word)],
124        "Tile::decoded: packed values are read a word a load, along {along:?}; this tile loads \
125         {:?}. Bind it one word wide",
126        load.extents()
127    ));
128    let varies = window.factor.varies_along(comptime!(along));
129    comptime!(assert!(
130        width == 1 || !varies,
131        "Tile::decoded: a {width}-wide line runs along {along:?}, which the scales vary along, so \
132         one line lies under several scales"
133    ));
134    // A line holds whole words, or sits inside one: both are powers of two.
135    let fields = comptime!(core::cmp::min(width, per_word));
136    let words_per_line = comptime!(width / fields);
137    let size!(NF) = fields;
138    let mem = window.mem("Tile::decoded");
139    let looks_up = mem.codebook.present();
140    let scaled = window.factor.scaled();
141    let words = window.nd_words::<Const<1>>(guard);
142    // The row's coordinates, every axis but the innermost; then where the line starts along it.
143    let rows = Coords::constant(comptime!(
144        (0..rank - 1)
145            .map(|p| space.extent_at(p))
146            .collect::<Vec<_>>()
147    ));
148    let mut first = rows.unravel(row as u32);
149    let along_first = (line * width) as u32;
150    first.push(along_first);
151    // Where the line starts in its word: zero where it spans whole words.
152    let shift = (along_first % comptime!(per_word as u32)) * bits;
153
154    let mut values = Vector::<f32, W>::empty();
155    #[unroll]
156    for w in 0..words_per_line {
157        let mut at = Coords::<u32>::new();
158        #[unroll]
159        for p in 0..rank {
160            if comptime!(p == rank - 1) {
161                at.push(first.at(p) + comptime!((w * per_word) as u32));
162            } else {
163                at.push(first.at(p));
164            }
165        }
166        let mut word = words.read(load.index(&at, &space)).extract(0usize);
167        if comptime!(fields < per_word) {
168            word >>= shift;
169        }
170        let word = Vector::<u32, Const<1>>::new(word);
171        if comptime!(looks_up) {
172            let indices = unpack_line::<u32, Const<1>, NF>(word, comptime!(field));
173            #[unroll]
174            for j in 0..fields {
175                values.insert(w * fields + j, mem.codebook.entry(indices.extract(j)));
176            }
177        } else {
178            let fields_of_word = unpack_line::<f32, Const<1>, NF>(word, comptime!(field));
179            #[unroll]
180            for j in 0..fields {
181                values.insert(w * fields + j, fields_of_word.extract(j));
182            }
183        }
184    }
185    if comptime!(scaled) {
186        let scale = window
187            .factor
188            .at_coords(&first, comptime!(space.clone()), guard);
189        values *= Vector::<f32, W>::cast_from(scale);
190    }
191    Vector::<O, W>::cast_from(values)
192}