|  | 
| 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