Skip to main content

cubecl_tile/launch/
refusal.rs

1//! [`Refusal`]: why a launch cannot run as described, or an operand cannot be bound to it.
2
3use core::fmt::{self, Display, Formatter};
4
5use cubecl::zspace::Tiling;
6
7use crate::{Axis, LineMisfit};
8
9/// Why a launch cannot run as described, or an operand cannot be bound to it.
10#[derive(Clone, PartialEq, Eq, Debug)]
11pub enum Refusal {
12    /// [`in_stride_order`](super::Arg::in_stride_order) on a gathered or storage-tiled operand.
13    StrideOrderOfAStatedLayout,
14    /// A gathered mapping over a storage-tiled binding.
15    GatherOfATiledBinding(Tiling),
16    /// [`gathered`](super::Arg::gathered) combined with `axes` or `batches`.
17    GatherWithLabels,
18    /// The mapping addresses `mapped` dims but the operand has `rank`.
19    GatherRankMismatch { mapped: usize, rank: usize },
20    /// The mapping spans an axis the launched space does not have.
21    GatherOfAnUnknownAxis(Axis),
22    /// The operand's rank is smaller than its labelled block of dims.
23    RankBelowLabels { rank: usize, block: usize },
24    /// More leading dims than batch axes to label them.
25    UnlabelledBatchDims { dims: usize, axes: usize },
26    /// An operand must address at least one coordinate.
27    NoCoordinate,
28    /// The buffer cannot be served `width` wide.
29    WidthNotServed { width: usize, why: LineMisfit },
30    /// The innermost axis is served in vector lines but is not provably in bounds.
31    UncheckableVectorEdge,
32    /// A gathered mapping read in vector lines whose innermost physical axis does not step by the
33    /// operand's last logical axis at coefficient 1.
34    GatherInnermostNotInLines,
35    /// A gathered mapping that reads `axis` off several physical axes: the operand is
36    /// storage-tiled, or reads one axis from two places.
37    GatherAxisAddressedTwice(Axis),
38    /// A grid read off the levels ([`Grid::FromLevels`](crate::launch::Grid::FromLevels)) needs a
39    /// units level of one unit or the whole `plane`; this one holds `units`.
40    GridNotReadOffLevels { units: u32, plane: u32 },
41    /// A cube of `units` units, past the `most` the device holds.
42    CubePastDevice { units: u32, most: u32 },
43    /// The kernel works in planes, and the device runs no plane operations.
44    PlanesUnavailable,
45    /// The kernel needs one plane width, and the device's varies between `min` and `max`.
46    ImprecisePlaneDim { min: u32, max: u32 },
47    /// A routine declines a problem it does not serve, for the reason `why`.
48    Declined { why: &'static str },
49    /// A cube shaped `shape` along x, y and z, past the `most` the device spawns along one of them:
50    /// a cube legal in units can still be too long on an axis.
51    CubeShapePastDevice {
52        shape: (u32, u32, u32),
53        most: (u32, u32, u32),
54    },
55    /// A cube with a zero axis, which spawns no units.
56    NoUnits,
57    /// A grid with a zero axis, which spawns no cubes: the runtime returns without dispatching
58    /// anything, and the outputs keep whatever they held.
59    NoCubes,
60    /// A grid of `count` cubes along x, y and z, past the `most` the device launches along one of
61    /// them.
62    GridPastDevice {
63        count: (u32, u32, u32),
64        most: (u32, u32, u32),
65    },
66    /// A cube holding `bytes` of shared memory, past the `most` the device gives one.
67    SharedMemoryPastDevice { bytes: usize, most: usize },
68    /// `fillers` planes are set aside to fill a walk's stages, and the device has no barrier type
69    /// for the two roles to meet on.
70    FillersWithoutBarriers { fillers: u32 },
71    /// A TMA box edge past the descriptor's per-axis limit `most`.
72    BoxPastDescriptor {
73        axis: Axis,
74        edge: usize,
75        most: usize,
76    },
77    /// A TMA box lands its rows whole, one after the other, and the stage's blocks split the rows
78    /// along `axis`, the innermost, or stack `rows` of them where the swizzle repeats every
79    /// `period`.
80    BoxSplitsStageBlocks {
81        axis: Axis,
82        rows: usize,
83        period: usize,
84    },
85    /// A TMA box lands its rows dense, at most swizzled within one span, and the stage keeps them
86    /// as `why` says.
87    RowsNoDescriptorLands { why: &'static str },
88    /// [`indexed`](crate::TileArgLaunch::indexed) over a pool of pages on a gathered or
89    /// storage-tiled operand: a page is resolved coordinate by coordinate over a plain buffer's
90    /// labelled dims.
91    PagingOfAStatedLayout,
92    /// [`indexed`](crate::TileArgLaunch::indexed) over a pool of pages naming an axis no dim of the
93    /// operand is labelled with.
94    PagingOfAnUnlabelledAxis(Axis),
95    /// [`indexed`](crate::TileArgLaunch::indexed) over a pool of pages naming one dim for both the
96    /// pages and their positions.
97    PagingOfOneDim(Axis),
98    /// [`indexed`](crate::TileArgLaunch::indexed) over a pool of pages with the pages along the
99    /// innermost dim, this axis's: a line runs along it, so one line would span pages.
100    PagesAlongALine(Axis),
101    /// [`indexed`](crate::TileArgLaunch::indexed) over a pool of pages on a pool of no pages, or of
102    /// pages of no positions: there is no page for a table entry to read.
103    PagingOfAnEmptyPool,
104    /// [`indexed`](crate::TileArgLaunch::indexed) over a pool of pages on an operand moved by runs
105    /// (a bulk copy, a tensor map): the table resolves one line at a time.
106    PagingMovedByRuns,
107    /// [`indexed`](crate::TileArgLaunch::indexed) over a pool of pages whose scales span its pages
108    /// or positions unplaced: they are a pool of their own, placed by the same table before they
109    /// are stated.
110    ScalesOfAPoolUnpaged,
111    /// [`indexed`](crate::TileArgLaunch::indexed) with a table that is not plain `u32` entries,
112    /// one a line: an entry is a coordinate.
113    IndexTableNotU32,
114    /// [`indexed`](crate::TileArgLaunch::indexed) with a table of `rank` dims keyed by `keys`
115    /// axes: a table holds one dim per key.
116    IndexTableRank { keys: usize, rank: usize },
117    /// [`indexed`](crate::TileArgLaunch::indexed) on a tensor map, or on a gathered, storage-tiled
118    /// or paged operand: an entry moves a window of a plain buffer's labelled dims.
119    IndexOfAStatedLayout,
120    /// [`indexed`](crate::TileArgLaunch::indexed) placing an axis the operand does not span.
121    IndexOfAnAxisNotSpanned(Axis),
122    /// [`indexed`](crate::TileArgLaunch::indexed) placing the innermost axis, this one, which the
123    /// operand reads more than one value a line along: an entry is an element, and a line would
124    /// straddle two of them.
125    IndexAlongALine(Axis),
126    /// [`indexed`](crate::TileArgLaunch::indexed) with no key, or keyed by the axis it places.
127    IndexKeyedByItsAxis(Axis),
128    /// [`Indexing::Run`](crate::Indexing::Run) keyed by this many axes: a run's end is the next
129    /// entry along its one key.
130    RunKeyedByMany(usize),
131    /// An indexed operand under a nest whose first level cutting one of these keys does not cut
132    /// them all: the table is read at one step.
133    IndexKeysCutApart(Vec<Axis>),
134    /// An indexed operand under a nest that cuts the axis its entry places: a coordinate is the
135    /// table's to state, and a run is placed whole before any level cuts it.
136    IndexedAxisCut(Axis),
137    /// [`codebook`](crate::TileArgLaunch::codebook) on values not stored as indices
138    /// ([`Field::Index`](crate::Field::Index)).
139    CodebookOfValuesNotIndices,
140    /// [`codebook`](crate::TileArgLaunch::codebook) with a table that is not a plain one-dim
141    /// buffer read one value a line.
142    CodebookTableNotPlain,
143    /// [`codebook`](crate::TileArgLaunch::codebook) with a table of `entries`, fewer than the
144    /// `2^bits` the indices name.
145    CodebookTableShort { entries: usize, bits: usize },
146}
147
148impl Display for Refusal {
149    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
150        match self {
151            Refusal::StrideOrderOfAStatedLayout => write!(
152                f,
153                "Arg::in_stride_order: the order a buffer steps is read off strides, which a \
154                 gathered mapping states for itself and a storage-tiled binding stores in tiles"
155            ),
156            Refusal::GatherOfATiledBinding(tiling) => write!(
157                f,
158                "Arg::gathered: the mapping addresses the buffer's own dims, so a storage-tiled \
159                 binding ({tiling:?}) has no reading here"
160            ),
161            Refusal::GatherWithLabels => write!(
162                f,
163                "Arg::gathered: the mapping is stated outright, so `axes` and `batches` have \
164                 nothing left to describe"
165            ),
166            Refusal::GatherRankMismatch { mapped, rank } => write!(
167                f,
168                "Arg::gathered: the mapping addresses {mapped} dims but the operand has {rank}"
169            ),
170            Refusal::GatherOfAnUnknownAxis(axis) => write!(
171                f,
172                "Arg::gathered: the mapping spans {axis:?}, which the launched space does not have"
173            ),
174            Refusal::RankBelowLabels { rank, block } => write!(
175                f,
176                "Arg: operand rank {rank} is smaller than its labelled block of {block} dims"
177            ),
178            Refusal::UnlabelledBatchDims { dims, axes } => {
179                write!(f, "Arg: {dims} batch dims but only {axes} batch axes given")
180            }
181            Refusal::NoCoordinate => {
182                write!(f, "Arg: an operand must address at least one coordinate")
183            }
184            Refusal::WidthNotServed { width, why } => {
185                write!(
186                    f,
187                    "Arg::vectorize: this operand cannot be served {width} wide: {why}"
188                )
189            }
190            Refusal::UncheckableVectorEdge => write!(
191                f,
192                "Arg: the innermost axis is served in vector lines but is not provably in bounds \
193                 (it overhangs its tiling, or its map is affine and reaches past any extent stated \
194                 here); serve it scalar, or state BoundaryPolicy::Unchecked if the launch proves \
195                 its vector lines are in bounds"
196            ),
197            Refusal::GatherInnermostNotInLines => write!(
198                f,
199                "Arg::gathered: the innermost physical axis must step by the operand's last \
200                 logical axis at coefficient 1 (it is addressed in vector lines)"
201            ),
202            Refusal::GatherAxisAddressedTwice(axis) => write!(
203                f,
204                "Arg::gathered: logical axis {axis:?} addresses several physical axes, so it is \
205                 either storage-tiled (a gathered operand must be untiled gmem) or read off two \
206                 places at once"
207            ),
208            Refusal::GridNotReadOffLevels { units, plane } => write!(
209                f,
210                "Launcher: a grid read off the levels needs a units level of one unit or the \
211                 whole plane ({plane}), got {units}"
212            ),
213            Refusal::CubePastDevice { units, most } => write!(
214                f,
215                "Launcher: a cube of {units} units, but the device holds at most {most}"
216            ),
217            Refusal::PlanesUnavailable => {
218                write!(
219                    f,
220                    "the kernel works in planes, and the device runs no plane operations"
221                )
222            }
223            Refusal::ImprecisePlaneDim { min, max } => write!(
224                f,
225                "the kernel needs one plane width, and the device's varies from {min} to {max}"
226            ),
227            Refusal::Declined { why } => f.write_str(why),
228            Refusal::CubeShapePastDevice { shape, most } => write!(
229                f,
230                "Launcher: a cube shaped {shape:?}, but the device spawns at most {most:?}"
231            ),
232            Refusal::NoUnits => write!(f, "Launcher: the cube has a zero axis and spawns no units"),
233            Refusal::NoCubes => write!(f, "Launcher: the grid has a zero axis and spawns no cubes"),
234            Refusal::GridPastDevice { count, most } => write!(
235                f,
236                "Launcher: a grid of {count:?} cubes, but the device launches at most {most:?}"
237            ),
238            Refusal::SharedMemoryPastDevice { bytes, most } => write!(
239                f,
240                "Launcher: a cube holding {bytes} bytes of shared memory, but the device gives \
241                 one at most {most}"
242            ),
243            Refusal::FillersWithoutBarriers { fillers } => write!(
244                f,
245                "Launcher: {fillers} plane(s) are set aside to fill a walk's stages, and this \
246                 device carries no barrier type for the two roles to meet on"
247            ),
248            Refusal::BoxPastDescriptor { axis, edge, most } => write!(
249                f,
250                "TMA: {edge} along {axis:?} exceeds the {most}-per-axis box limit"
251            ),
252            Refusal::BoxSplitsStageBlocks { axis, rows, period } => write!(
253                f,
254                "TMA: a box lands its rows whole and in order, and the stage's blocks split \
255                 {axis:?} or stack {rows} rows apiece where the swizzle repeats every {period}"
256            ),
257            Refusal::RowsNoDescriptorLands { why } => write!(
258                f,
259                "TMA: a box lands its rows dense, at most swizzled within a span, and the stage \
260                 holds {why}"
261            ),
262            Refusal::PagingOfAStatedLayout => write!(
263                f,
264                "TileArgLaunch::indexed: a page is resolved coordinate by coordinate over a plain \
265                 buffer's labelled dims, so a gathered mapping or a storage-tiled binding has no \
266                 reading here"
267            ),
268            Refusal::PagingOfAnUnlabelledAxis(axis) => write!(
269                f,
270                "TileArgLaunch::indexed: {axis:?} labels no dim of this operand, so no dim holds \
271                 its pages"
272            ),
273            Refusal::PagingOfOneDim(axis) => write!(
274                f,
275                "TileArgLaunch::indexed: {axis:?} names the pages and their positions both, on one \
276                 dim"
277            ),
278            Refusal::PagesAlongALine(axis) => write!(
279                f,
280                "TileArgLaunch::indexed: the pages along {axis:?} lie on the innermost dim, which \
281                 a line runs along, so a line would span pages"
282            ),
283            Refusal::PagingOfAnEmptyPool => write!(
284                f,
285                "TileArgLaunch::indexed: the pool holds no page, or its pages no position, for a \
286                 table entry to read"
287            ),
288            Refusal::ScalesOfAPoolUnpaged => write!(
289                f,
290                "TileArgLaunch::indexed: the scales of a pool of pages span its pages or \
291                 positions, so they are a pool of their own, placed by the same table before they \
292                 are stated"
293            ),
294            Refusal::PagingMovedByRuns => write!(
295                f,
296                "TileArgLaunch::indexed: a page table is resolved a line at a time, so a delivery \
297                 that moves runs has no reading through it"
298            ),
299            Refusal::CodebookOfValuesNotIndices => write!(
300                f,
301                "TileArgLaunch::codebook: a codebook is read through values stored as indices \
302                 (`Field::Index`)"
303            ),
304            Refusal::CodebookTableNotPlain => write!(
305                f,
306                "TileArgLaunch::codebook: the table is a plain one-dim buffer, one value a line"
307            ),
308            Refusal::CodebookTableShort { entries, bits } => write!(
309                f,
310                "TileArgLaunch::codebook: {bits}-bit indices name {} entries; the table holds \
311                 {entries}",
312                1usize << bits
313            ),
314            Refusal::IndexTableNotU32 => write!(
315                f,
316                "TileArgLaunch::indexed: a table holds plain u32 entries, one a line, each a \
317                 coordinate"
318            ),
319            Refusal::IndexTableRank { keys, rank } => write!(
320                f,
321                "TileArgLaunch::indexed: the table has {rank} dims and {keys} keys; it holds one dim \
322                 per key"
323            ),
324            Refusal::IndexOfAStatedLayout => write!(
325                f,
326                "TileArgLaunch::indexed: an entry moves the window of a plain buffer over its \
327                 labelled dims, which a tensor map, a gathered, storage-tiled or paged operand is not"
328            ),
329            Refusal::IndexOfAnAxisNotSpanned(axis) => write!(
330                f,
331                "TileArgLaunch::indexed: the entry places {axis:?}, which the operand does not span"
332            ),
333            Refusal::IndexAlongALine(axis) => write!(
334                f,
335                "TileArgLaunch::indexed: the entry places {axis:?}, the innermost axis, which the \
336                 operand reads more than one value a line along; bind it one value a line"
337            ),
338            Refusal::IndexKeyedByItsAxis(axis) => write!(
339                f,
340                "TileArgLaunch::indexed: the table is keyed by no axis, or by {axis:?}, the axis its \
341                 entry places"
342            ),
343            Refusal::RunKeyedByMany(keys) => write!(
344                f,
345                "Indexing::Run: a run ends at the next entry along its one key, and this table has \
346                 {keys} keys"
347            ),
348            Refusal::IndexKeysCutApart(keys) => write!(
349                f,
350                "Indexing: the first level cutting one of {keys:?} does not cut them all; the \
351                 table is read at one step"
352            ),
353            Refusal::IndexedAxisCut(axis) => write!(
354                f,
355                "Indexing: a level cuts {axis:?}, the axis the entry places; a coordinate is the \
356                 table's to state, and a run is placed whole before any level cuts it"
357            ),
358        }
359    }
360}
361
362impl std::error::Error for Refusal {}