1#[repr(u8)]
8#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
9pub enum DataType {
10 Float32 = 1,
11 Uint8 = 2,
12 Int8 = 3,
13 Uint16 = 4,
14 Int16 = 5,
15 Int32 = 6,
16 Int64 = 7,
17 String = 8,
18 Bool = 9,
19 Float16 = 10,
20 Float64 = 11,
21 Uint32 = 12,
22 Uint64 = 13,
23 BFloat16 = 16,
24 Float8E4M3FN = 17,
25 Float8E4M3FNUZ = 18,
26 Float8E5M2 = 19,
27 Float8E5M2FNUZ = 20,
28 Uint4 = 21,
29 Int4 = 22,
30 Float4E2M1 = 23,
31}
32
33impl DataType {
34 pub fn byte_size(self) -> usize {
37 match self {
38 Self::Float32 | Self::Int32 | Self::Uint32 => 4,
39 Self::Float64 | Self::Int64 | Self::Uint64 => 8,
40 Self::Float16 | Self::BFloat16 | Self::Int16 | Self::Uint16 => 2,
41 Self::Int8
42 | Self::Uint8
43 | Self::Bool
44 | Self::Float8E4M3FN
45 | Self::Float8E4M3FNUZ
46 | Self::Float8E5M2
47 | Self::Float8E5M2FNUZ => 1,
48 Self::Int4 | Self::Uint4 | Self::Float4E2M1 => 0, Self::String => 0, }
51 }
52
53 pub fn bit_size(self) -> usize {
55 match self {
56 Self::Int4 | Self::Uint4 | Self::Float4E2M1 => 4,
57 other => other.byte_size() * 8,
58 }
59 }
60
61 pub fn is_float(self) -> bool {
63 matches!(
64 self,
65 Self::Float32
66 | Self::Float64
67 | Self::Float16
68 | Self::BFloat16
69 | Self::Float8E4M3FN
70 | Self::Float8E4M3FNUZ
71 | Self::Float8E5M2
72 | Self::Float8E5M2FNUZ
73 | Self::Float4E2M1
74 )
75 }
76
77 pub fn is_int(self) -> bool {
79 matches!(
80 self,
81 Self::Uint8
82 | Self::Int8
83 | Self::Uint16
84 | Self::Int16
85 | Self::Int32
86 | Self::Int64
87 | Self::Uint32
88 | Self::Uint64
89 | Self::Int4
90 | Self::Uint4
91 )
92 }
93
94 pub fn is_sub_byte(self) -> bool {
96 matches!(self, Self::Int4 | Self::Uint4 | Self::Float4E2M1)
97 }
98
99 pub fn storage_bytes(self, count: usize) -> usize {
107 self.checked_storage_bytes(count)
108 .expect("storage_bytes overflow")
109 }
110
111 pub fn checked_storage_bytes(self, count: usize) -> Option<usize> {
121 if self.is_sub_byte() {
122 Some(count.div_ceil(2))
123 } else {
124 count.checked_mul(self.byte_size())
125 }
126 }
127
128 pub fn from_onnx(raw: i32) -> Option<Self> {
135 Some(match raw {
136 1 => Self::Float32,
137 2 => Self::Uint8,
138 3 => Self::Int8,
139 4 => Self::Uint16,
140 5 => Self::Int16,
141 6 => Self::Int32,
142 7 => Self::Int64,
143 8 => Self::String,
144 9 => Self::Bool,
145 10 => Self::Float16,
146 11 => Self::Float64,
147 12 => Self::Uint32,
148 13 => Self::Uint64,
149 16 => Self::BFloat16,
150 17 => Self::Float8E4M3FN,
151 18 => Self::Float8E4M3FNUZ,
152 19 => Self::Float8E5M2,
153 20 => Self::Float8E5M2FNUZ,
154 21 => Self::Uint4,
155 22 => Self::Int4,
156 23 => Self::Float4E2M1,
157 _ => return None,
158 })
159 }
160
161 pub fn to_onnx(self) -> i32 {
163 self as i32
164 }
165}
166
167#[cfg(test)]
168mod tests {
169 use super::*;
170
171 #[test]
172 fn byte_and_bit_sizes() {
173 assert_eq!(DataType::Float32.byte_size(), 4);
174 assert_eq!(DataType::Float32.bit_size(), 32);
175 assert_eq!(DataType::Int64.byte_size(), 8);
176 assert_eq!(DataType::Float16.byte_size(), 2);
177 assert_eq!(DataType::Bool.byte_size(), 1);
178 assert_eq!(DataType::Int4.byte_size(), 0);
179 assert_eq!(DataType::Int4.bit_size(), 4);
180 }
181
182 #[test]
183 fn sub_byte_storage_rounds_up() {
184 assert_eq!(DataType::Int4.storage_bytes(4), 2);
185 assert_eq!(DataType::Int4.storage_bytes(5), 3);
186 assert_eq!(DataType::Float32.storage_bytes(4), 16);
187 }
188
189 #[test]
190 fn checked_storage_bytes_normal_counts() {
191 assert_eq!(DataType::Float32.checked_storage_bytes(4), Some(16));
192 assert_eq!(DataType::Float64.checked_storage_bytes(3), Some(24));
193 assert_eq!(DataType::Int4.checked_storage_bytes(5), Some(3));
194 assert_eq!(DataType::Bool.checked_storage_bytes(0), Some(0));
195 }
196
197 #[test]
198 fn checked_storage_bytes_detects_byte_overflow() {
199 assert_eq!(DataType::Float64.checked_storage_bytes(usize::MAX / 4), None);
203 assert_eq!(DataType::Int64.checked_storage_bytes(usize::MAX), None);
204 assert_eq!(
206 DataType::Int4.checked_storage_bytes(usize::MAX),
207 Some(usize::MAX / 2 + 1)
208 );
209 }
210
211 #[test]
212 fn classification() {
213 assert!(DataType::Float16.is_float());
214 assert!(!DataType::Int32.is_float());
215 assert!(DataType::Int32.is_int());
216 assert!(!DataType::Bool.is_int());
217 assert!(DataType::Uint4.is_sub_byte());
218 for dt in [
220 DataType::Float8E4M3FN,
221 DataType::Float8E4M3FNUZ,
222 DataType::Float8E5M2,
223 DataType::Float8E5M2FNUZ,
224 DataType::Float4E2M1,
225 ] {
226 assert!(dt.is_float(), "{dt:?} should be float");
227 assert!(!dt.is_int(), "{dt:?} should not be int");
228 }
229 assert!(DataType::Int4.is_sub_byte());
231 assert!(DataType::Float4E2M1.is_sub_byte());
232 assert!(!DataType::Float8E5M2.is_sub_byte());
233 assert!(!DataType::Float8E4M3FNUZ.is_sub_byte());
234 }
235
236 #[test]
237 fn float4_sub_byte_storage_rounds_up() {
238 assert_eq!(DataType::Float4E2M1.byte_size(), 0);
239 assert_eq!(DataType::Float4E2M1.bit_size(), 4);
240 assert_eq!(DataType::Float4E2M1.storage_bytes(4), 2);
241 assert_eq!(DataType::Float4E2M1.storage_bytes(5), 3);
242 assert_eq!(DataType::Float4E2M1.checked_storage_bytes(usize::MAX), Some(usize::MAX / 2 + 1));
243 }
244
245 #[test]
246 fn float8_variants_are_one_byte() {
247 for dt in [
248 DataType::Float8E4M3FN,
249 DataType::Float8E4M3FNUZ,
250 DataType::Float8E5M2,
251 DataType::Float8E5M2FNUZ,
252 ] {
253 assert_eq!(dt.byte_size(), 1, "{dt:?} should be 1 byte");
254 assert_eq!(dt.bit_size(), 8, "{dt:?} should be 8 bits");
255 assert!(!dt.is_sub_byte(), "{dt:?} is not packed");
256 }
257 }
258
259 #[test]
263 fn onnx_discriminants_match_spec() {
264 let table: &[(DataType, i32)] = &[
265 (DataType::Float32, 1),
266 (DataType::Uint8, 2),
267 (DataType::Int8, 3),
268 (DataType::Uint16, 4),
269 (DataType::Int16, 5),
270 (DataType::Int32, 6),
271 (DataType::Int64, 7),
272 (DataType::String, 8),
273 (DataType::Bool, 9),
274 (DataType::Float16, 10),
275 (DataType::Float64, 11),
276 (DataType::Uint32, 12),
277 (DataType::Uint64, 13),
278 (DataType::BFloat16, 16),
279 (DataType::Float8E4M3FN, 17),
280 (DataType::Float8E4M3FNUZ, 18),
281 (DataType::Float8E5M2, 19),
282 (DataType::Float8E5M2FNUZ, 20),
283 (DataType::Uint4, 21),
284 (DataType::Int4, 22),
285 (DataType::Float4E2M1, 23),
286 ];
287 for &(dt, raw) in table {
288 assert_eq!(dt.to_onnx(), raw, "{dt:?} to_onnx mismatch");
289 assert_eq!(DataType::from_onnx(raw), Some(dt), "from_onnx({raw}) mismatch");
290 }
291 }
292
293 #[test]
294 fn onnx_roundtrip() {
295 for dt in [
296 DataType::Float32,
297 DataType::Int64,
298 DataType::BFloat16,
299 DataType::Uint4,
300 DataType::Float4E2M1,
301 DataType::Float8E5M2FNUZ,
302 ] {
303 assert_eq!(DataType::from_onnx(dt.to_onnx()), Some(dt));
304 }
305 }
306
307 #[test]
310 fn unknown_raw_values_return_none() {
311 for raw in [0, 14, 15, 24, 100, 9999, -1, i32::MAX, i32::MIN] {
313 assert_eq!(DataType::from_onnx(raw), None, "raw {raw} should be None");
314 }
315 }
316}