You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@spark.apache.org by ru...@apache.org on 2026/10/13 00:58:14 UTC

(spark) branch master updated: [SPARK-59563][ML] Use BLAS routines for vector L1 and L2 norms

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

zhengruifeng pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/spark.git


The following commit(s) were added to refs/heads/master by this push:
     new 4c07f63d8ddc [SPARK-59563][ML] Use BLAS routines for vector L1 and L2 norms
4c07f63d8ddc is described below

commit 4c07f63d8ddcf6fd02acce490f2baba379be4e38
Author: Ruifeng Zheng <ru...@apache.org>
AuthorDate: Tue Oct 13 08:57:51 2026 +0800

    [SPARK-59563][ML] Use BLAS routines for vector L1 and L2 norms
    
    ### What changes were proposed in this pull request?
    
    This PR adds `BLAS.asum(Vector)` and `BLAS.nrm2(Vector)` to both ML and MLlib and uses them for
    L1 and L2 vector norms. Dense vectors pass their backing arrays to BLAS, while sparse vectors pass
    only their stored values.
    
    ### Why are the changes needed?
    
    The existing Scala loops do not use the SIMD BLAS implementation available to Spark.
    
    On an Intel Xeon Platinum 8375C with JDK 17 and `VectorBLAS`, a local benchmark measured these
    speedups over the existing Scala loops:
    
    | Stored values | L1 | L2 |
    | ---: | ---: | ---: |
    | 16 | 1.6x | 2.1x |
    | 256 | 8.4x | 9.2x |
    | 4,096 | 7.7x | 8.3x |
    | 65,536 | 6.1x | 7.2x |
    
    Dense and sparse vectors had equivalent results for the same stored-value count.
    
    ### Does this PR introduce _any_ user-facing change?
    
    No. This replaces the internal norm loops with equivalent BLAS routines.
    
    ### How was this patch tested?
    
    Ran the existing dense and sparse vector norm coverage:
    
    ```
    build/sbt "mllib-local/testOnly org.apache.spark.ml.linalg.VectorsSuite"
    build/sbt "mllib/testOnly org.apache.spark.mllib.linalg.VectorsSuite"
    ```
    
    All 76 tests passed (36 in `mllib-local`, 40 in `mllib`). The performance results above were
    measured with a temporary Spark `Benchmark` harness that compared the previous loops with
    `Vectors.norm`; the harness is not included in this PR.
    
    ### Was this patch authored or co-authored using generative AI tooling?
    
    Generated-by: Codex (GPT-5)
    
    Closes #58849 from zhengruifeng/ml-blas-vector-norms-dev-7.
    
    Authored-by: Ruifeng Zheng <ru...@apache.org>
    Signed-off-by: Ruifeng Zheng <ru...@apache.org>
---
 .../scala/org/apache/spark/ml/linalg/BLAS.scala    | 26 +++++++++
 .../scala/org/apache/spark/ml/linalg/Vectors.scala | 61 +++++++++-------------
 .../scala/org/apache/spark/mllib/linalg/BLAS.scala | 26 +++++++++
 .../org/apache/spark/mllib/linalg/Vectors.scala    | 61 +++++++++-------------
 4 files changed, 104 insertions(+), 70 deletions(-)

diff --git a/mllib-local/src/main/scala/org/apache/spark/ml/linalg/BLAS.scala b/mllib-local/src/main/scala/org/apache/spark/ml/linalg/BLAS.scala
index d07eb890dc32..6e508bdf92f6 100644
--- a/mllib-local/src/main/scala/org/apache/spark/ml/linalg/BLAS.scala
+++ b/mllib-local/src/main/scala/org/apache/spark/ml/linalg/BLAS.scala
@@ -190,6 +190,32 @@ private[spark] object BLAS extends Serializable {
     sum
   }
 
+  /**
+   * sum(abs(x))
+   */
+  def asum(x: Vector): Double = {
+    val values = x match {
+      case sx: SparseVector => sx.values
+      case dx: DenseVector => dx.values
+      case _ =>
+        throw new IllegalArgumentException(s"asum doesn't support vector type ${x.getClass}.")
+    }
+    getBLAS(values.length).dasum(values.length, values, 1)
+  }
+
+  /**
+   * sqrt(sum(x_i^2))
+   */
+  def nrm2(x: Vector): Double = {
+    val values = x match {
+      case sx: SparseVector => sx.values
+      case dx: DenseVector => dx.values
+      case _ =>
+        throw new IllegalArgumentException(s"nrm2 doesn't support vector type ${x.getClass}.")
+    }
+    getBLAS(values.length).dnrm2(values.length, values, 1)
+  }
+
   /**
    * y = x
    */
diff --git a/mllib-local/src/main/scala/org/apache/spark/ml/linalg/Vectors.scala b/mllib-local/src/main/scala/org/apache/spark/ml/linalg/Vectors.scala
index 95bd3c93a8c6..0d490f1d506e 100644
--- a/mllib-local/src/main/scala/org/apache/spark/ml/linalg/Vectors.scala
+++ b/mllib-local/src/main/scala/org/apache/spark/ml/linalg/Vectors.scala
@@ -337,46 +337,37 @@ object Vectors {
   def norm(vector: Vector, p: Double): Double = {
     require(p >= 1.0, "To compute the p-norm of the vector, we require that you specify a p>=1. " +
       s"You specified p=$p.")
-    val values = vector match {
-      case DenseVector(vs) => vs
-      case SparseVector(n, ids, vs) => vs
-      case v => throw new IllegalArgumentException("Do not support vector type " + v.getClass)
-    }
-    val size = values.length
 
     if (p == 1) {
-      var sum = 0.0
-      var i = 0
-      while (i < size) {
-        sum += math.abs(values(i))
-        i += 1
-      }
-      sum
+      BLAS.asum(vector)
     } else if (p == 2) {
-      var sum = 0.0
-      var i = 0
-      while (i < size) {
-        sum += values(i) * values(i)
-        i += 1
-      }
-      math.sqrt(sum)
-    } else if (p == Double.PositiveInfinity) {
-      var max = 0.0
-      var i = 0
-      while (i < size) {
-        val value = math.abs(values(i))
-        if (value > max) max = value
-        i += 1
-      }
-      max
+      BLAS.nrm2(vector)
     } else {
-      var sum = 0.0
-      var i = 0
-      while (i < size) {
-        sum += math.pow(math.abs(values(i)), p)
-        i += 1
+      val values = vector match {
+        case DenseVector(vs) => vs
+        case SparseVector(n, ids, vs) => vs
+        case v => throw new IllegalArgumentException("Do not support vector type " + v.getClass)
+      }
+      val size = values.length
+
+      if (p == Double.PositiveInfinity) {
+        var max = 0.0
+        var i = 0
+        while (i < size) {
+          val value = math.abs(values(i))
+          if (value > max) max = value
+          i += 1
+        }
+        max
+      } else {
+        var sum = 0.0
+        var i = 0
+        while (i < size) {
+          sum += math.pow(math.abs(values(i)), p)
+          i += 1
+        }
+        math.pow(sum, 1.0 / p)
       }
-      math.pow(sum, 1.0 / p)
     }
   }
 
diff --git a/mllib/src/main/scala/org/apache/spark/mllib/linalg/BLAS.scala b/mllib/src/main/scala/org/apache/spark/mllib/linalg/BLAS.scala
index 637380752c1d..5072240009b6 100644
--- a/mllib/src/main/scala/org/apache/spark/mllib/linalg/BLAS.scala
+++ b/mllib/src/main/scala/org/apache/spark/mllib/linalg/BLAS.scala
@@ -192,6 +192,32 @@ private[spark] object BLAS extends Serializable with Logging {
     sum
   }
 
+  /**
+   * sum(abs(x))
+   */
+  def asum(x: Vector): Double = {
+    val values = x match {
+      case sx: SparseVector => sx.values
+      case dx: DenseVector => dx.values
+      case _ =>
+        throw new IllegalArgumentException(s"asum doesn't support vector type ${x.getClass}.")
+    }
+    getBLAS(values.length).dasum(values.length, values, 1)
+  }
+
+  /**
+   * sqrt(sum(x_i^2))
+   */
+  def nrm2(x: Vector): Double = {
+    val values = x match {
+      case sx: SparseVector => sx.values
+      case dx: DenseVector => dx.values
+      case _ =>
+        throw new IllegalArgumentException(s"nrm2 doesn't support vector type ${x.getClass}.")
+    }
+    getBLAS(values.length).dnrm2(values.length, values, 1)
+  }
+
   /**
    * y = x
    */
diff --git a/mllib/src/main/scala/org/apache/spark/mllib/linalg/Vectors.scala b/mllib/src/main/scala/org/apache/spark/mllib/linalg/Vectors.scala
index 47209a5c9aa9..3533ac43ba54 100644
--- a/mllib/src/main/scala/org/apache/spark/mllib/linalg/Vectors.scala
+++ b/mllib/src/main/scala/org/apache/spark/mllib/linalg/Vectors.scala
@@ -491,46 +491,37 @@ object Vectors {
   def norm(vector: Vector, p: Double): Double = {
     require(p >= 1.0, "To compute the p-norm of the vector, we require that you specify a p>=1. " +
       s"You specified p=$p.")
-    val values = vector match {
-      case DenseVector(vs) => vs
-      case SparseVector(n, ids, vs) => vs
-      case v => throw new IllegalArgumentException("Do not support vector type " + v.getClass)
-    }
-    val size = values.length
 
     if (p == 1) {
-      var sum = 0.0
-      var i = 0
-      while (i < size) {
-        sum += math.abs(values(i))
-        i += 1
-      }
-      sum
+      BLAS.asum(vector)
     } else if (p == 2) {
-      var sum = 0.0
-      var i = 0
-      while (i < size) {
-        sum += values(i) * values(i)
-        i += 1
-      }
-      math.sqrt(sum)
-    } else if (p == Double.PositiveInfinity) {
-      var max = 0.0
-      var i = 0
-      while (i < size) {
-        val value = math.abs(values(i))
-        if (value > max) max = value
-        i += 1
-      }
-      max
+      BLAS.nrm2(vector)
     } else {
-      var sum = 0.0
-      var i = 0
-      while (i < size) {
-        sum += math.pow(math.abs(values(i)), p)
-        i += 1
+      val values = vector match {
+        case DenseVector(vs) => vs
+        case SparseVector(n, ids, vs) => vs
+        case v => throw new IllegalArgumentException("Do not support vector type " + v.getClass)
+      }
+      val size = values.length
+
+      if (p == Double.PositiveInfinity) {
+        var max = 0.0
+        var i = 0
+        while (i < size) {
+          val value = math.abs(values(i))
+          if (value > max) max = value
+          i += 1
+        }
+        max
+      } else {
+        var sum = 0.0
+        var i = 0
+        while (i < size) {
+          sum += math.pow(math.abs(values(i)), p)
+          i += 1
+        }
+        math.pow(sum, 1.0 / p)
       }
-      math.pow(sum, 1.0 / p)
     }
   }
 


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@spark.apache.org
For additional commands, e-mail: commits-help@spark.apache.org