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