• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /* Copyright 2018 The TensorFlow Authors. All Rights Reserved.
2 
3 Licensed under the Apache License, Version 2.0 (the "License");
4 you may not use this file except in compliance with the License.
5 You may obtain a copy of the License at
6 
7     http://www.apache.org/licenses/LICENSE-2.0
8 
9 Unless required by applicable law or agreed to in writing, software
10 distributed under the License is distributed on an "AS IS" BASIS,
11 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 See the License for the specific language governing permissions and
13 limitations under the License.
14 ==============================================================================*/
15 
16 #include "tensorflow/compiler/xla/literal_comparison.h"
17 
18 #include <unistd.h>
19 
20 #include <cmath>
21 #include <vector>
22 
23 #include "absl/base/casts.h"
24 #include "absl/strings/str_cat.h"
25 #include "absl/strings/str_format.h"
26 #include "tensorflow/compiler/xla/literal_util.h"
27 #include "tensorflow/compiler/xla/util.h"
28 #include "tensorflow/core/platform/env.h"
29 
30 using absl::StrAppend;
31 using absl::StrAppendFormat;
32 using absl::StrCat;
33 
34 namespace xla {
35 namespace literal_comparison {
36 namespace {
37 
38 // Since Eigen::half doesn't satisfy the absl::bit_cast contract, we need to be
39 // able to transparently access the raw 16-bit value contained within.
40 template <typename T>
GetRawValue(T val)41 T GetRawValue(T val) {
42   return val;
43 }
GetRawValue(Eigen::half val)44 uint16 GetRawValue(Eigen::half val) { return val.x; }
45 
46 // Helper function for comparing a floating point type, FloatT, bitwise equal
47 // between the left-hand-side and right-hand-side, by bit-casting to UnsignedT
48 // -- on miscompare, a nice error message is given in the AssertionFailure.
49 template <typename FloatT, typename UnsignedT>
CompareFloatsBitwiseEqual(FloatT lhs,FloatT rhs,absl::Span<const int64> multi_index)50 bool CompareFloatsBitwiseEqual(FloatT lhs, FloatT rhs,
51                                absl::Span<const int64> multi_index) {
52   auto ulhs = absl::bit_cast<UnsignedT>(GetRawValue(lhs));
53   auto urhs = absl::bit_cast<UnsignedT>(GetRawValue(rhs));
54   return ulhs == urhs;
55 }
56 
57 // Templated comparator that specializes for float equality comparison with the
58 // bitwise helper above (this is the un-specialized fallback, to just use the
59 // default gunit implementation).
60 template <typename NativeT>
CompareEqual(NativeT lhs,NativeT rhs,absl::Span<const int64> multi_index)61 bool CompareEqual(NativeT lhs, NativeT rhs,
62                   absl::Span<const int64> multi_index) {
63   return lhs == rhs;
64 }
65 
66 // Specializations for floating types that do bitwise comparisons when equality
67 // comparison is requested.
68 template <>
CompareEqual(bfloat16 lhs,bfloat16 rhs,absl::Span<const int64> multi_index)69 bool CompareEqual<bfloat16>(bfloat16 lhs, bfloat16 rhs,
70                             absl::Span<const int64> multi_index) {
71   return CompareFloatsBitwiseEqual<bfloat16, uint16>(lhs, rhs, multi_index);
72 }
73 template <>
CompareEqual(Eigen::half lhs,Eigen::half rhs,absl::Span<const int64> multi_index)74 bool CompareEqual<Eigen::half>(Eigen::half lhs, Eigen::half rhs,
75                                absl::Span<const int64> multi_index) {
76   return CompareFloatsBitwiseEqual<Eigen::half, uint16>(lhs, rhs, multi_index);
77 }
78 template <>
CompareEqual(float lhs,float rhs,absl::Span<const int64> multi_index)79 bool CompareEqual<float>(float lhs, float rhs,
80                          absl::Span<const int64> multi_index) {
81   return CompareFloatsBitwiseEqual<float, uint32>(lhs, rhs, multi_index);
82 }
83 template <>
CompareEqual(double lhs,double rhs,absl::Span<const int64> multi_index)84 bool CompareEqual<double>(double lhs, double rhs,
85                           absl::Span<const int64> multi_index) {
86   return CompareFloatsBitwiseEqual<double, uint64>(lhs, rhs, multi_index);
87 }
88 template <>
CompareEqual(complex64 lhs,complex64 rhs,absl::Span<const int64> multi_index)89 bool CompareEqual<complex64>(complex64 lhs, complex64 rhs,
90                              absl::Span<const int64> multi_index) {
91   return CompareEqual<float>(lhs.real(), rhs.real(), multi_index) &&
92          CompareEqual<float>(lhs.imag(), rhs.imag(), multi_index);
93 }
94 template <>
CompareEqual(complex128 lhs,complex128 rhs,absl::Span<const int64> multi_index)95 bool CompareEqual<complex128>(complex128 lhs, complex128 rhs,
96                               absl::Span<const int64> multi_index) {
97   return CompareEqual<double>(lhs.real(), rhs.real(), multi_index) &&
98          CompareEqual<double>(lhs.imag(), rhs.imag(), multi_index);
99 }
100 
101 template <typename NativeT, typename UnsignedT>
MakeBitwiseErrorStatus(NativeT lhs,NativeT rhs,absl::Span<const int64> multi_index)102 Status MakeBitwiseErrorStatus(NativeT lhs, NativeT rhs,
103                               absl::Span<const int64> multi_index) {
104   auto ulhs = absl::bit_cast<UnsignedT>(GetRawValue(lhs));
105   auto urhs = absl::bit_cast<UnsignedT>(GetRawValue(rhs));
106   auto lhs_double = static_cast<double>(lhs);
107   auto rhs_double = static_cast<double>(rhs);
108   return InvalidArgument(
109       "floating values are not bitwise-equal; and equality testing "
110       "was requested: %s=%s=%a vs %s=%s=%a at array index %s",
111       StrCat(absl::Hex(ulhs)), RoundTripFpToString(lhs), lhs_double,
112       StrCat(absl::Hex(urhs)), RoundTripFpToString(rhs), rhs_double,
113       LiteralUtil::MultiIndexAsString(multi_index));
114 }
115 
116 template <typename NativeT>
MakeErrorStatus(NativeT lhs,NativeT rhs,absl::Span<const int64> multi_index)117 Status MakeErrorStatus(NativeT lhs, NativeT rhs,
118                        absl::Span<const int64> multi_index) {
119   return InvalidArgument(
120       "first mismatch at array index %s:\n  expected value: %s\n  actual "
121       "value:   %s",
122       LiteralUtil::MultiIndexAsString(multi_index), StrCat(lhs), StrCat(rhs));
123 }
124 
125 template <>
MakeErrorStatus(bfloat16 lhs,bfloat16 rhs,absl::Span<const int64> multi_index)126 Status MakeErrorStatus(bfloat16 lhs, bfloat16 rhs,
127                        absl::Span<const int64> multi_index) {
128   return MakeBitwiseErrorStatus<bfloat16, uint16>(lhs, rhs, multi_index);
129 }
130 template <>
MakeErrorStatus(Eigen::half lhs,Eigen::half rhs,absl::Span<const int64> multi_index)131 Status MakeErrorStatus(Eigen::half lhs, Eigen::half rhs,
132                        absl::Span<const int64> multi_index) {
133   return MakeBitwiseErrorStatus<Eigen::half, uint16>(lhs, rhs, multi_index);
134 }
135 template <>
MakeErrorStatus(float lhs,float rhs,absl::Span<const int64> multi_index)136 Status MakeErrorStatus(float lhs, float rhs,
137                        absl::Span<const int64> multi_index) {
138   return MakeBitwiseErrorStatus<float, uint32>(lhs, rhs, multi_index);
139 }
140 template <>
MakeErrorStatus(double lhs,double rhs,absl::Span<const int64> multi_index)141 Status MakeErrorStatus(double lhs, double rhs,
142                        absl::Span<const int64> multi_index) {
143   return MakeBitwiseErrorStatus<double, uint64>(lhs, rhs, multi_index);
144 }
145 template <>
MakeErrorStatus(complex64 lhs,complex64 rhs,absl::Span<const int64> multi_index)146 Status MakeErrorStatus(complex64 lhs, complex64 rhs,
147                        absl::Span<const int64> multi_index) {
148   if (!CompareEqual<float>(lhs.real(), rhs.real(), multi_index)) {
149     return MakeErrorStatus(lhs.real(), rhs.real(), multi_index);
150   }
151   return MakeErrorStatus(lhs.imag(), rhs.imag(), multi_index);
152 }
153 template <>
MakeErrorStatus(complex128 lhs,complex128 rhs,absl::Span<const int64> multi_index)154 Status MakeErrorStatus(complex128 lhs, complex128 rhs,
155                        absl::Span<const int64> multi_index) {
156   if (!CompareEqual<double>(lhs.real(), rhs.real(), multi_index)) {
157     return MakeErrorStatus(lhs.real(), rhs.real(), multi_index);
158   }
159   return MakeErrorStatus(lhs.imag(), rhs.imag(), multi_index);
160 }
161 
162 // A recursive function which iterates through every index of expected and
163 // actual literal and compares their values elementwise. Returns true if all
164 // elements are equal. Mismatched must either be:
165 //    - a literal of booleans that has the same shape as expected and actual. In
166 //      this case, each index in mismatched will be set to true if expected does
167 //      not equal actual at that index and false if there are equal.
168 //    - nullptr. In this case, the function will return once any mismatch is
169 //      found between expected and actual.
170 template <typename NativeT>
Equal(LiteralSlice expected,LiteralSlice actual,absl::Span<int64> multi_index,int64 dimension,Literal * mismatched=nullptr)171 Status Equal(LiteralSlice expected, LiteralSlice actual,
172              absl::Span<int64> multi_index, int64 dimension,
173              Literal* mismatched = nullptr) {
174   if (dimension == expected.shape().dimensions_size()) {
175     NativeT expected_value = expected.Get<NativeT>(multi_index);
176     NativeT actual_value = actual.Get<NativeT>(multi_index);
177     bool result =
178         CompareEqual<NativeT>(expected_value, actual_value, multi_index);
179     if (mismatched) {
180       mismatched->Set<bool>(multi_index, !result);
181     }
182     return result ? Status::OK()
183                   : MakeErrorStatus<NativeT>(expected_value, actual_value,
184                                              multi_index);
185   }
186 
187   Status result;
188   for (int64 i = 0; i < expected.shape().dimensions(dimension); ++i) {
189     multi_index[dimension] = i;
190     if (mismatched != nullptr) {
191       result.Update(Equal<NativeT>(expected, actual, multi_index, dimension + 1,
192                                    mismatched));
193     } else {
194       TF_RETURN_IF_ERROR(Equal<NativeT>(expected, actual, multi_index,
195                                         dimension + 1, mismatched));
196     }
197   }
198   return result;
199 }
200 
201 // Gets the total element count.  For tuples, this is not the count of tuple
202 // elements, but the sum of elements of each tuple element.
RecursiveElementCount(const Shape & shape)203 int64 RecursiveElementCount(const Shape& shape) {
204   if (shape.IsTuple()) {
205     const int64 tuple_elements = ShapeUtil::TupleElementCount(shape);
206     int64 total = 0;
207     for (int64 i = 0; i < tuple_elements; ++i) {
208       total += RecursiveElementCount(ShapeUtil::GetTupleElementShape(shape, i));
209     }
210     return total;
211   } else if (shape.IsArray()) {
212     return ShapeUtil::ElementsIn(shape);
213   } else {
214     return 0;
215   }
216 }
217 
218 // Returns whether the given value is infinity.
219 template <typename NativeT>
IsInf(NativeT val)220 bool IsInf(NativeT val) {
221   return Eigen::numext::isinf(val);
222 }
223 // Returns whether the given value is nan.
224 template <typename NativeT>
IsNan(NativeT value)225 bool IsNan(NativeT value) {
226   return Eigen::numext::isnan(value);
227 }
228 
229 // Converts the given floating-point value to a string.
FpValueToString(bfloat16 value)230 string FpValueToString(bfloat16 value) {
231   return absl::StrFormat("%10.4g", static_cast<double>(value));
232 }
233 
FpValueToString(half value)234 string FpValueToString(half value) {
235   return absl::StrFormat("%11.5g", static_cast<double>(value));
236 }
237 
FpValueToString(float value)238 string FpValueToString(float value) {
239   return absl::StrFormat("%15.9g", static_cast<double>(value));
240 }
241 
FpValueToString(double value)242 string FpValueToString(double value) {
243   return absl::StrFormat("%24.17g", value);
244 }
245 
FpValueToString(complex64 value)246 string FpValueToString(complex64 value) {
247   return absl::StrCat(FpValueToString(value.real()), " + ",
248                       FpValueToString(value.imag()));
249 }
250 
FpValueToString(complex128 value)251 string FpValueToString(complex128 value) {
252   return absl::StrCat(FpValueToString(value.real()), " + ",
253                       FpValueToString(value.imag()));
254 }
255 
256 // A wrapper of std::abs to include data types that are not supported by
257 // std::abs, in particular, bfloat16 and half.
258 template <typename NativeT>
FpAbsoluteValue(NativeT value)259 double FpAbsoluteValue(NativeT value) {
260   return std::abs(value);
261 }
262 
263 template <>
FpAbsoluteValue(bfloat16 value)264 double FpAbsoluteValue(bfloat16 value) {
265   return FpAbsoluteValue<float>(static_cast<float>(value));
266 }
267 
268 template <>
FpAbsoluteValue(half value)269 double FpAbsoluteValue(half value) {
270   return FpAbsoluteValue<float>(static_cast<float>(value));
271 }
272 
273 // Helper class for comparing floating-point literals within an error bound.
274 template <typename NativeT>
275 class NearComparator {
276  public:
277   // Compares the two array literals elementwise and returns a comparison
278   // result. The comparison is ok() if all actual and expected elements are
279   // within the given error bound. In case of error, the status contains a
280   // detailed message about the discrepancy.
Compare(const LiteralSlice & expected,const LiteralSlice & actual,const ShapeIndex & shape_index,ErrorSpec error,bool detailed_message,const MiscompareCallback & miscompare_callback)281   static Status Compare(const LiteralSlice& expected,
282                         const LiteralSlice& actual,
283                         const ShapeIndex& shape_index, ErrorSpec error,
284                         bool detailed_message,
285                         const MiscompareCallback& miscompare_callback) {
286     NearComparator<NativeT> comparator(expected, actual, shape_index, error,
287                                        detailed_message, miscompare_callback);
288     return comparator.Run();
289   }
290 
291  private:
292   // Data structure encapsulating metadata about a single element mismatch.
293   struct Mismatch {
294     NativeT actual;
295     NativeT expected;
296     double rel_error;
297     double abs_error;
298 
299     // The linear index of the failure within the shape. This linear index is
300     // from the 'actual' literal.
301     int64 linear_index;
302 
operator <xla::literal_comparison::__anon90b6a3750111::NearComparator::Mismatch303     bool operator<(const Mismatch& other) const {
304       return rel_error < other.rel_error;
305     }
306 
ToStringxla::literal_comparison::__anon90b6a3750111::NearComparator::Mismatch307     string ToString(const Shape& shape) const {
308       return absl::StrFormat(
309           "actual %s, expected %s, index %s, rel error %8.3g, abs error %8.3g",
310           FpValueToString(actual), FpValueToString(expected),
311           LiteralUtil::MultiIndexAsString(
312               IndexUtil::LinearIndexToMultidimensionalIndex(shape,
313                                                             linear_index)),
314           rel_error, abs_error);
315     }
316   };
317 
NearComparator(const LiteralSlice & expected,const LiteralSlice & actual,const ShapeIndex & shape_index,ErrorSpec error,bool detailed_message,const MiscompareCallback & miscompare_callback)318   NearComparator(const LiteralSlice& expected, const LiteralSlice& actual,
319                  const ShapeIndex& shape_index, ErrorSpec error,
320                  bool detailed_message,
321                  const MiscompareCallback& miscompare_callback)
322       : expected_(expected),
323         actual_(actual),
324         shape_index_(shape_index),
325         error_(error),
326         detailed_message_(detailed_message),
327         miscompare_callback_(miscompare_callback),
328         abs_value_buckets_(kAbsValueBucketBounds.size() - 1, {0, 0}),
329         abs_error_buckets_(kErrorBucketBounds.size(), 0),
330         rel_error_buckets_(kErrorBucketBounds.size(), 0) {}
331 
332   // Runs the comparison between expected and actual literals.
Run()333   Status Run() {
334     // If the shapes mismatch, we simply fail the expectation instead of
335     // printing out data, as it's a type error rather than a value error.
336     TF_RETURN_IF_ERROR(EqualShapes(expected_.shape(), actual_.shape()));
337     if (!expected_.shape().IsArray()) {
338       return InvalidArgument("Expected array shape; got %s.",
339                              ShapeUtil::HumanString(expected_.shape()));
340     }
341 
342     mismatches_ = Literal(ShapeUtil::ChangeElementType(actual_.shape(), PRED));
343     mismatches_.PopulateWithValue(false);
344 
345     CompareLiterals();
346 
347     if (num_mismatches_ == 0) {
348       return Status::OK();
349     } else if (!VLOG_IS_ON(1) && miscompare_callback_ != nullptr) {
350       miscompare_callback_(expected_, actual_, mismatches_, shape_index_);
351     }
352     return InvalidArgument("%s", ErrorMessage());
353   }
354 
355   // Insert the given absolute value into the absolute value bucket vector. The
356   // bounds of the buckets are given by kAbsValueBucketBounds.
UpdateAbsValueBucket(NativeT value,bool is_mismatch)357   void UpdateAbsValueBucket(NativeT value, bool is_mismatch) {
358     // Adjust the bucket containing the absolute values of the 'actual'
359     // elements.
360     const double abs_value = FpAbsoluteValue(value);
361     for (int i = 0; i < abs_value_buckets_.size(); ++i) {
362       if (i == abs_value_buckets_.size() - 1 ||
363           (abs_value >= kAbsValueBucketBounds[i] &&
364            abs_value < kAbsValueBucketBounds[i + 1])) {
365         // The first value of the pair is the count of elements in the bucket,
366         // the second is the count of mismatches in the bucket.
367         abs_value_buckets_[i].first++;
368         if (is_mismatch) {
369           abs_value_buckets_[i].second++;
370         }
371         return;
372       }
373     }
374   }
375 
376   // Insert the given error into the given error bucket vector.
UpdateErrorBucket(double error,absl::Span<int64> error_buckets)377   void UpdateErrorBucket(double error, absl::Span<int64> error_buckets) {
378     CHECK_EQ(error_buckets.size(), kErrorBucketBounds.size());
379     for (int i = 0; i < error_buckets.size(); ++i) {
380       if (error >= kErrorBucketBounds[i]) {
381         error_buckets[i]++;
382       }
383     }
384   }
385 
386   // Compares the two given elements from the expected and actual literals at
387   // the given literal_index and keeps track of various mismatch statistics.
388   template <typename T>
CompareValues(T expected,T actual,int64 linear_index)389   void CompareValues(T expected, T actual, int64 linear_index) {
390     double abs_error;
391     double rel_error;
392     if (CompareEqual<T>(expected, actual, {linear_index})) {
393       abs_error = 0;
394       rel_error = 0;
395     } else if (IsNan(expected) || IsNan(actual)) {
396       if ((!error_.relaxed_nans && IsNan(expected) != IsNan(actual)) ||
397           (error_.relaxed_nans && !IsNan(expected) && IsNan(actual))) {
398         num_nan_mismatches_++;
399         // A nan mismatch is considered to have infinite error. rel_error is
400         // used for sorting a std::set of the top mismatches, and a nan value
401         // here will result in undefined behavior because nan's do not satisfy
402         // the strict weak ordering requirement of std containers.
403         abs_error = std::numeric_limits<float>::infinity();
404         rel_error = std::numeric_limits<float>::infinity();
405       } else {
406         abs_error = 0;
407         rel_error = 0;
408       }
409     } else if (IsInf(actual) && !IsInf(expected) && error_.fewer_infs_ok) {
410       // `fewer_infs_ok` gives us the option of comparing as though `actual`
411       // were float_max/min rather than inf.
412       T actual_finite = actual > T{0} ? std::numeric_limits<T>::max()
413                                       : std::numeric_limits<T>::lowest();
414       abs_error = FpAbsoluteValue(actual_finite - expected);
415 
416       // Avoid division by 0 even though it's well-defined because ubsan can be
417       // configured to treat this as a fatal error.
418       if (expected != T{0}) {
419         rel_error = abs_error / FpAbsoluteValue(expected);
420       } else {
421         rel_error = std::numeric_limits<float>::infinity();
422       }
423     } else if (IsInf(expected) || IsInf(actual)) {
424       // If either the expected or actual value is infinity but not both,
425       // then both absolute and relative error are regarded as infinity.
426       CHECK(!CompareEqual(expected, actual, {linear_index}));
427       abs_error = std::numeric_limits<float>::infinity();
428       rel_error = std::numeric_limits<float>::infinity();
429     } else {
430       abs_error = FpAbsoluteValue(actual - expected);
431 
432       // Avoid division by 0 even though it's well-defined because ubsan can be
433       // configured to treat this as a fatal error.
434       if (expected != T{0}) {
435         rel_error = abs_error / FpAbsoluteValue(expected);
436       } else {
437         rel_error = std::numeric_limits<float>::infinity();
438       }
439     }
440     const bool is_abs_mismatch = abs_error > error_.abs;
441     const bool is_rel_mismatch = rel_error > error_.rel;
442     const bool is_mismatch = is_abs_mismatch && is_rel_mismatch;
443 
444     // Update the error of the relative bucket only if the *absolute* error
445     // bound is exceeded and vice versa.
446     if (is_abs_mismatch) {
447       num_abs_mismatches_++;
448       UpdateErrorBucket(rel_error, absl::MakeSpan(rel_error_buckets_));
449     }
450     if (is_rel_mismatch) {
451       num_rel_mismatches_++;
452       UpdateErrorBucket(abs_error, absl::MakeSpan(abs_error_buckets_));
453     }
454 
455     UpdateAbsValueBucket(actual, is_mismatch);
456 
457     if (!is_mismatch) {
458       return;
459     }
460 
461     num_mismatches_++;
462 
463     // Keep track of the kTopRelativeErrorCount relative error mismatches.
464     if (top_rel_mismatches_.size() < kTopRelativeErrorCount ||
465         rel_error > top_rel_mismatches_.begin()->rel_error) {
466       Mismatch mismatch = {actual, expected, rel_error, abs_error,
467                            linear_index};
468       top_rel_mismatches_.insert(mismatch);
469       if (top_rel_mismatches_.size() > kTopRelativeErrorCount) {
470         top_rel_mismatches_.erase(top_rel_mismatches_.begin());
471       }
472     }
473 
474     mismatches_.data<bool>()[linear_index] = true;
475   }
476 
477   // For complex types, we compare real and imaginary parts individually.
CompareValues(complex64 expected,complex64 actual,int64 linear_index)478   void CompareValues(complex64 expected, complex64 actual, int64 linear_index) {
479     const auto both_parts_mismatch = num_mismatches_ + 2;
480     CompareValues<float>(expected.real(), actual.real(), linear_index);
481     CompareValues<float>(expected.imag(), actual.imag(), linear_index);
482     if (num_mismatches_ == both_parts_mismatch) {
483       // The mismatch counter had been incremented by each CompareValues() call,
484       // which means that both real and imaginary parts of the passed-in complex
485       // values are different. However, the counter should reflect a single
486       // mismatch between these complex values.
487       num_mismatches_--;
488     }
489   }
490 
CompareValues(complex128 expected,complex128 actual,int64 linear_index)491   void CompareValues(complex128 expected, complex128 actual,
492                      int64 linear_index) {
493     const auto both_parts_mismatch = num_mismatches_ + 2;
494     CompareValues<double>(expected.real(), actual.real(), linear_index);
495     CompareValues<double>(expected.imag(), actual.imag(), linear_index);
496     if (num_mismatches_ == both_parts_mismatch) {
497       // The mismatch counter had been incremented by each CompareValues() call,
498       // which means that both real and imaginary parts of the passed-in complex
499       // values are different. However, the counter should reflect a single
500       // mismatch between these complex values.
501       num_mismatches_--;
502     }
503   }
504 
505   // Compares the two literals elementwise.
CompareLiterals()506   void CompareLiterals() {
507     // Fast path optimization for the case were layouts match.
508     if (LayoutUtil::Equal(actual_.shape().layout(),
509                           expected_.shape().layout())) {
510       absl::Span<const NativeT> expected_data = expected_.data<NativeT>();
511       absl::Span<const NativeT> actual_data = actual_.data<NativeT>();
512       const int64 len = expected_data.size();
513       for (int64 i = 0; i < len; ++i) {
514         CompareValues(expected_data[i], actual_data[i], i);
515       }
516       return;
517     }
518     std::vector<int64> multi_index(actual_.shape().rank(), 0);
519     CompareLiteralsSlow(0, &multi_index);
520   }
521 
522   // Slow path for CompareLiterals when 'actual' and 'expected' literals have
523   // different layouts. In this case, multidimensional indices are constructed
524   // and indexed for each element.
CompareLiteralsSlow(int64 dimension,std::vector<int64> * multi_index)525   void CompareLiteralsSlow(int64 dimension, std::vector<int64>* multi_index) {
526     if (dimension == multi_index->size()) {
527       CompareValues(expected_.Get<NativeT>(*multi_index),
528                     actual_.Get<NativeT>(*multi_index),
529                     IndexUtil::MultidimensionalIndexToLinearIndex(
530                         actual_.shape(), *multi_index));
531     } else {
532       for (int64 i = 0; i < expected_.shape().dimensions(dimension); ++i) {
533         (*multi_index)[dimension] = i;
534         CompareLiteralsSlow(dimension + 1, multi_index);
535       }
536     }
537   }
538 
539   // Returns an error message string with a detailed breakdown of the
540   // mismatches. Called after calling Run().
ErrorMessage()541   string ErrorMessage() {
542     string out;
543     int64 element_count = ShapeUtil::ElementsIn(actual_.shape());
544 
545     auto percent_string = [](float a, float b) {
546       float pct = b == 0.0 ? 0.0 : 100.0 * a / b;
547       return absl::StrFormat("%0.4f%%", pct);
548     };
549 
550     StrAppendFormat(
551         &out,
552         "\nMismatch count %d (%s) in shape %s (%d elements), abs bound "
553         "%g, rel bound %g\n",
554         num_mismatches_, percent_string(num_mismatches_, element_count),
555         ShapeUtil::HumanString(actual_.shape()),
556         ShapeUtil::ElementsIn(actual_.shape()), error_.abs, error_.rel);
557     if (num_nan_mismatches_ > 0) {
558       StrAppend(&out, "nan mismatches ", num_nan_mismatches_, "\n");
559     }
560     StrAppendFormat(&out, "Top relative error mismatches:\n");
561     for (auto it = top_rel_mismatches_.rbegin();
562          it != top_rel_mismatches_.rend(); ++it) {
563       StrAppend(&out, "  ", it->ToString(actual_.shape()), "\n");
564     }
565 
566     if (!detailed_message_) {
567       return out;
568     }
569 
570     StrAppend(&out, "Absolute magnitude breakdown of actual values:\n");
571     CHECK_EQ(abs_value_buckets_.size() + 1, kAbsValueBucketBounds.size());
572     for (int i = 0; i < abs_value_buckets_.size(); ++i) {
573       const int64 bucket_size = abs_value_buckets_[i].first;
574       const int64 bucket_mismatches = abs_value_buckets_[i].second;
575       string mismatch_str =
576           bucket_mismatches > 0
577               ? absl::StrFormat(", mismatches %d", bucket_mismatches)
578               : "";
579       StrAppendFormat(&out, "  %-6g <= x < %-6g : %7d (%9s)%s\n",
580                       kAbsValueBucketBounds[i], kAbsValueBucketBounds[i + 1],
581                       bucket_size, percent_string(bucket_size, element_count),
582                       mismatch_str);
583     }
584 
585     auto print_accum_buckets = [&](const string& header, int64 total,
586                                    absl::Span<const int64> buckets) {
587       StrAppend(&out, header, ":\n");
588       StrAppendFormat(&out, "  <  %-6g : %7d (%s)\n", kErrorBucketBounds[0],
589                       total - buckets[0],
590                       percent_string(total - buckets[0], total));
591       CHECK_EQ(buckets.size(), kErrorBucketBounds.size());
592       for (int i = 0; i < kErrorBucketBounds.size(); ++i) {
593         StrAppendFormat(&out, "  >= %-6g : %7d (%s)\n", kErrorBucketBounds[i],
594                         buckets[i], percent_string(buckets[i], total));
595       }
596     };
597     StrAppendFormat(&out, "Elements exceeding abs error bound %g: %d (%s)\n",
598                     error_.abs, num_abs_mismatches_,
599                     percent_string(num_abs_mismatches_, element_count));
600     print_accum_buckets(
601         "Relative error breakdown of elements exceeding abs error bound",
602         num_abs_mismatches_, rel_error_buckets_);
603     StrAppendFormat(&out, "Elements exceeding rel error bound %g: %d (%s)\n",
604                     error_.rel, num_rel_mismatches_,
605                     percent_string(num_rel_mismatches_, element_count));
606     print_accum_buckets(
607         "Absolute error breakdown of elements exceeding rel error bound",
608         num_rel_mismatches_, abs_error_buckets_);
609     return out;
610   }
611 
612   // 'actual' and 'expected' literals being compared.
613   LiteralSlice expected_;
614   LiteralSlice actual_;
615 
616   // The shape index of the LiteralSlice that is being compared.
617   ShapeIndex shape_index_;
618 
619   // The error bounds of the comparison.
620   ErrorSpec error_;
621 
622   // Whether to include detailed breakdown of mismatches in the error message.
623   bool detailed_message_;
624 
625   // Callback to invoke on miscompare.
626   MiscompareCallback miscompare_callback_;
627 
628   // Number of element mismatches encountered so far.
629   int64 num_mismatches_ = 0;
630 
631   // Number of elements with a nan mismatch.
632   int64 num_nan_mismatches_ = 0;
633 
634   // Number of elements which exceed the absolute/relative error bound.
635   int64 num_abs_mismatches_ = 0;
636   int64 num_rel_mismatches_ = 0;
637 
638   // A Literal containing which elements did not match in the expected and
639   // actual literals. mismatches_ contains PREDs and is of the same sizes as
640   // the comparison literals.
641   Literal mismatches_;
642 
643   // The number of mismatches to report in the output, sorted by relative error
644   // magnitude.
645   static constexpr int64 kTopRelativeErrorCount = 5;
646 
647   // The set of mismatches with the largest relative error. The size of this set
648   // is bounded by kTopRelativeErrorCount.
649   std::multiset<Mismatch> top_rel_mismatches_;
650 
651   // Actual values are bucketed by absolute value. kAbsValueBucketBounds is the
652   // bounds of these buckets. abs_value_buckets_ contains a pair for each
653   // bucket: the element count and failure count.
654   static constexpr std::array<float, 7> kAbsValueBucketBounds = {
655       0.0, 0.0001, 0.001, 0.01, 0.1, 1, std::numeric_limits<float>::infinity()};
656   std::vector<std::pair<int64, int64>> abs_value_buckets_;
657 
658   // Buckets for relative and absolute errors. The relative error buckets only
659   // contains those elements which exceed the *absolute* error bound, and vice
660   // versa. This makes it easy to see the effect of adjusting the relative (or
661   // absolute) error bound on the success of the comparison. kErrorBucketBounds
662   // are the lower bounds of the buckets in both vectors. The error buckets are
663   // a cumulative distribution so an error value may appear in more than one
664   // bucket. For example an error value of 0.003 may appear in the buckets
665   // bounded by 0.01, 0.1, and 1.0.
666   static constexpr std::array<float, 5> kErrorBucketBounds = {0.0001, 0.001,
667                                                               0.01, 0.1, 1};
668   std::vector<int64> abs_error_buckets_;
669   std::vector<int64> rel_error_buckets_;
670 };
671 
672 template <typename NativeT>
673 constexpr std::array<float, 7> NearComparator<NativeT>::kAbsValueBucketBounds;
674 template <typename NativeT>
675 constexpr std::array<float, 5> NearComparator<NativeT>::kErrorBucketBounds;
676 
EqualHelper(const LiteralSlice & expected,const LiteralSlice & actual,const ShapeIndex & shape_index,const MiscompareCallback & miscompare_callback)677 Status EqualHelper(const LiteralSlice& expected, const LiteralSlice& actual,
678                    const ShapeIndex& shape_index,
679                    const MiscompareCallback& miscompare_callback) {
680   TF_RETURN_IF_ERROR(EqualShapes(expected.shape(), actual.shape()));
681 
682   Status result;
683   if (expected.shape().IsTuple()) {
684     ShapeIndex next_index = shape_index;
685     for (int i = 0; i < ShapeUtil::TupleElementCount(expected.shape()); ++i) {
686       next_index.push_back(i);
687       Status tuple_result =
688           EqualHelper(LiteralSlice(expected, {i}), LiteralSlice(actual, {i}),
689                       next_index, miscompare_callback);
690       if (miscompare_callback) {
691         result.Update(tuple_result);
692       } else {
693         TF_RETURN_IF_ERROR(tuple_result);
694       }
695       next_index.pop_back();
696     }
697   } else {
698     std::vector<int64> multi_index(expected.shape().dimensions_size(), 0);
699     auto index = absl::MakeSpan(multi_index);
700 
701     Shape unequal_shape = ShapeUtil::MakeShape(PrimitiveType::PRED,
702                                                expected.shape().dimensions());
703     Literal miscompared(unequal_shape);
704     Literal* miscompared_ptr =
705         (miscompare_callback == nullptr ? nullptr : &miscompared);
706 
707     switch (expected.shape().element_type()) {
708       case PRED:
709         result = Equal<bool>(expected, actual, index, 0, miscompared_ptr);
710         break;
711       case S8:
712         result = Equal<int8>(expected, actual, index, 0, miscompared_ptr);
713         break;
714       case S16:
715         result = Equal<int16>(expected, actual, index, 0, miscompared_ptr);
716         break;
717       case S32:
718         result = Equal<int32>(expected, actual, index, 0, miscompared_ptr);
719         break;
720       case S64:
721         result = Equal<int64>(expected, actual, index, 0, miscompared_ptr);
722         break;
723       case U8:
724         result = Equal<uint8>(expected, actual, index, 0, miscompared_ptr);
725         break;
726       case U16:
727         result = Equal<uint16>(expected, actual, index, 0, miscompared_ptr);
728         break;
729       case U32:
730         result = Equal<uint32>(expected, actual, index, 0, miscompared_ptr);
731         break;
732       case U64:
733         result = Equal<uint64>(expected, actual, index, 0, miscompared_ptr);
734         break;
735       case BF16:
736         result = Equal<bfloat16>(expected, actual, index, 0, miscompared_ptr);
737         break;
738       case F16:
739         result = Equal<half>(expected, actual, index, 0, miscompared_ptr);
740         break;
741       case F32:
742         result = Equal<float>(expected, actual, index, 0, miscompared_ptr);
743         break;
744       case F64:
745         result = Equal<double>(expected, actual, index, 0, miscompared_ptr);
746         break;
747       case C64:
748         result = Equal<complex64>(expected, actual, index, 0, miscompared_ptr);
749         break;
750       case C128:
751         result = Equal<complex128>(expected, actual, index, 0, miscompared_ptr);
752         break;
753       case TOKEN:
754         // Tokens have no on-device representation and are trivially equal.
755         return Status::OK();
756       default:
757         LOG(FATAL) << "Unsupported primitive type: "
758                    << PrimitiveType_Name(expected.shape().element_type());
759     }
760 
761     if (!result.ok() && miscompare_callback) {
762       miscompare_callback(expected, actual, LiteralSlice(miscompared),
763                           shape_index);
764     }
765   }
766 
767   return result;
768 }
769 
770 // Helper function for comparing two literals for nearness. Handles tuple-shapes
771 // via recursion. shape_index is the ShapeIndex of expected (or actual)
772 // currently being compared.
NearHelper(const LiteralSlice & expected,const LiteralSlice & actual,const ShapeIndex & shape_index,const ErrorSpec & error,absl::optional<bool> detailed_message,const MiscompareCallback & miscompare_callback)773 Status NearHelper(const LiteralSlice& expected, const LiteralSlice& actual,
774                   const ShapeIndex& shape_index, const ErrorSpec& error,
775                   absl::optional<bool> detailed_message,
776                   const MiscompareCallback& miscompare_callback) {
777   TF_RETURN_IF_ERROR(EqualShapes(expected.shape(), actual.shape()));
778 
779   if (expected.shape().IsTuple()) {
780     Status return_status;
781     for (int64 i = 0; i < ShapeUtil::TupleElementCount(expected.shape()); ++i) {
782       const auto expected_element = LiteralSlice(expected, {i});
783       const auto actual_element = LiteralSlice(actual, {i});
784       ShapeIndex element_index = shape_index;
785       element_index.push_back(i);
786       Status element_result =
787           NearHelper(expected_element, actual_element, element_index, error,
788                      detailed_message, miscompare_callback);
789       if (!element_result.ok()) {
790         element_result = InvalidArgument("Array at shape index %s, %s",
791                                          element_index.ToString(),
792                                          element_result.error_message());
793         if (return_status.ok()) {
794           return_status = element_result;
795         } else {
796           return_status =
797               AppendStatus(return_status, element_result.error_message());
798         }
799       }
800     }
801     if (!return_status.ok() && shape_index.empty()) {
802       // Emit a top-level error message containing the top-level shape in case
803       // of mismatch.
804       int64 total_elements = RecursiveElementCount(actual.shape());
805       return_status =
806           InvalidArgument("\nMismatches in shape %s (%d elements):\n%s",
807                           ShapeUtil::HumanString(actual.shape()),
808                           total_elements, return_status.error_message());
809     }
810     return return_status;
811   }
812 
813   if (ShapeUtil::ElementIsFloating(expected.shape()) ||
814       ShapeUtil::ElementIsComplex(expected.shape())) {
815     bool use_detailed_message = detailed_message.value_or(
816         ShapeUtil::ElementsIn(expected.shape()) >= 64);
817     switch (expected.shape().element_type()) {
818       case BF16:
819         return NearComparator<bfloat16>::Compare(expected, actual, shape_index,
820                                                  error, use_detailed_message,
821                                                  miscompare_callback);
822         break;
823       case F16:
824         return NearComparator<half>::Compare(expected, actual, shape_index,
825                                              error, use_detailed_message,
826                                              miscompare_callback);
827         break;
828       case F32:
829         return NearComparator<float>::Compare(expected, actual, shape_index,
830                                               error, use_detailed_message,
831                                               miscompare_callback);
832         break;
833       case F64:
834         return NearComparator<double>::Compare(expected, actual, shape_index,
835                                                error, use_detailed_message,
836                                                miscompare_callback);
837         break;
838       case C64:
839         return NearComparator<complex64>::Compare(expected, actual, shape_index,
840                                                   error, use_detailed_message,
841                                                   miscompare_callback);
842         break;
843       case C128:
844         return NearComparator<complex128>::Compare(
845             expected, actual, shape_index, error, use_detailed_message,
846             miscompare_callback);
847         break;
848       default:
849         LOG(FATAL) << "Unsupported primitive type in near comparator: "
850                    << PrimitiveType_Name(expected.shape().element_type())
851                    << ". Must be floating-point type.";
852     }
853   }
854 
855   // Non-floating point, non-tuple literal.
856   return EqualHelper(expected, actual, shape_index, miscompare_callback);
857 }
858 
859 }  // namespace
860 
EqualShapes(const Shape & expected,const Shape & actual)861 Status EqualShapes(const Shape& expected, const Shape& actual) {
862   if (expected.element_type() != actual.element_type()) {
863     return InvalidArgument("element type mismatch, want: %s got %s",
864                            ShapeUtil::HumanString(expected),
865                            ShapeUtil::HumanString(actual));
866   }
867   if (expected.IsTuple()) {
868     if (ShapeUtil::TupleElementCount(expected) !=
869         ShapeUtil::TupleElementCount(actual)) {
870       return InvalidArgument(
871           "want tuple element count: %d got tuple element count: %d",
872           ShapeUtil::TupleElementCount(expected),
873           ShapeUtil::TupleElementCount(actual));
874     }
875     for (int i = 0; i < expected.tuple_shapes_size(); ++i) {
876       Status result =
877           EqualShapes(expected.tuple_shapes(i), actual.tuple_shapes(i));
878       if (!result.ok()) {
879         return AppendStatus(result, StrCat("mismatch in tuple index", i));
880       }
881     }
882   } else if (expected.IsArray()) {
883     if (expected.rank() != actual.rank()) {
884       return InvalidArgument("want rank of %s got rank of %s",
885                              ShapeUtil::HumanString(expected),
886                              ShapeUtil::HumanString(actual));
887     }
888     if (expected.element_type() != actual.element_type()) {
889       return InvalidArgument("mismatch in primitive type %s vs %s",
890                              PrimitiveType_Name(expected.element_type()),
891                              PrimitiveType_Name(actual.element_type()));
892     }
893     if (expected.dimensions_size() != actual.dimensions_size()) {
894       return InvalidArgument("want dimensions_size %d got dimensions_size %d",
895                              expected.dimensions_size(),
896                              actual.dimensions_size());
897     }
898     for (int i = 0; i < expected.dimensions_size(); ++i) {
899       if (expected.dimensions(i) != actual.dimensions(i)) {
900         return InvalidArgument(
901             "mismatch in dimension #%d expected: %s actual: %s", i,
902             ShapeUtil::HumanString(expected), ShapeUtil::HumanString(actual));
903       }
904     }
905   }
906   // Non-array, non-tuple shapes are trivially equivalent.
907   return Status::OK();
908 }
909 
910 namespace {
911 
912 // If result is an error, extend the error message with the expected and actual
913 // literals.
EmitLiteralsInErrorMessage(const Status & result,const LiteralSlice & expected,const LiteralSlice & actual)914 Status EmitLiteralsInErrorMessage(const Status& result,
915                                   const LiteralSlice& expected,
916                                   const LiteralSlice& actual) {
917   if (result.ok()) {
918     return result;
919   }
920   return InvalidArgument("%s\n\nExpected literal:\n%s\n\nActual literal:\n%s",
921                          result.error_message(), ToStringTruncated(expected),
922                          ToStringTruncated(actual));
923 }
924 
925 }  // namespace
926 
Equal(const LiteralSlice & expected,const LiteralSlice & actual)927 Status Equal(const LiteralSlice& expected, const LiteralSlice& actual) {
928   VLOG(1) << "expected:";
929   XLA_VLOG_LINES(1, expected.ToString());
930   VLOG(1) << "actual:";
931   XLA_VLOG_LINES(1, actual.ToString());
932   Status result = EqualHelper(expected, actual, {}, nullptr);
933   return EmitLiteralsInErrorMessage(result, expected, actual);
934 }
935 
Near(const LiteralSlice & expected,const LiteralSlice & actual,const ErrorSpec & error,absl::optional<bool> detailed_message,const MiscompareCallback & miscompare_callback)936 Status Near(const LiteralSlice& expected, const LiteralSlice& actual,
937             const ErrorSpec& error, absl::optional<bool> detailed_message,
938             const MiscompareCallback& miscompare_callback) {
939   VLOG(1) << "Expected literal:";
940   XLA_VLOG_LINES(1, expected.ToString());
941   VLOG(1) << "Actual literal:";
942   XLA_VLOG_LINES(1, actual.ToString());
943   Status result = NearHelper(expected, actual, /*shape_index=*/{}, error,
944                              detailed_message, miscompare_callback);
945   return EmitLiteralsInErrorMessage(result, expected, actual);
946 }
947 
ToStringTruncated(const LiteralSlice & literal)948 string ToStringTruncated(const LiteralSlice& literal) {
949   return RecursiveElementCount(literal.shape()) < 1000
950              ? literal.ToString()
951              : "[TRUNCATED, Literal with more than 1000 values]";
952 }
953 
954 }  // namespace literal_comparison
955 }  // namespace xla
956