1use std::{convert::Infallible, fmt::Display};
4
5use crate::{DType, DeviceLocation, Layout, Shape};
6
7#[derive(Debug, Clone)]
9pub struct MatMulUnexpectedStriding {
10 pub lhs_l: Layout,
11 pub rhs_l: Layout,
12 pub bmnk: (usize, usize, usize, usize),
13 pub msg: &'static str,
14}
15
16impl std::fmt::Debug for Error {
17 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
18 write!(f, "{self}")
19 }
20}
21
22#[derive(thiserror::Error)]
24pub enum Error {
25 #[error("{msg}, expected: {expected:?}, got: {got:?}")]
27 UnexpectedDType {
28 msg: &'static str,
29 expected: DType,
30 got: DType,
31 },
32
33 #[error("dtype mismatch in {op}, lhs: {lhs:?}, rhs: {rhs:?}")]
34 DTypeMismatchBinaryOp {
35 lhs: DType,
36 rhs: DType,
37 op: &'static str,
38 },
39
40 #[error("unsupported dtype {0:?} for op {1}")]
41 UnsupportedDTypeForOp(DType, &'static str),
42
43 #[error("no zero scalar for dtype {0:?} (scale format has no exact zero)")]
44 NoZeroScalar(DType),
45 #[error("no one scalar for dtype {0:?}")]
46 NoOneScalar(DType),
47 #[error("value {1} not representable as a scalar of dtype {0:?}")]
48 ScalarUnrepresentable(DType, f64),
49 #[error("dtype {0:?} is a packed element format with no scalar representation")]
50 PackedElementHasNoScalar(DType),
51
52 #[error("{op}: dimension index {dim} out of range for shape {shape:?}")]
54 DimOutOfRange {
55 shape: Box<Shape>,
56 dim: i32,
57 op: &'static str,
58 },
59
60 #[error("{op}: duplicate dim index {dims:?} for shape {shape:?}")]
61 DuplicateDimIndex {
62 shape: Box<Shape>,
63 dims: Vec<usize>,
64 op: &'static str,
65 },
66
67 #[error("unexpected rank, expected: {expected}, got: {got} ({shape:?})")]
69 UnexpectedNumberOfDims {
70 expected: usize,
71 got: usize,
72 shape: Box<Shape>,
73 },
74
75 #[error("{msg}, expected: {expected:?}, got: {got:?}")]
76 UnexpectedShape {
77 msg: String,
78 expected: Box<Shape>,
79 got: Box<Shape>,
80 },
81
82 #[error(
83 "Shape mismatch, got buffer of size {buffer_size} which is compatible with shape {shape:?}"
84 )]
85 ShapeMismatch {
86 buffer_size: usize,
87 shape: Box<Shape>,
88 },
89
90 #[error("shape mismatch in {op}, lhs: {lhs:?}, rhs: {rhs:?}")]
91 ShapeMismatchBinaryOp {
92 lhs: Box<Shape>,
93 rhs: Box<Shape>,
94 op: &'static str,
95 },
96
97 #[error(
98 "shape mismatch in cat for dim {dim}, shape for arg 1: {first_shape:?} shape for arg {n}: {nth_shape:?}"
99 )]
100 ShapeMismatchCat {
101 dim: usize,
102 first_shape: Box<Shape>,
103 n: usize,
104 nth_shape: Box<Shape>,
105 },
106
107 #[error("Cannot divide tensor of shape {shape:?} equally along dim {dim} into {n_parts}")]
108 ShapeMismatchSplit {
109 shape: Box<Shape>,
110 dim: usize,
111 n_parts: usize,
112 },
113
114 #[error("{op} can only be performed on a single dimension")]
115 OnlySingleDimension { op: &'static str, dims: Vec<usize> },
116
117 #[error("empty tensor for {op}")]
118 EmptyTensor { op: &'static str },
119
120 #[error("device mismatch in {op}, lhs: {lhs:?}, rhs: {rhs:?}")]
122 DeviceMismatchBinaryOp {
123 lhs: DeviceLocation,
124 rhs: DeviceLocation,
125 op: &'static str,
126 },
127
128 #[error("narrow invalid args {msg}: {shape:?}, dim: {dim}, start: {start}, len:{len}")]
130 NarrowInvalidArgs {
131 shape: Box<Shape>,
132 dim: usize,
133 start: usize,
134 len: usize,
135 msg: &'static str,
136 },
137
138 #[error(
139 "conv1d invalid args {msg}: inp: {inp_shape:?}, k: {k_shape:?}, pad: {padding}, stride: {stride}"
140 )]
141 Conv1dInvalidArgs {
142 inp_shape: Box<Shape>,
143 k_shape: Box<Shape>,
144 padding: usize,
145 stride: usize,
146 msg: &'static str,
147 },
148
149 #[error("{op} invalid index {index} with dim size {size}")]
150 InvalidIndex {
151 op: &'static str,
152 index: usize,
153 size: usize,
154 },
155
156 #[error("cannot broadcast {src_shape:?} to {dst_shape:?}")]
157 BroadcastIncompatibleShapes {
158 src_shape: Box<Shape>,
159 dst_shape: Box<Shape>,
160 },
161
162 #[error("cannot set variable {msg}")]
163 CannotSetVar { msg: &'static str },
164
165 #[error("{0:?}")]
166 MatMulUnexpectedStriding(Box<MatMulUnexpectedStriding>),
167
168 #[error("{op} only supports contiguous tensors")]
169 RequiresContiguous { op: &'static str },
170
171 #[error("{op} expects at least one tensor")]
172 OpRequiresAtLeastOneTensor { op: &'static str },
173
174 #[error("{op} expects at least two tensors")]
175 OpRequiresAtLeastTwoTensors { op: &'static str },
176
177 #[error("backward is not supported for {op}")]
178 BackwardNotSupported { op: &'static str },
179
180 #[error(
191 "no backend supports {op} on {dtypes:?}; available backends: \
192 {available_backends:?}; supported (op, dtypes) by backend: \
193 {supported_combinations:?}"
194 )]
195 NoBackendForOp {
196 op: crate::dispatch::OpKind,
197 dtypes: Vec<DType>,
198 available_backends: Vec<crate::probe::BackendId>,
199 supported_combinations: Vec<(crate::probe::BackendId, crate::dispatch::OpKind, Vec<DType>)>,
200 },
201
202 #[error("unsupported transfer from {from:?} to {to:?}; available paths: {available_paths:?}")]
206 UnsupportedTransfer {
207 from: DeviceLocation,
208 to: DeviceLocation,
209 available_paths: Vec<crate::backend::TransferPath>,
210 },
211
212 #[error("alignment mismatch on {backend:?}: required {required} bytes, got {actual} bytes")]
216 AlignmentMismatch {
217 backend: crate::probe::BackendId,
218 required: usize,
219 actual: usize,
220 },
221
222 #[error(
235 "execution plan was built against topology generation {plan_generation}, \
236 but generation {current_generation} is now current; rebuild and retry"
237 )]
238 TopologyChanged {
239 plan_generation: u64,
240 current_generation: u64,
241 },
242
243 #[error("the fuel crate has not been built with cuda support")]
245 NotCompiledWithCudaSupport,
246
247 #[error("the fuel crate has not been built with metal support")]
248 NotCompiledWithMetalSupport,
249
250 #[error("the fuel crate has not been built with vulkan support")]
251 NotCompiledWithVulkanSupport,
252
253 #[error("cannot find tensor {path}")]
254 CannotFindTensor { path: String },
255
256 #[error(transparent)]
258 Cuda(Box<dyn std::error::Error + Send + Sync>),
259
260 #[error(transparent)]
261 Metal(Box<dyn std::error::Error + Send + Sync>),
262
263 #[error(transparent)]
264 Vulkan(Box<dyn std::error::Error + Send + Sync>),
265
266 #[cfg(all(not(target_arch = "wasm32"), not(target_os = "ios"), feature = "ug"))]
267 #[error(transparent)]
268 Ug(Box<dyn std::error::Error + Send + Sync>),
269
270 #[error(transparent)]
271 TryFromIntError(#[from] core::num::TryFromIntError),
272
273 #[error("npy/npz error {0}")]
274 Npy(String),
275
276 #[error(transparent)]
278 Zip(#[from] zip::result::ZipError),
279
280 #[error(transparent)]
282 ParseInt(#[from] std::num::ParseIntError),
283
284 #[error(transparent)]
286 FromUtf8(#[from] std::string::FromUtf8Error),
287
288 #[error(transparent)]
290 Io(#[from] std::io::Error),
291
292 #[error(transparent)]
294 SafeTensor(#[from] safetensors::SafeTensorError),
295
296 #[error("unsupported safetensor dtype {0:?}")]
297 UnsupportedSafeTensorDtype(safetensors::Dtype),
298
299 #[error("{0}")]
301 Wrapped(Box<dyn std::fmt::Display + Send + Sync>),
302
303 #[error("{wrapped:?}\n{context:?}")]
305 WrappedContext {
306 wrapped: Box<dyn std::error::Error + Send + Sync>,
307 context: String,
308 },
309
310 #[error("{context}\n{inner}")]
311 Context {
312 inner: Box<Self>,
313 context: Box<dyn std::fmt::Display + Send + Sync>,
314 },
315
316 #[error("path: {path:?} {inner}")]
318 WithPath {
319 inner: Box<Self>,
320 path: std::path::PathBuf,
321 },
322
323 #[error("{inner}\n{backtrace}")]
324 WithBacktrace {
325 inner: Box<Self>,
326 backtrace: Box<std::backtrace::Backtrace>,
327 },
328
329 #[error("{0}")]
331 Msg(String),
332
333 #[error("unwrap none")]
334 UnwrapNone,
335
336 #[error(
347 "ranker hard filter `{filter}` rejected all {available_alternatives} candidate(s); \
348 context: {ctx_summary}"
349 )]
350 FilterRejected {
351 filter: &'static str,
353 ctx_summary: String,
356 available_alternatives: usize,
360 },
361
362 #[error("invalid branch: {reason}")]
373 InvalidBranch {
374 reason: String,
377 },
378}
379
380pub type Result<T> = std::result::Result<T, Error>;
382
383const _: () = assert!(
400 std::mem::size_of::<Error>() <= 96,
401 "fuel_ir::Error exceeded its size budget: a Result<T> hot-path regression. \
402 Box the large field, or consciously raise the bound (see the comment above)."
403);
404
405impl Error {
406 pub fn wrap(err: impl std::fmt::Display + Send + Sync + 'static) -> Self {
408 Self::Wrapped(Box::new(err)).bt()
409 }
410
411 pub fn msg(err: impl std::fmt::Display) -> Self {
413 Self::Msg(err.to_string()).bt()
414 }
415
416 pub fn debug(err: impl std::fmt::Debug) -> Self {
418 Self::Msg(format!("{err:?}")).bt()
419 }
420
421 pub fn cuda(err: impl std::error::Error + Send + Sync + 'static) -> Self {
423 Self::Cuda(Box::new(err)).bt()
424 }
425
426 pub fn metal(err: impl std::error::Error + Send + Sync + 'static) -> Self {
428 Self::Metal(Box::new(err)).bt()
429 }
430
431 pub fn vulkan(err: impl std::error::Error + Send + Sync + 'static) -> Self {
433 Self::Vulkan(Box::new(err)).bt()
434 }
435
436 pub fn bt(self) -> Self {
438 let backtrace = std::backtrace::Backtrace::capture();
439 match backtrace.status() {
440 std::backtrace::BacktraceStatus::Disabled
441 | std::backtrace::BacktraceStatus::Unsupported => self,
442 _ => Self::WithBacktrace {
443 inner: Box::new(self),
444 backtrace: Box::new(backtrace),
445 },
446 }
447 }
448
449 pub fn with_path<P: AsRef<std::path::Path>>(self, p: P) -> Self {
451 Self::WithPath {
452 inner: Box::new(self),
453 path: p.as_ref().to_path_buf(),
454 }
455 }
456
457 pub fn context(self, c: impl std::fmt::Display + Send + Sync + 'static) -> Self {
459 Self::Context {
460 inner: Box::new(self),
461 context: Box::new(c),
462 }
463 }
464}
465
466#[macro_export]
468macro_rules! bail {
469 ($msg:literal $(,)?) => {
470 return Err($crate::Error::Msg(format!($msg).into()).bt())
471 };
472 ($err:expr $(,)?) => {
473 return Err($crate::Error::Msg(format!($err).into()).bt())
474 };
475 ($fmt:expr, $($arg:tt)*) => {
476 return Err($crate::Error::Msg(format!($fmt, $($arg)*).into()).bt())
477 };
478}
479
480pub fn zip<T, U>(r1: Result<T>, r2: Result<U>) -> Result<(T, U)> {
482 match (r1, r2) {
483 (Ok(r1), Ok(r2)) => Ok((r1, r2)),
484 (Err(e), _) => Err(e),
485 (_, Err(e)) => Err(e),
486 }
487}
488
489#[doc(hidden)]
490pub mod private {
491 pub trait Sealed {}
492
493 impl<T, E> Sealed for std::result::Result<T, E> where E: std::error::Error {}
494 impl<T> Sealed for Option<T> {}
495}
496
497pub trait Context<T, E>: private::Sealed {
499 fn context<C>(self, context: C) -> std::result::Result<T, Error>
501 where
502 C: Display + Send + Sync + 'static;
503
504 fn with_context<C, F>(self, f: F) -> std::result::Result<T, Error>
506 where
507 C: Display + Send + Sync + 'static,
508 F: FnOnce() -> C;
509}
510
511impl<T, E> Context<T, E> for std::result::Result<T, E>
512where
513 E: std::error::Error + Send + Sync + 'static,
514{
515 fn context<C>(self, context: C) -> std::result::Result<T, Error>
516 where
517 C: Display + Send + Sync + 'static,
518 {
519 self.map_err(|e| Error::WrappedContext {
520 wrapped: Box::new(e),
521 context: context.to_string(),
522 })
523 }
524
525 fn with_context<C, F>(self, context: F) -> std::result::Result<T, Error>
526 where
527 C: Display + Send + Sync + 'static,
528 F: FnOnce() -> C,
529 {
530 match self {
531 Ok(ok) => Ok(ok),
532 Err(error) => Err(Error::WrappedContext {
533 wrapped: Box::new(error),
534 context: context().to_string(),
535 }
536 .bt()),
537 }
538 }
539}
540
541impl<T> Context<T, Infallible> for Option<T> {
542 fn context<C>(self, context: C) -> std::result::Result<T, Error>
543 where
544 C: Display + Send + Sync + 'static,
545 {
546 match self {
549 Some(ok) => Ok(ok),
550 None => Err(Error::msg(context).bt()),
551 }
552 }
553
554 fn with_context<C, F>(self, context: F) -> std::result::Result<T, Error>
555 where
556 C: Display + Send + Sync + 'static,
557 F: FnOnce() -> C,
558 {
559 match self {
560 Some(v) => Ok(v),
561 None => Err(Error::UnwrapNone.context(context()).bt()),
562 }
563 }
564}