1#[repr(u8)]
8#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
9pub enum DataType {
10 Undefined = 0,
11 Float32 = 1,
12 Uint8 = 2,
13 Int8 = 3,
14 Uint16 = 4,
15 Int16 = 5,
16 Int32 = 6,
17 Int64 = 7,
18 String = 8,
19 Bool = 9,
20 Float16 = 10,
21 Float64 = 11,
22 Uint32 = 12,
23 Uint64 = 13,
24 Complex64 = 14,
25 Complex128 = 15,
26 BFloat16 = 16,
27 Float8E4M3FN = 17,
28 Float8E4M3FNUZ = 18,
29 Float8E5M2 = 19,
30 Float8E5M2FNUZ = 20,
31 Uint4 = 21,
32 Int4 = 22,
33 Float4E2M1 = 23,
34 Float8E8M0 = 24,
35 Uint2 = 25,
36 Int2 = 26,
37}
38
39impl DataType {
40 pub fn byte_size(self) -> usize {
43 match self {
44 Self::Float32 | Self::Int32 | Self::Uint32 => 4,
45 Self::Float64 | Self::Int64 | Self::Uint64 | Self::Complex64 => 8,
46 Self::Complex128 => 16,
47 Self::Float16 | Self::BFloat16 | Self::Int16 | Self::Uint16 => 2,
48 Self::Int8
49 | Self::Uint8
50 | Self::Bool
51 | Self::Float8E4M3FN
52 | Self::Float8E4M3FNUZ
53 | Self::Float8E5M2
54 | Self::Float8E5M2FNUZ
55 | Self::Float8E8M0 => 1,
56 Self::Int4 | Self::Uint4 | Self::Float4E2M1 | Self::Int2 | Self::Uint2 => 0,
57 Self::String | Self::Undefined => 0, }
59 }
60
61 pub fn bit_size(self) -> usize {
63 match self {
64 Self::Int4 | Self::Uint4 | Self::Float4E2M1 => 4,
65 Self::Int2 | Self::Uint2 => 2,
66 other => other.byte_size() * 8,
67 }
68 }
69
70 pub fn is_float(self) -> bool {
72 matches!(
73 self,
74 Self::Float32
75 | Self::Float64
76 | Self::Float16
77 | Self::BFloat16
78 | Self::Float8E4M3FN
79 | Self::Float8E4M3FNUZ
80 | Self::Float8E5M2
81 | Self::Float8E5M2FNUZ
82 | Self::Float8E8M0
83 | Self::Float4E2M1
84 )
85 }
86
87 pub fn is_int(self) -> bool {
89 matches!(
90 self,
91 Self::Uint8
92 | Self::Int8
93 | Self::Uint16
94 | Self::Int16
95 | Self::Int32
96 | Self::Int64
97 | Self::Uint32
98 | Self::Uint64
99 | Self::Int4
100 | Self::Uint4
101 | Self::Int2
102 | Self::Uint2
103 )
104 }
105
106 pub fn is_complex(self) -> bool {
108 matches!(self, Self::Complex64 | Self::Complex128)
109 }
110
111 pub fn is_sub_byte(self) -> bool {
113 matches!(
114 self,
115 Self::Int4 | Self::Uint4 | Self::Float4E2M1 | Self::Int2 | Self::Uint2
116 )
117 }
118
119 pub fn storage_bytes(self, count: usize) -> usize {
127 self.checked_storage_bytes(count)
128 .expect("storage_bytes overflow")
129 }
130
131 pub fn checked_storage_bytes(self, count: usize) -> Option<usize> {
141 if self == Self::Undefined {
142 None
143 } else if self.is_sub_byte() {
144 let elements_per_byte = 8 / self.bit_size();
145 Some(count / elements_per_byte + usize::from(!count.is_multiple_of(elements_per_byte)))
146 } else {
147 count.checked_mul(self.byte_size())
148 }
149 }
150
151 pub fn from_onnx(raw: i32) -> Option<Self> {
157 Some(match raw {
158 1 => Self::Float32,
159 2 => Self::Uint8,
160 3 => Self::Int8,
161 4 => Self::Uint16,
162 5 => Self::Int16,
163 6 => Self::Int32,
164 7 => Self::Int64,
165 8 => Self::String,
166 9 => Self::Bool,
167 10 => Self::Float16,
168 11 => Self::Float64,
169 12 => Self::Uint32,
170 13 => Self::Uint64,
171 14 => Self::Complex64,
172 15 => Self::Complex128,
173 16 => Self::BFloat16,
174 17 => Self::Float8E4M3FN,
175 18 => Self::Float8E4M3FNUZ,
176 19 => Self::Float8E5M2,
177 20 => Self::Float8E5M2FNUZ,
178 21 => Self::Uint4,
179 22 => Self::Int4,
180 23 => Self::Float4E2M1,
181 24 => Self::Float8E8M0,
182 25 => Self::Uint2,
183 26 => Self::Int2,
184 _ => return None,
185 })
186 }
187
188 pub fn to_onnx(self) -> i32 {
190 self as i32
191 }
192}
193
194#[cfg(test)]
195mod tests {
196 use super::*;
197
198 #[test]
199 fn byte_and_bit_sizes() {
200 assert_eq!(DataType::Undefined.checked_storage_bytes(1), None);
201 assert_eq!(DataType::Float32.byte_size(), 4);
202 assert_eq!(DataType::Float32.bit_size(), 32);
203 assert_eq!(DataType::Int64.byte_size(), 8);
204 assert_eq!(DataType::Float16.byte_size(), 2);
205 assert_eq!(DataType::Bool.byte_size(), 1);
206 assert_eq!(DataType::Int4.byte_size(), 0);
207 assert_eq!(DataType::Int4.bit_size(), 4);
208 assert_eq!(DataType::Int2.bit_size(), 2);
209 }
210
211 #[test]
212 fn sub_byte_storage_rounds_up() {
213 assert_eq!(DataType::Int4.storage_bytes(4), 2);
214 assert_eq!(DataType::Int4.storage_bytes(5), 3);
215 assert_eq!(DataType::Int2.storage_bytes(5), 2);
216 assert_eq!(DataType::Float32.storage_bytes(4), 16);
217 }
218
219 #[test]
220 fn checked_storage_bytes_normal_counts() {
221 assert_eq!(DataType::Float32.checked_storage_bytes(4), Some(16));
222 assert_eq!(DataType::Float64.checked_storage_bytes(3), Some(24));
223 assert_eq!(DataType::Int4.checked_storage_bytes(5), Some(3));
224 assert_eq!(DataType::Uint2.checked_storage_bytes(5), Some(2));
225 assert_eq!(DataType::Bool.checked_storage_bytes(0), Some(0));
226 }
227
228 #[test]
229 fn checked_storage_bytes_detects_byte_overflow() {
230 assert_eq!(
234 DataType::Float64.checked_storage_bytes(usize::MAX / 4),
235 None
236 );
237 assert_eq!(DataType::Int64.checked_storage_bytes(usize::MAX), None);
238 assert_eq!(
240 DataType::Int4.checked_storage_bytes(usize::MAX),
241 Some(usize::MAX / 2 + 1)
242 );
243 assert_eq!(
244 DataType::Int2.checked_storage_bytes(usize::MAX),
245 Some(usize::MAX / 4 + 1)
246 );
247 }
248
249 #[test]
250 fn classification() {
251 assert!(DataType::Float16.is_float());
252 assert!(!DataType::Int32.is_float());
253 assert!(DataType::Int32.is_int());
254 assert!(!DataType::Bool.is_int());
255 assert!(DataType::Complex64.is_complex());
256 assert!(DataType::Complex128.is_complex());
257 assert!(!DataType::Float32.is_complex());
258 assert!(DataType::Uint4.is_sub_byte());
259 for dt in [
261 DataType::Float8E4M3FN,
262 DataType::Float8E4M3FNUZ,
263 DataType::Float8E5M2,
264 DataType::Float8E5M2FNUZ,
265 DataType::Float8E8M0,
266 DataType::Float4E2M1,
267 ] {
268 assert!(dt.is_float(), "{dt:?} should be float");
269 assert!(!dt.is_int(), "{dt:?} should not be int");
270 }
271 assert!(DataType::Int4.is_sub_byte());
273 assert!(DataType::Float4E2M1.is_sub_byte());
274 assert!(DataType::Int2.is_sub_byte());
275 assert!(DataType::Uint2.is_sub_byte());
276 assert!(!DataType::Float8E5M2.is_sub_byte());
277 assert!(!DataType::Float8E4M3FNUZ.is_sub_byte());
278 }
279
280 #[test]
281 fn float4_sub_byte_storage_rounds_up() {
282 assert_eq!(DataType::Float4E2M1.byte_size(), 0);
283 assert_eq!(DataType::Float4E2M1.bit_size(), 4);
284 assert_eq!(DataType::Float4E2M1.storage_bytes(4), 2);
285 assert_eq!(DataType::Float4E2M1.storage_bytes(5), 3);
286 assert_eq!(
287 DataType::Float4E2M1.checked_storage_bytes(usize::MAX),
288 Some(usize::MAX / 2 + 1)
289 );
290 }
291
292 #[test]
293 fn float8_variants_are_one_byte() {
294 for dt in [
295 DataType::Float8E4M3FN,
296 DataType::Float8E4M3FNUZ,
297 DataType::Float8E5M2,
298 DataType::Float8E5M2FNUZ,
299 DataType::Float8E8M0,
300 ] {
301 assert_eq!(dt.byte_size(), 1, "{dt:?} should be 1 byte");
302 assert_eq!(dt.bit_size(), 8, "{dt:?} should be 8 bits");
303 assert!(!dt.is_sub_byte(), "{dt:?} is not packed");
304 }
305 }
306
307 #[test]
311 fn onnx_discriminants_match_spec() {
312 assert_eq!(DataType::Undefined.to_onnx(), 0);
313 let table: &[(DataType, i32)] = &[
314 (DataType::Float32, 1),
315 (DataType::Uint8, 2),
316 (DataType::Int8, 3),
317 (DataType::Uint16, 4),
318 (DataType::Int16, 5),
319 (DataType::Int32, 6),
320 (DataType::Int64, 7),
321 (DataType::String, 8),
322 (DataType::Bool, 9),
323 (DataType::Float16, 10),
324 (DataType::Float64, 11),
325 (DataType::Uint32, 12),
326 (DataType::Uint64, 13),
327 (DataType::Complex64, 14),
328 (DataType::Complex128, 15),
329 (DataType::BFloat16, 16),
330 (DataType::Float8E4M3FN, 17),
331 (DataType::Float8E4M3FNUZ, 18),
332 (DataType::Float8E5M2, 19),
333 (DataType::Float8E5M2FNUZ, 20),
334 (DataType::Uint4, 21),
335 (DataType::Int4, 22),
336 (DataType::Float4E2M1, 23),
337 (DataType::Float8E8M0, 24),
338 (DataType::Uint2, 25),
339 (DataType::Int2, 26),
340 ];
341 for &(dt, raw) in table {
342 assert_eq!(dt.to_onnx(), raw, "{dt:?} to_onnx mismatch");
343 assert_eq!(
344 DataType::from_onnx(raw),
345 Some(dt),
346 "from_onnx({raw}) mismatch"
347 );
348 }
349 }
350
351 #[test]
352 fn onnx_roundtrip() {
353 for dt in [
354 DataType::Float32,
355 DataType::Int64,
356 DataType::BFloat16,
357 DataType::Uint4,
358 DataType::Float4E2M1,
359 DataType::Float8E5M2FNUZ,
360 DataType::Float8E8M0,
361 DataType::Uint2,
362 DataType::Int2,
363 ] {
364 assert_eq!(DataType::from_onnx(dt.to_onnx()), Some(dt));
365 }
366 }
367
368 #[test]
371 fn unknown_raw_values_return_none() {
372 for raw in [0, 27, 100, 9999, -1, i32::MAX, i32::MIN] {
374 assert_eq!(DataType::from_onnx(raw), None, "raw {raw} should be None");
375 }
376 }
377}