This is an automated email from the ASF dual-hosted git repository.

HyukjinKwon pushed a commit to branch branch-4.2
in repository https://gitbox.apache.org/repos/asf/spark.git


The following commit(s) were added to refs/heads/branch-4.2 by this push:
     new 2f2901d340bb [SPARK-57951][ML][TEST] Tolerate last-ULP FP differences 
in MLTest single-prediction checks for macOS
2f2901d340bb is described below

commit 2f2901d340bb662efdc94934a1367d504bf8f99a
Author: Hyukjin Kwon <[email protected]>
AuthorDate: Mon Jul 6 17:29:45 2026 +0900

    [SPARK-57951][ML][TEST] Tolerate last-ULP FP differences in MLTest 
single-prediction checks for macOS
    
    ### What changes were proposed in this pull request?
    
    `MLTest.testClassificationModelSingleRawPrediction` and
    `testProbClassificationModelSingleProbPrediction` compared the DataFrame 
`transform`
    output against the scalar `predictRaw` / `predictProbability` output with 
exact `===`
    equality. This PR compares the two prediction vectors with a tight absolute 
tolerance
    (`1e-9`) via a small `assertVectorsAlmostEqual` helper instead.
    
    ### Why are the changes needed?
    
    On arm64 macOS the DataFrame path and the scalar path can round differently 
in the last
    ULP (e.g. `1.543502002724983` vs `1.5435020027249835`), so the exact `===` 
check fails:
    
    ```
    [1.543502002724983,-1.543502002724983] did not equal
    [1.5435020027249835,-1.5435020027249835] (MLTest.scala:199)
    ```
    
    This fails suites such as `MultilayerPerceptronClassifierSuite` on the 
scheduled
    `Build / Maven (Scala 2.13, JDK 21, MacOS-26)` lane, while the same tests 
are bit-identical
    on Linux. A `1e-9` absolute tolerance absorbs last-ULP platform rounding 
while still catching
    any real discrepancy between the two code paths.
    
    ### Does this PR introduce _any_ user-facing change?
    
    No. Test-only.
    
    ### How was this patch tested?
    
    Ran the affected classifier suites on a `macos-15` GitHub Actions runner 
(arm64, same class as
    `macos-26`):
    
    - `MultilayerPerceptronClassifierSuite`, `LogisticRegressionSuite`, 
`LinearSVCSuite`,
      `NaiveBayesSuite` — **113 tests, 0 failures** (the `prediction on single 
instance` cases that
      previously failed now pass).
    
    Passed: https://github.com/HyukjinKwon/spark/actions/runs/28766568832
    
    Before this change the same suites fail on `macos-26`:
    https://github.com/apache/spark/actions/runs/28753698265
    
    ### Was this patch authored or co-authored using generative AI tooling?
    
    Generated-by: Claude Code
    
    Closes #57032 from HyukjinKwon/ci-fix/agent4-mllib-fp-tol-pr.
    
    Authored-by: Hyukjin Kwon <[email protected]>
    Signed-off-by: Hyukjin Kwon <[email protected]>
    (cherry picked from commit fa81bf5a1dd3ec955a18186ff8038fb5a234d02d)
    Signed-off-by: Hyukjin Kwon <[email protected]>
---
 .../scala/org/apache/spark/ml/util/MLTest.scala    | 23 ++++++++++++++++++++--
 1 file changed, 21 insertions(+), 2 deletions(-)

diff --git a/mllib/src/test/scala/org/apache/spark/ml/util/MLTest.scala 
b/mllib/src/test/scala/org/apache/spark/ml/util/MLTest.scala
index a35b19b2816e..23d27162654c 100644
--- a/mllib/src/test/scala/org/apache/spark/ml/util/MLTest.scala
+++ b/mllib/src/test/scala/org/apache/spark/ml/util/MLTest.scala
@@ -196,7 +196,11 @@ trait MLTest extends StreamTest with TempDirectory { self: 
Suite =>
     model.transform(dataset).select(model.getFeaturesCol, 
model.getRawPredictionCol)
       .collect().foreach {
       case Row(features: Vector, rawPrediction: Vector) =>
-        assert(rawPrediction === model.predictRaw(features))
+        // Compare with a tight tolerance rather than exact equality: the 
DataFrame
+        // `transform` path and the scalar `predictRaw` path can round 
differently in the
+        // last ULP on some platforms (e.g. arm64 macOS, where native BLAS 
diverges from
+        // JVM scalar math), which broke the MacOS-26 lane while Linux stayed 
bit-identical.
+        assertVectorsAlmostEqual(rawPrediction, model.predictRaw(features))
     }
   }
 
@@ -206,7 +210,22 @@ trait MLTest extends StreamTest with TempDirectory { self: 
Suite =>
     model.transform(dataset).select(model.getFeaturesCol, 
model.getProbabilityCol)
       .collect().foreach {
       case Row(features: Vector, probPrediction: Vector) =>
-        assert(probPrediction === model.predictProbability(features))
+        assertVectorsAlmostEqual(probPrediction, 
model.predictProbability(features))
+    }
+  }
+
+  /**
+   * Asserts two prediction vectors are equal up to a tight absolute 
tolerance. Used instead of
+   * exact `===` so that last-ULP rounding differences between the DataFrame 
`transform` path and
+   * the scalar `predict*` path (observed on arm64 macOS) do not fail the 
tests, while any real
+   * discrepancy is still caught.
+   */
+  private def assertVectorsAlmostEqual(actual: Vector, expected: Vector): Unit 
= {
+    assert(actual.size === expected.size,
+      s"vector sizes differ: ${actual.size} vs ${expected.size}")
+    actual.toArray.zip(expected.toArray).zipWithIndex.foreach { case ((a, e), 
i) =>
+      assert(math.abs(a - e) <= 1e-9,
+        s"prediction vectors differ at index $i: $a vs $e (actual=$actual, 
expected=$expected)")
     }
   }
 


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to