1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
use std::fmt;
use std::path::PathBuf;
#[cfg(feature = "_cuda-libraries")]
use cudarc::cublas::result::CublasError;
#[cfg(feature = "_cuda-libraries")]
use cudarc::cudnn::CudnnError;
use cudarc::driver::DriverError;
use safetensors::SafeTensorError;
use super::{ComputeCapability, CudaMath, PtxTier};
/// A CUDA shared library that the native backend loads at run time
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum CudaLibrary {
/// The driver API, `libcuda`, installed with the NVIDIA driver
Driver,
/// cuBLAS
Cublas,
/// cuDNN 9
Cudnn,
/// Runtime compilation used by the cuDNN dynamic LSTM
Nvrtc,
}
impl fmt::Display for CudaLibrary {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(match self {
Self::Driver => "CUDA driver (libcuda)",
Self::Cublas => "cuBLAS",
Self::Cudnn => "cuDNN",
Self::Nvrtc => "NVRTC",
})
}
}
/// Errors from the native CUDA backend
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum CudaError {
/// A CUDA shared library is absent or lacks a required symbol
#[error("could not load the {library} shared library")]
LibraryUnavailable {
/// The library that failed to load
library: CudaLibrary,
},
/// A direct optional-library request is forbidden by the driver-only policy
#[error("driver-only CUDA prohibits a direct request for {library}")]
LibraryForbidden {
/// The library requested without a selected model boundary
library: CudaLibrary,
},
/// A required PTX tier is absent from this build
#[error("PTX tier {tier} for device {device} is not compiled in; enable `{feature}`")]
TierNotCompiledIn {
/// Required PTX tier
tier: PtxTier,
/// Exact device capability
device: ComputeCapability,
/// Cargo feature that embeds the tier
feature: &'static str,
},
/// An area has no embedded variant that the device can run within the tier limit
#[error(
"CUDA area {area} has no runnable PTX at tier {tier} for device {device}; enable `{feature}`"
)]
AreaTierNotCompiledIn {
/// Kernel area without a runnable variant
area: &'static str,
/// Runtime's PTX tier limit
tier: PtxTier,
/// Exact device capability
device: ComputeCapability,
/// Cargo feature that embeds a runnable variant
feature: &'static str,
},
/// A forced tier has no accepted record for this device
#[error("PTX tier {tier} is not qualified for device {device}")]
TierNotQualified {
/// Forced tier
tier: PtxTier,
/// Exact device capability
device: ComputeCapability,
},
/// A model boundary has no complete library-free candidate for this tuple
#[error("driver-only CUDA missing kernel: {boundary} b{batch} {math:?}")]
MissingKernel {
/// The model operation without a complete candidate
boundary: String,
/// Requested batch class
batch: usize,
/// Requested arithmetic mode
math: CudaMath,
},
/// A selected model boundary requires an optional library
#[error(
"driver-only CUDA: {area}/{boundary} b{batch} {math:?}, PTX tier {tier}, device {device} requires {library}"
)]
NotDriverOnly {
/// Kernel area that owns the boundary
area: &'static str,
/// Boundary without a library-free qualified implementation
boundary: String,
/// Production batch class
batch: usize,
/// Configured precision
math: CudaMath,
/// Area's selected PTX variant
tier: PtxTier,
/// Exact device capability
device: ComputeCapability,
/// Required library
library: CudaLibrary,
},
/// A selected candidate cannot run on this device and fallback is forbidden
#[error(
"CUDA candidate {area}/{boundary} b{batch} {math:?}, PTX tier {tier}, device {device}: {reason}"
)]
CandidateDeviceUnsupported {
/// Kernel area that owns the candidate
area: &'static str,
/// Selected model boundary
boundary: String,
/// Selected batch class
batch: usize,
/// Configured precision
math: CudaMath,
/// Actual area PTX variant
tier: PtxTier,
/// Exact device capability
device: ComputeCapability,
/// Candidate's device constraint
reason: String,
},
/// A selected candidate refused weights outside its numeric contract, and fallback
/// is forbidden
#[error(
"CUDA candidate {area}/{boundary} b{batch} {math:?}, PTX tier {tier}, device {device}: weights `{layer}` are outside the candidate contract: {fault}"
)]
CandidateWeightsOutOfContract {
/// Kernel area that owns the candidate
area: &'static str,
/// Selected model boundary
boundary: String,
/// Selected batch class
batch: usize,
/// Configured precision
math: CudaMath,
/// Actual area PTX variant
tier: PtxTier,
/// Exact device capability
device: ComputeCapability,
/// The refused weight tensor
layer: &'static str,
/// The first violation
fault: WeightFault,
},
/// A selected candidate refused its plan geometry
///
/// An invalid geometry is a host bug in every mode. An unimplemented geometry
/// reaches this error only where fallback is forbidden
#[error("CUDA candidate {area}/{boundary} b{batch} {math:?}: {error}")]
CandidateGeometry {
/// Kernel area that owns the candidate
area: &'static str,
/// Selected model boundary
boundary: String,
/// Selected batch class
batch: usize,
/// Configured precision
math: CudaMath,
/// The refused geometry
error: GeometryError,
},
/// The requested device does not exist
#[error("CUDA device {ordinal} is not available; {count} device(s) found")]
NoDevice {
/// The requested device ordinal
ordinal: usize,
/// Devices the driver reports
count: usize,
},
/// The device is older than the PTX baseline every kernel area ships
#[error(
"GPU compute capability {capability} is below the native CUDA backend's {baseline} baseline (sm_75, Turing)"
)]
UnsupportedDevice {
/// The device's compute capability
capability: ComputeCapability,
/// The lowest PTX tier speakrs ships
baseline: PtxTier,
},
/// `SPEAKRS_CUDA_PTX_TIER` names no known tier
#[error("unknown PTX tier `{value}`; expected one of sm75, sm80, sm90, sm120")]
InvalidPtxTier {
/// The rejected value
value: String,
},
/// The requested PTX tier needs a newer GPU than the device
#[error("PTX tier {tier} needs compute capability {}, but the GPU has {capability}", tier.min_capability())]
PtxTierAboveDevice {
/// The requested tier
tier: PtxTier,
/// The device's compute capability
capability: ComputeCapability,
},
/// The CUDA driver API returned an error
#[error("CUDA driver: {0}")]
Driver(#[from] DriverError),
/// cuBLAS returned an error
#[error("cuBLAS: {0}")]
#[cfg(feature = "_cuda-libraries")]
Cublas(#[from] CublasError),
/// cuDNN returned an error
#[error("cuDNN: {0}")]
#[cfg(feature = "_cuda-libraries")]
Cudnn(#[from] CudnnError),
/// The driver could not load an embedded PTX module
#[error("loading PTX module `{module}`: {source}")]
ModuleLoad {
/// The kernel module name
module: &'static str,
/// The driver error
#[source]
source: DriverError,
},
/// The exact artifact pinned by a record is absent or conflicts with a cached load
#[error("CUDA module `{module}` cannot supply requested artifact {artifact:?}")]
ArtifactUnavailable {
/// The kernel area
module: &'static str,
/// The requested record identity
artifact: super::kernels::LoadedArtifact,
},
/// The driver rejected the artifact requested by qualification
#[error("CUDA module `{module}` rejected requested artifact {artifact:?}: {source}")]
ArtifactLoad {
/// The kernel area
module: &'static str,
/// The requested record identity
artifact: super::kernels::LoadedArtifact,
/// The driver's refusal
#[source]
source: DriverError,
},
/// A PTX module has no kernel with the requested name
#[error("PTX module `{module}` has no kernel `{kernel}`: {source}")]
KernelMissing {
/// The kernel module name
module: &'static str,
/// The requested kernel entry name
kernel: String,
/// The driver error
#[source]
source: DriverError,
},
/// A weights file could not be read
#[error("reading weights `{path}`: {source}")]
WeightsIo {
/// The weights file
path: PathBuf,
/// The I/O error
#[source]
source: std::io::Error,
},
/// A weights file is not valid safetensors
#[error("parsing weights `{path}`: {source}")]
WeightsFormat {
/// The weights file
path: PathBuf,
/// The safetensors error
#[source]
source: SafeTensorError,
},
/// A weights file lacks a required tensor
#[error("weights `{path}` have no tensor `{name}`")]
MissingTensor {
/// The weights file
path: PathBuf,
/// The missing tensor name
name: String,
},
/// A weight tensor is not stored as FP32
#[error("tensor `{name}` is {dtype}, expected F32")]
TensorDtype {
/// The tensor name
name: String,
/// The stored dtype
dtype: String,
},
/// A weight tensor has a different shape than the model expects
#[error("tensor `{name}` has shape {actual:?}, expected {expected:?}")]
TensorShape {
/// The tensor name
name: String,
/// The shape the model expects
expected: Vec<usize>,
/// The stored shape
actual: Vec<usize>,
},
/// A buffer length does not match the shape or operation it is used for
#[error("{context}: buffer holds {actual} elements, needs {expected}")]
BufferLength {
/// Which operation checked the buffer
context: &'static str,
/// Elements the operation needs
expected: usize,
/// Elements the buffer holds
actual: usize,
},
/// A dimension does not fit the 32-bit integers that cuBLAS and cuDNN take
#[error("{context}: dimension {value} does not fit in a 32-bit CUDA library argument")]
DimensionOverflow {
/// Which operation checked the dimension
context: &'static str,
/// The dimension that overflowed
value: usize,
},
/// An operation was asked for a shape or option it does not support
#[error("{context}: {reason}")]
Unsupported {
/// Which operation refused
context: &'static str,
/// What it does not support
reason: String,
},
}
/// Why a candidate refused a weight tensor at plan time
#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
#[non_exhaustive]
pub enum WeightFault {
/// An element is NaN or infinite
#[error("element {index} is not finite")]
NonFinite {
/// Flat element index
index: usize,
},
/// A column's exponent range does not fit the candidate's packed format
#[error("column {column} has exponent {exponent}, outside the packed range")]
ExponentOutOfRange {
/// Packed column
column: usize,
/// The offending binary exponent
exponent: i32,
},
}
/// A plan geometry a candidate refused before any launch
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
#[non_exhaustive]
pub enum GeometryError {
/// The geometry violates the pinned plan's invariants, which is a host bug
#[error("{context}: invalid geometry: {reason}")]
Invalid {
/// Which plan refused
context: &'static str,
/// The violated invariant
reason: String,
},
/// A valid geometry this candidate does not implement
#[error("{context}: unimplemented geometry: {reason}")]
Unimplemented {
/// Which plan refused
context: &'static str,
/// What the candidate lacks
reason: String,
},
}
impl CudaError {
/// Returns true when this machine has no usable NVIDIA GPU: the driver library is
/// missing, the driver reports no device, or the GPU is older than the `sm_75`
/// baseline
///
/// A missing cuBLAS or cuDNN on a machine with a GPU is a broken install, not a
/// missing GPU, so it does not count
pub fn is_device_unavailable(&self) -> bool {
matches!(
self,
Self::LibraryUnavailable {
library: CudaLibrary::Driver
} | Self::NoDevice { .. }
| Self::UnsupportedDevice { .. }
)
}
}
/// Converts a dimension for a cuBLAS or cuDNN call
#[cfg(feature = "_cuda-libraries")]
pub(super) fn to_c_int(context: &'static str, value: usize) -> Result<i32, CudaError> {
i32::try_from(value).map_err(|_| CudaError::DimensionOverflow { context, value })
}
/// Checks that a buffer holds exactly the elements an operation reads or writes
pub(super) fn check_len(
context: &'static str,
expected: usize,
actual: usize,
) -> Result<(), CudaError> {
if expected != actual {
return Err(CudaError::BufferLength {
context,
expected,
actual,
});
}
Ok(())
}
/// Number of elements in a shape
pub(super) fn element_count(context: &'static str, shape: &[usize]) -> Result<usize, CudaError> {
shape.iter().try_fold(1_usize, |count, &dim| {
count.checked_mul(dim).ok_or(CudaError::DimensionOverflow {
context,
value: dim,
})
})
}
impl From<crate::inference::native_model::NativeWeightsError> for CudaError {
fn from(error: crate::inference::native_model::NativeWeightsError) -> Self {
use crate::inference::native_model::NativeWeightsError;
match error {
NativeWeightsError::WeightsIo { path, source } => Self::WeightsIo { path, source },
NativeWeightsError::WeightsFormat { path, source } => {
Self::WeightsFormat { path, source }
}
NativeWeightsError::MissingTensor { path, name } => Self::MissingTensor { path, name },
NativeWeightsError::TensorDtype { name, dtype } => Self::TensorDtype { name, dtype },
NativeWeightsError::TensorShape {
name,
expected,
actual,
} => Self::TensorShape {
name,
expected,
actual,
},
}
}
}