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

Yicong-Huang pushed a commit to branch branch-4.3
in repository https://gitbox.apache.org/repos/asf/spark.git


The following commit(s) were added to refs/heads/branch-4.3 by this push:
     new b4cbf6b17c4f [SPARK-58177][GRAPHX][TEST][FOLLOWUP] Add regression test 
for SVD++ aggregateMessages combiner
b4cbf6b17c4f is described below

commit b4cbf6b17c4faa64201d45f7c21fff86d4b407d2
Author: Matthew B. <[email protected]>
AuthorDate: Wed Aug 5 00:01:25 2026 -0700

    [SPARK-58177][GRAPHX][TEST][FOLLOWUP] Add regression test for SVD++ 
aggregateMessages combiner
    
    ### What changes were proposed in this pull request?
    
    Follow-up to 
[SPARK-58177](https://issues.apache.org/jira/browse/SPARK-58177) / #57314, 
which fixed a wrong operand in the SVD++ Phase 2 `aggregateMessages` combiner 
but merged without a test. This adds the regression test:
    
    - Extract the Phase 2 training-message merge logic into a package-private 
`SVDPlusPlus.combineTrainMessages`, and use it in place of the inline 
`aggregateMessages` combiner.
    - Add a unit test in `SVDPlusPlusSuite` that asserts both vectors and the 
scalar are summed element-wise, and that the input messages are not mutated.
    
    ### Why are the changes needed?
    
    The fixed bug (the second vector was based on `g2._2`, which dropped 
`g1._2` and doubled `g2._2`) only manifests when a vertex merges two or more 
differing messages, and the combiner was an inline lambda that could not be 
tested directly. Extracting it makes the merge logic unit-testable and guards 
against a future regression.
    
    ### Does this PR introduce _any_ user-facing change?
    
    No.
    
    ### How was this patch tested?
    
    Added a unit test in `SVDPlusPlusSuite`; the full suite passes locally. The 
test was confirmed to fail when the pre-fix operand (`g2._2`) is restored and 
to pass with the fix in place.
    
    ### Was this patch authored or co-authored using generative AI tooling?
    
    No
    
    Closes #57392 from Ma77Ball/SPARK-58231-svdpp-combiner-regression-test.
    
    Authored-by: Matthew B. <[email protected]>
    Signed-off-by: Yicong Huang <[email protected]>
    (cherry picked from commit 15b4cdf0b9e148d6bc5205e45edd67785ae64a74)
    Signed-off-by: Yicong Huang <[email protected]>
---
 .../org/apache/spark/graphx/lib/SVDPlusPlus.scala    | 20 ++++++++++++--------
 .../apache/spark/graphx/lib/SVDPlusPlusSuite.scala   | 15 +++++++++++++++
 2 files changed, 27 insertions(+), 8 deletions(-)

diff --git 
a/graphx/src/main/scala/org/apache/spark/graphx/lib/SVDPlusPlus.scala 
b/graphx/src/main/scala/org/apache/spark/graphx/lib/SVDPlusPlus.scala
index 7b282ec6d24e..beb15b0ccbc5 100644
--- a/graphx/src/main/scala/org/apache/spark/graphx/lib/SVDPlusPlus.scala
+++ b/graphx/src/main/scala/org/apache/spark/graphx/lib/SVDPlusPlus.scala
@@ -38,6 +38,17 @@ object SVDPlusPlus {
       var gamma7: Double)
     extends Serializable
 
+  /** Sums two Phase 2 training messages component-wise: both vectors and the 
scalar. */
+  private[graphx] def combineTrainMessages(
+      g1: (Array[Double], Array[Double], Double),
+      g2: (Array[Double], Array[Double], Double)): (Array[Double], 
Array[Double], Double) = {
+    val out1 = g1._1.clone()
+    BLAS.nativeBLAS.daxpy(out1.length, 1.0, g2._1, 1, out1, 1)
+    val out2 = g1._2.clone()
+    BLAS.nativeBLAS.daxpy(out2.length, 1.0, g2._2, 1, out2, 1)
+    (out1, out2, g1._3 + g2._3)
+  }
+
   // scalastyle:off line.size.limit
   /**
    * Implement SVD++ based on "Factorization Meets the Neighborhood: a 
Multifaceted
@@ -152,14 +163,7 @@ object SVDPlusPlus {
       g.cache()
       val t2 = g.aggregateMessages(
         sendMsgTrainF(conf, u),
-        (g1: (Array[Double], Array[Double], Double), g2: (Array[Double], 
Array[Double], Double)) =>
-        {
-          val out1 = g1._1.clone()
-          BLAS.nativeBLAS.daxpy(out1.length, 1.0, g2._1, 1, out1, 1)
-          val out2 = g1._2.clone()
-          BLAS.nativeBLAS.daxpy(out2.length, 1.0, g2._2, 1, out2, 1)
-          (out1, out2, g1._3 + g2._3)
-        })
+        combineTrainMessages)
       val gJoinT2 = g.outerJoinVertices(t2) {
         (vid: VertexId,
          vd: (Array[Double], Array[Double], Double, Double),
diff --git 
a/graphx/src/test/scala/org/apache/spark/graphx/lib/SVDPlusPlusSuite.scala 
b/graphx/src/test/scala/org/apache/spark/graphx/lib/SVDPlusPlusSuite.scala
index da139cec5c3d..62f257d2cce3 100644
--- a/graphx/src/test/scala/org/apache/spark/graphx/lib/SVDPlusPlusSuite.scala
+++ b/graphx/src/test/scala/org/apache/spark/graphx/lib/SVDPlusPlusSuite.scala
@@ -49,4 +49,19 @@ class SVDPlusPlusSuite extends SparkFunSuite with 
LocalSparkContext {
       assert(graph.edges.count() == 0)
     }
   }
+
+  test("SPARK-58177: training-phase message combiner sums both vectors 
element-wise") {
+    val g1 = (Array(1.0, 2.0), Array(10.0, 20.0), 100.0)
+    val g2 = (Array(3.0, 4.0), Array(30.0, 40.0), 200.0)
+    val (out1, out2, out3) = SVDPlusPlus.combineTrainMessages(g1, g2)
+    // Each component is the element-wise sum of the two messages.
+    assert(out1.toSeq === Seq(4.0, 6.0))
+    assert(out2.toSeq === Seq(40.0, 60.0))
+    assert(out3 === 300.0)
+    // Inputs are cloned, not mutated.
+    assert(g1._1.toSeq === Seq(1.0, 2.0))
+    assert(g1._2.toSeq === Seq(10.0, 20.0))
+    assert(g2._1.toSeq === Seq(3.0, 4.0))
+    assert(g2._2.toSeq === Seq(30.0, 40.0))
+  }
 }


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

Reply via email to