Skip to main content

cubecl_tile/stage/
base.rs

1//! Where a stage lives and how it lays its cells out ([`StageStorage`]).
2
3use crate::{Axis, ChunkSwizzle, LineBytes, Refusal, Space, StageForm};
4use cubecl::prelude::TensorMapSwizzle;
5
6/// Where a stage lives and how it lays its cells out, stated at
7/// [`Stages::smem`](crate::Stages::smem).
8#[derive(Clone, PartialEq, Eq, Hash, Debug)]
9pub enum StageStorage {
10    /// Shared memory grouped into `block`-sized tiles, one edge per axis of the operation.
11    Tiled {
12        block: Vec<(Axis, usize)>,
13        chunks: RowChunks,
14    },
15    Strided,
16    /// The plane's units, one line per unit.
17    Lines {
18        read: UnitRead,
19    },
20}
21
22/// How a [`Tiled`](StageStorage::Tiled) block lays each row down, in 16-byte chunks.
23#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, serde::Serialize, serde::Deserialize)]
24pub enum RowChunks {
25    /// Each row one contiguous run, in order; readable by `cmma` and TMA.
26    InOrder,
27    /// Each row's chunks XOR-permuted by a row key; only per-unit-addressed readers can read it.
28    Swizzled,
29    /// Each row in order followed by one unused chunk, so rows start on distinct banks.
30    Padded,
31}
32
33impl RowChunks {
34    /// Bytes one chunk holds: the widest read one unit issues.
35    pub const CHUNK_BYTES: usize = 16;
36}
37
38/// How a value reaches the unit asking for it, out of the lines the plane holds.
39#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, serde::Serialize, serde::Deserialize)]
40pub enum UnitRead {
41    /// By plane shuffle: one shuffle per word of the line plus a dynamic pick.
42    Shuffle,
43    /// Through a plane-owned shared-memory window written once per load. Serialized as `Window`.
44    #[serde(rename = "Window")]
45    PlaneShared,
46}
47
48impl StageStorage {
49    /// The swizzle a TMA descriptor moving the whole of a stage over `space` in one box lands its
50    /// rows with, so they lie as this storage keeps them: the stage's lines `vector_size` values of
51    /// `elem_bits` bits, which a line holds whole bytes of (a packed operand's line is its words).
52    /// What the launch builds the descriptor with, and what the stage fill holds the stage to.
53    ///
54    /// # Errors
55    ///
56    /// Blocks splitting a row, which a box lands whole; blocks stacking a number of rows the
57    /// swizzle's period of rows does not divide, whose rows a box keys off their place in the
58    /// stage rather than in the block; padded rows, which a box lands dense, and swizzled rows
59    /// longer than a descriptor swizzle spans.
60    pub fn tma_swizzle(
61        &self,
62        space: &Space,
63        vector_size: usize,
64        elem_bits: usize,
65    ) -> Result<TensorMapSwizzle, Refusal> {
66        assert!(
67            (vector_size * elem_bits).is_multiple_of(8),
68            "StageStorage::tma_swizzle: a line of {vector_size} {elem_bits}-bit values is not whole \
69             bytes"
70        );
71        let form = StageForm::dense(
72            space,
73            vector_size,
74            self.clone(),
75            LineBytes(vector_size * elem_bits / 8),
76        );
77        let swizzle = form
78            .rows
79            .tma_swizzle()
80            .map_err(|why| Refusal::RowsNoDescriptorLands { why })?;
81        let rank = space.rank();
82        let row = space.axis_at(rank - 1);
83        let period = match swizzle {
84            TensorMapSwizzle::None => 1,
85            _ => ChunkSwizzle::ROWS_PER_PERIOD,
86        };
87        for block in self.nesting(space) {
88            let rows = match rank {
89                1 => 1,
90                _ => block.extent_at(rank - 2),
91            };
92            if block.extent_at(rank - 1) < space.extent_at(rank - 1) || !rows.is_multiple_of(period)
93            {
94                return Err(Refusal::BoxSplitsStageBlocks {
95                    axis: row,
96                    rows,
97                    period,
98                });
99            }
100        }
101        Ok(swizzle)
102    }
103
104    /// Bytes a stage over `space` holds laid out this way, its lines `vector_size` values of
105    /// `elem_bits` bits, which a line holds whole bytes of: every line of its cells, and the
106    /// padding after each row. What [`Stages::smem`](crate::Stages::smem) allocates for it.
107    pub fn bytes(&self, space: &Space, vector_size: usize, elem_bits: usize) -> usize {
108        let line = vector_size * elem_bits / 8;
109        StageForm::dense(space, vector_size, self.clone(), LineBytes(line)).cells() * line
110    }
111
112    /// The storage-tiling nesting a stage over `space` gets, coarse to fine; empty is row-major.
113    pub(crate) fn nesting(&self, space: &Space) -> Vec<Space> {
114        match self {
115            StageStorage::Lines { .. } => {
116                panic!("StageStorage::Lines: the plane's units are not shared memory")
117            }
118            StageStorage::Tiled { block, .. } => {
119                let nested = Space::new(
120                    &space
121                        .axes()
122                        .map(|axis| {
123                            let edge = block
124                                .iter()
125                                .find(|&&(a, _)| a == axis)
126                                .unwrap_or_else(|| {
127                                    panic!(
128                                        "StageStorage::Tiled: the block states no edge for {axis:?}"
129                                    )
130                                })
131                                .1;
132                            (axis, edge)
133                        })
134                        .collect::<Vec<_>>(),
135                );
136                if &nested == space {
137                    return Vec::new();
138                }
139                vec![nested]
140            }
141            StageStorage::Strided => Vec::new(),
142        }
143    }
144}