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