|
1 | 1 | /*
|
2 | 2 | * Copyright (c) Meta Platforms, Inc. and affiliates.
|
3 | 3 | * All rights reserved.
|
| 4 | + * Copyright 2025 Arm Limited and/or its affiliates. |
4 | 5 | *
|
5 | 6 | * This source code is licensed under the BSD-style license found in the
|
6 | 7 | * LICENSE file in the root directory of this source tree.
|
@@ -346,6 +347,116 @@ ET_NODISCARD Error load_bundled_input(
|
346 | 347 | return Error::Ok;
|
347 | 348 | }
|
348 | 349 |
|
| 350 | +ET_NODISCARD ErrorStats compute_method_output_error_stats( |
| 351 | + Method& method, |
| 352 | + SerializedBundledProgram* bundled_program_ptr, |
| 353 | + size_t testset_idx) { |
| 354 | + if (!bundled_program_flatbuffer::BundledProgramBufferHasIdentifier( |
| 355 | + bundled_program_ptr)) { |
| 356 | + // The input buffer should be a bundled program. |
| 357 | + return {Error::InvalidArgument, 0, 0, 0, 0}; |
| 358 | + } |
| 359 | + |
| 360 | + auto method_test = get_method_test_suite( |
| 361 | + bundled_program_flatbuffer::GetBundledProgram(bundled_program_ptr), |
| 362 | + method); |
| 363 | + |
| 364 | + if (!method_test.ok()) { |
| 365 | + return {method_test.error(), 0, 0, 0, 0}; |
| 366 | + } |
| 367 | + |
| 368 | + auto test_cases = method_test.get()->test_cases(); |
| 369 | + |
| 370 | + if (testset_idx >= test_cases->size()) { |
| 371 | + return {Error::InvalidArgument, 0, 0, 0, 0}; |
| 372 | + } |
| 373 | + auto bundled_expected_outputs = |
| 374 | + test_cases->Get(static_cast<flatbuffers::uoffset_t>(testset_idx)) |
| 375 | + ->expected_outputs(); |
| 376 | + |
| 377 | + if (bundled_expected_outputs->size() == 0) { |
| 378 | + ET_LOG( |
| 379 | + Error, |
| 380 | + "No bundled expected outputs, so we can't verify the method outputs."); |
| 381 | + return {Error::InvalidArgument, 0, 0, 0, 0}; |
| 382 | + } |
| 383 | + |
| 384 | + // abs_err = (a - b).abs() |
| 385 | + // relative_err = (a - b).abs() / torch.maximum(torch.tensor(1e-8), |
| 386 | + // torch.maximum(a.abs(), b.abs())) |
| 387 | + double sum_abs = 0.0, max_abs = 0.0; |
| 388 | + double sum_rel = 0.0, max_rel = 0.0; |
| 389 | + // Make sure divider is bigger then eps=1e-8f to behave better around 0 values |
| 390 | + const double eps = 1e-8f; |
| 391 | + |
| 392 | + int64_t total_elems = 0; |
| 393 | + |
| 394 | + for (size_t output_idx = 0; output_idx < method.outputs_size(); |
| 395 | + output_idx++) { |
| 396 | + auto bundled_expected_output = |
| 397 | + bundled_expected_outputs->GetMutableObject(output_idx); |
| 398 | + auto method_output = method.get_output(output_idx); |
| 399 | + switch (bundled_expected_output->val_type()) { |
| 400 | + case bundled_program_flatbuffer::ValueUnion::Tensor: { |
| 401 | + auto bundled_expected_output_tensor = |
| 402 | + static_cast<bundled_program_flatbuffer::Tensor*>( |
| 403 | + bundled_expected_output->mutable_val()); |
| 404 | + const auto method_output_tensor = method_output.toTensor(); |
| 405 | + |
| 406 | +#ifdef USE_ATEN_LIB |
| 407 | + Tensor expected = tensor_like(bundled_expected_output_tensor); |
| 408 | +#else // !USE_ATEN_LIB |
| 409 | + TensorImpl impl = impl_like(bundled_expected_output_tensor); |
| 410 | + Tensor expected = Tensor(&impl); |
| 411 | +#endif |
| 412 | + // sanity check |
| 413 | + int64_t nelem = expected.numel(); |
| 414 | + if (method_output_tensor.numel() != nelem) { |
| 415 | + ET_LOG(Error, "Tensor size mismatch"); |
| 416 | + return {Error::InvalidArgument, 0, 0, 0, 0}; |
| 417 | + } |
| 418 | + |
| 419 | + // we assume float32 here; adapt for other dtypes as needed |
| 420 | + const float* e_data = expected.data_ptr<float>(); |
| 421 | + const float* a_data = method_output_tensor.data_ptr<float>(); |
| 422 | + |
| 423 | + for (int64_t k = 0; k < nelem; ++k) { |
| 424 | + double abs_err = std::abs(a_data[k] - e_data[k]); |
| 425 | + double relative_divider = |
| 426 | + std::max(std::abs(a_data[k]), std::abs(e_data[k])); |
| 427 | + relative_divider = std::max(relative_divider, eps); |
| 428 | + double relative_err = abs_err / relative_divider; |
| 429 | + |
| 430 | + sum_abs += abs_err; |
| 431 | + max_abs = std::max(max_abs, abs_err); |
| 432 | + sum_rel += relative_err; |
| 433 | + max_rel = std::max(max_rel, relative_err); |
| 434 | + } |
| 435 | + total_elems += nelem; |
| 436 | + break; |
| 437 | + } |
| 438 | + default: { |
| 439 | + ET_LOG( |
| 440 | + Error, |
| 441 | + "Data type %hhd not supported", |
| 442 | + static_cast<uint8_t>(bundled_expected_output->val_type())); |
| 443 | + return {Error::NotSupported, 0, 0, 0, 0}; |
| 444 | + break; // Never reached |
| 445 | + } |
| 446 | + } |
| 447 | + } |
| 448 | + |
| 449 | + if (total_elems == 0) { |
| 450 | + return {Error::Ok, 0, 0, 0, 0}; |
| 451 | + } |
| 452 | + return { |
| 453 | + Error::Ok, |
| 454 | + sum_abs / total_elems, |
| 455 | + max_abs, |
| 456 | + sum_rel / total_elems, |
| 457 | + max_rel}; |
| 458 | +} |
| 459 | + |
349 | 460 | ET_NODISCARD Error verify_method_outputs(
|
350 | 461 | Method& method,
|
351 | 462 | SerializedBundledProgram* bundled_program_ptr,
|
|
0 commit comments