1use alloc::format;
2use alloc::string::String;
3use num_traits::{Float, ToPrimitive};
4
5use super::TensorData;
6use crate::{
7 BoolStore, DType, Element, ElementOrdered, bf16, f16, quantization::global_scale_dtype,
8};
9
10#[derive(Debug, Clone, Copy)]
29pub struct Tolerance<F> {
30 relative: F,
31 absolute: F,
32}
33
34impl<F: Float> Default for Tolerance<F> {
35 fn default() -> Self {
36 Self::balanced()
37 }
38}
39
40impl<F: Float> Tolerance<F> {
41 pub fn strict() -> Self {
43 Self {
44 relative: F::from(0.00).unwrap(),
45 absolute: F::from(64).unwrap() * F::min_positive_value(),
46 }
47 }
48 pub fn balanced() -> Self {
50 Self {
51 relative: F::from(0.005).unwrap(), absolute: F::from(1e-5).unwrap(),
53 }
54 }
55
56 pub fn permissive() -> Self {
58 Self {
59 relative: F::from(0.01).unwrap(), absolute: F::from(0.01).unwrap(),
61 }
62 }
63 pub fn rel_abs<FF: ToPrimitive>(relative: FF, absolute: FF) -> Self {
73 let relative = Self::check_relative(relative);
74 let absolute = Self::check_absolute(absolute);
75
76 Self { relative, absolute }
77 }
78
79 pub fn relative<FF: ToPrimitive>(tolerance: FF) -> Self {
89 let relative = Self::check_relative(tolerance);
90
91 Self {
92 relative,
93 absolute: F::from(0.0).unwrap(),
94 }
95 }
96
97 pub fn absolute<FF: ToPrimitive>(tolerance: FF) -> Self {
107 let absolute = Self::check_absolute(tolerance);
108
109 Self {
110 relative: F::from(0.0).unwrap(),
111 absolute,
112 }
113 }
114
115 pub fn get_relative(&self) -> F {
117 self.relative
118 }
119
120 pub fn get_absolute(&self) -> F {
122 self.absolute
123 }
124
125 pub fn set_relative<FF: ToPrimitive>(mut self, tolerance: FF) -> Self {
127 self.relative = Self::check_relative(tolerance);
128 self
129 }
130
131 pub fn set_half_precision_relative<FF: ToPrimitive>(mut self, tolerance: FF) -> Self {
133 if core::mem::size_of::<F>() == 2 {
134 self.relative = Self::check_relative(tolerance);
135 }
136 self
137 }
138
139 pub fn set_single_precision_relative<FF: ToPrimitive>(mut self, tolerance: FF) -> Self {
141 if core::mem::size_of::<F>() == 4 {
142 self.relative = Self::check_relative(tolerance);
143 }
144 self
145 }
146
147 pub fn set_double_precision_relative<FF: ToPrimitive>(mut self, tolerance: FF) -> Self {
149 if core::mem::size_of::<F>() == 8 {
150 self.relative = Self::check_relative(tolerance);
151 }
152 self
153 }
154
155 pub fn set_absolute<FF: ToPrimitive>(mut self, tolerance: FF) -> Self {
157 self.absolute = Self::check_absolute(tolerance);
158 self
159 }
160
161 pub fn set_half_precision_absolute<FF: ToPrimitive>(mut self, tolerance: FF) -> Self {
163 if core::mem::size_of::<F>() == 2 {
164 self.absolute = Self::check_absolute(tolerance);
165 }
166 self
167 }
168
169 pub fn set_single_precision_absolute<FF: ToPrimitive>(mut self, tolerance: FF) -> Self {
171 if core::mem::size_of::<F>() == 4 {
172 self.absolute = Self::check_absolute(tolerance);
173 }
174 self
175 }
176
177 pub fn set_double_precision_absolute<FF: ToPrimitive>(mut self, tolerance: FF) -> Self {
179 if core::mem::size_of::<F>() == 8 {
180 self.absolute = Self::check_absolute(tolerance);
181 }
182 self
183 }
184
185 pub fn approx_eq(&self, x: F, y: F) -> bool {
187 if x == y {
193 return true;
194 }
195
196 let diff = (x - y).abs();
197 let max = F::max(x.abs(), y.abs());
198
199 diff < self.absolute.max(self.relative * max)
200 }
201
202 fn check_relative<FF: ToPrimitive>(tolerance: FF) -> F {
203 let tolerance = F::from(tolerance).unwrap();
204 assert!(tolerance <= F::one());
205 tolerance
206 }
207
208 fn check_absolute<FF: ToPrimitive>(tolerance: FF) -> F {
209 let tolerance = F::from(tolerance).unwrap();
210 assert!(tolerance >= F::zero());
211 tolerance
212 }
213}
214
215impl TensorData {
216 #[track_caller]
229 pub fn assert_eq(&self, other: &Self, strict: bool) {
230 if strict {
231 assert_eq!(
232 self.dtype, other.dtype,
233 "Data types differ ({:?} != {:?})",
234 self.dtype, other.dtype
235 );
236 }
237
238 match self.dtype {
239 DType::F64 => self.assert_eq_elem::<f64>(other),
240 DType::F32 | DType::Flex32 => self.assert_eq_elem::<f32>(other),
241 DType::F16 => self.assert_eq_elem::<f16>(other),
242 DType::BF16 => self.assert_eq_elem::<bf16>(other),
243 DType::I64 => self.assert_eq_elem::<i64>(other),
244 DType::I32 => self.assert_eq_elem::<i32>(other),
245 DType::I16 => self.assert_eq_elem::<i16>(other),
246 DType::I8 => self.assert_eq_elem::<i8>(other),
247 DType::U64 => self.assert_eq_elem::<u64>(other),
248 DType::U32 => self.assert_eq_elem::<u32>(other),
249 DType::U16 => self.assert_eq_elem::<u16>(other),
250 DType::U8 => self.assert_eq_elem::<u8>(other),
251 DType::Bool(BoolStore::Native) => self.assert_eq_elem::<bool>(other),
252 DType::Bool(BoolStore::U8) => self.assert_eq_elem::<u8>(other),
253 DType::Bool(BoolStore::U32) => self.assert_eq_elem::<u32>(other),
254 DType::QFloat(q) => {
255 let q_other = if let DType::QFloat(q_other) = other.dtype {
257 q_other
258 } else {
259 panic!("Quantized data differs from other not quantized data")
260 };
261
262 if q.value == q_other.value
264 && q.block_size() == q_other.block_size()
265 && global_scale_dtype(&q) == global_scale_dtype(&q_other)
266 {
267 self.assert_eq_elem::<i8>(other)
268 } else {
269 panic!("Quantization schemes differ ({q:?} != {q_other:?})")
270 }
271 }
272 }
273 }
274
275 #[track_caller]
276 fn assert_eq_elem<E: Element>(&self, other: &Self) {
277 let mut message = String::new();
278 if self.shape != other.shape {
279 message += format!(
280 "\n => Shape is different: {:?} != {:?}",
281 self.shape, other.shape
282 )
283 .as_str();
284 }
285
286 let iter_self = self.iter_exact::<E>();
288 let iter_other = other.iter_exact::<E>();
289 let len_self = iter_self.len();
290 let len_other = iter_other.len();
291 if len_self != len_other {
292 message += format!("\n => Element counts differ: {len_self} != {len_other}").as_str();
293 }
294
295 let mut num_diff = 0;
296 let max_num_diff = 5;
297 for (i, (a, b)) in iter_self.zip(iter_other).enumerate() {
298 if !a.eq(&b) {
299 if num_diff < max_num_diff {
301 message += format!("\n => Position {i}: {a} != {b}").as_str();
302 }
303 num_diff += 1;
304 }
305 }
306
307 if num_diff >= max_num_diff {
308 message += format!("\n{} more errors...", num_diff - max_num_diff).as_str();
309 }
310
311 if !message.is_empty() {
312 panic!("Tensors are not eq:{message}");
313 }
314 }
315
316 #[track_caller]
327 pub fn assert_approx_eq<F: Float + Element>(&self, other: &Self, tolerance: Tolerance<F>) {
328 let mut message = String::new();
329 if self.shape != other.shape {
330 message += format!(
331 "\n => Shape is different: {:?} != {:?}",
332 self.shape, other.shape
333 )
334 .as_str();
335 }
336
337 let iter = self.iter::<F>().zip(other.iter::<F>());
338
339 let mut num_diff = 0;
340 let max_num_diff = 5;
341
342 for (i, (a, b)) in iter.enumerate() {
343 let both_nan = a.is_nan() && b.is_nan();
345 let both_inf =
347 a.is_infinite() && b.is_infinite() && ((a > F::zero()) == (b > F::zero()));
348
349 if both_nan || both_inf {
350 continue;
351 }
352
353 if !tolerance.approx_eq(F::from(a).unwrap(), F::from(b).unwrap()) {
354 if num_diff < max_num_diff {
356 let diff_abs = ToPrimitive::to_f64(&(a - b).abs()).unwrap();
357 let max = F::max(a.abs(), b.abs());
358 let diff_rel = diff_abs / ToPrimitive::to_f64(&max).unwrap();
359
360 let tol_rel = ToPrimitive::to_f64(&tolerance.relative).unwrap();
361 let tol_abs = ToPrimitive::to_f64(&tolerance.absolute).unwrap();
362
363 message += format!(
364 "\n => Position {i}: {a} != {b}\n diff (rel = {diff_rel:+.2e}, abs = {diff_abs:+.2e}), tol (rel = {tol_rel:+.2e}, abs = {tol_abs:+.2e})"
365 )
366 .as_str();
367 }
368 num_diff += 1;
369 }
370 }
371
372 if num_diff >= max_num_diff {
373 message += format!("\n{} more errors...", num_diff - 5).as_str();
374 }
375
376 if !message.is_empty() {
377 panic!("Tensors are not approx eq:{message}");
378 }
379 }
380
381 pub fn assert_within_range<E: ElementOrdered>(&self, range: core::ops::Range<E>) {
392 for elem in self.iter::<E>() {
393 if elem.cmp(&range.start).is_lt() || elem.cmp(&range.end).is_ge() {
394 panic!("Element ({elem:?}) is not within range {range:?}");
395 }
396 }
397 }
398
399 pub fn assert_within_range_inclusive<E: ElementOrdered>(
409 &self,
410 range: core::ops::RangeInclusive<E>,
411 ) {
412 let start = range.start();
413 let end = range.end();
414
415 for elem in self.iter::<E>() {
416 if elem.cmp(start).is_lt() || elem.cmp(end).is_gt() {
417 panic!("Element ({elem:?}) is not within range {range:?}");
418 }
419 }
420 }
421}
422
423#[cfg(test)]
424mod tests {
425 use super::*;
426 use alloc::vec;
427
428 #[test]
429 #[should_panic(expected = "Element counts differ")]
430 fn should_assert_eq_reject_shorter_data() {
431 let data =
434 TensorData::from_bytes_unchecked(TensorData::from([1.0f32]).bytes, [2], DType::F32);
435 let expected = TensorData::from([1.0f32, 2.0]);
436
437 data.assert_eq(&expected, false);
438 }
439
440 #[test]
441 #[should_panic(expected = "Element counts differ")]
442 fn should_assert_eq_reject_longer_data() {
443 let data = TensorData::from([1.0f32, 2.0]);
444 let expected =
445 TensorData::from_bytes_unchecked(TensorData::from([1.0f32]).bytes, [2], DType::F32);
446
447 data.assert_eq(&expected, true);
448 }
449
450 #[test]
451 #[should_panic(expected = "Element counts differ")]
452 fn should_assert_eq_reject_empty_data_with_nonempty_shape() {
453 let data =
454 TensorData::from_bytes_unchecked(crate::Bytes::from_bytes_vec(vec![]), [1], DType::F32);
455 let expected = TensorData::from([1.0f32]);
456
457 data.assert_eq(&expected, false);
458 }
459
460 #[test]
461 #[should_panic(expected = "Element counts differ")]
462 fn should_assert_eq_reject_different_counts_with_equal_byte_lengths() {
463 let data =
464 TensorData::from_bytes_unchecked(TensorData::from([1.0f64]).bytes, [2], DType::F64);
465 let expected = TensorData::from([1.0f32, 2.0]);
466
467 data.assert_eq(&expected, false);
468 }
469
470 #[test]
471 fn should_assert_eq_allow_different_dtypes_when_not_strict() {
472 let data = TensorData::from([1.0f32, 2.0]);
473 let expected = TensorData::from([1i64, 2]);
474
475 data.assert_eq(&expected, false);
476 expected.assert_eq(&data, false);
477 }
478
479 #[test]
480 fn should_assert_appox_eq_limit() {
481 let data1 = TensorData::from([[3.0, 5.0, 6.0]]);
482 let data2 = TensorData::from([[3.03, 5.0, 6.0]]);
483
484 data1.assert_approx_eq::<f32>(&data2, Tolerance::absolute(3e-2));
485 data1.assert_approx_eq::<f16>(&data2, Tolerance::absolute(3e-2));
486 }
487
488 #[test]
489 #[should_panic]
490 fn should_assert_approx_eq_above_limit() {
491 let data1 = TensorData::from([[3.0, 5.0, 6.0]]);
492 let data2 = TensorData::from([[3.031, 5.0, 6.0]]);
493
494 data1.assert_approx_eq::<f32>(&data2, Tolerance::absolute(1e-2));
495 }
496
497 #[test]
498 #[should_panic]
499 fn should_assert_approx_eq_check_shape() {
500 let data1 = TensorData::from([[3.0, 5.0, 6.0, 7.0]]);
501 let data2 = TensorData::from([[3.0, 5.0, 6.0]]);
502
503 data1.assert_approx_eq::<f32>(&data2, Tolerance::absolute(1e-2));
504 }
505}