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/08/19 10:41:11 UTC

(spark) branch master updated: [SPARK-58854][ML] Optimize GeneralizedLinearRegressionModel transform closure

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 1f0641f4bc92 [SPARK-58854][ML] Optimize GeneralizedLinearRegressionModel transform closure
1f0641f4bc92 is described below

commit 1f0641f4bc92a12ac983d6282a82d67ea36d59a9
Author: Ruifeng Zheng <ru...@apache.org>
AuthorDate: Wed Aug 19 18:40:58 2026 +0800

    [SPARK-58854][ML] Optimize GeneralizedLinearRegressionModel transform closure
    
    ### What changes were proposed in this pull request?
    
    This PR optimizes `GeneralizedLinearRegressionModel.transform` by snapshotting the prediction
    state before creating transform UDFs. The UDFs now capture only the required coefficients,
    intercept, and `FamilyAndLink` instead of the full model.
    
    The public prediction methods continue to use the same prediction logic through companion helper
    methods.
    
    ### Why are the changes needed?
    
    Avoiding the model capture reduces transform-closure retained memory, which is useful for Spark
    Connect server workloads and follows the closure-size optimizations tracked under SPARK-58584.
    
    ### Does this PR introduce _any_ user-facing change?
    
    No.
    
    ### How was this patch tested?
    
    Ran local static checks:
    
    ```
    git diff --check
    grep -rn -P "[^\x00-\x7F]" mllib/src/main/scala/org/apache/spark/ml/regression/GeneralizedLinearRegression.scala
    awk 'length>100 && $0 !~ /^[[:space:]]*(import|package) / && $0 !~ /https?:\/\// {print FILENAME":"FNR": "length" chars"}' mllib/src/main/scala/org/apache/spark/ml/regression/GeneralizedLinearRegression.scala
    ```
    
    Measured serialized `ScalaUDF.function` closure size with a temporary local probe:
    
    | Case | Before | After | Delta |
    |---|---:|---:|---:|
    | `directPrediction` | 6753 B | 3033 B | -3720 B, -55.1% |
    | `linkAndPrediction` total | 13450 B | 9559 B | -3891 B, -28.9% |
    
    For `linkAndPrediction`, the two UDF closures measured individually as:
    
    | UDF | Before | After | Delta |
    |---:|---:|---:|---:|
    | `udf#0` | 6669 B | 7482 B | +813 B, +12.2% |
    | `udf#1` | 6781 B | 2077 B | -4704 B, -69.4% |
    
    No unit test was added because this is an internal refactor of existing prediction logic.
    
    ### Was this patch authored or co-authored using generative AI tooling?
    
    Generated-by: Codex (GPT-5)
    
    Closes #58100 from zhengruifeng/spark-58854-glr-transform-closure.
    
    Authored-by: Ruifeng Zheng <ru...@apache.org>
    Signed-off-by: Ruifeng Zheng <ru...@foxmail.com>
---
 .../regression/GeneralizedLinearRegression.scala   | 37 ++++++++++++++++++----
 1 file changed, 31 insertions(+), 6 deletions(-)

diff --git a/mllib/src/main/scala/org/apache/spark/ml/regression/GeneralizedLinearRegression.scala b/mllib/src/main/scala/org/apache/spark/ml/regression/GeneralizedLinearRegression.scala
index f4d6e4e3562a..de10d1e7aed7 100644
--- a/mllib/src/main/scala/org/apache/spark/ml/regression/GeneralizedLinearRegression.scala
+++ b/mllib/src/main/scala/org/apache/spark/ml/regression/GeneralizedLinearRegression.scala
@@ -1032,22 +1032,22 @@ class GeneralizedLinearRegressionModel private[ml] (
   private lazy val familyAndLink = FamilyAndLink(this)
 
   override def predict(features: Vector): Double = {
-    predict(features, 0.0)
+    GeneralizedLinearRegressionModel.predict(features, 0.0, coefficients, intercept, familyAndLink)
   }
 
   /**
    * Calculates the predicted value when offset is set.
    */
   private def predict(features: Vector, offset: Double): Double = {
-    val eta = predictLink(features, offset)
-    familyAndLink.fitted(eta)
+    GeneralizedLinearRegressionModel.predict(
+      features, offset, coefficients, intercept, familyAndLink)
   }
 
   /**
    * Calculates the link prediction (linear predictor) of the given instance.
    */
   private def predictLink(features: Vector, offset: Double): Double = {
-    BLAS.dot(features, coefficients) + intercept + offset
+    GeneralizedLinearRegressionModel.predictLink(features, offset, coefficients, intercept)
   }
 
   override def transform(dataset: Dataset[_]): DataFrame = {
@@ -1061,9 +1061,14 @@ class GeneralizedLinearRegressionModel private[ml] (
     val offset = if (!hasOffsetCol) lit(0.0) else col($(offsetCol)).cast(DoubleType)
     var outputData = dataset
     var numColsOutput = 0
+    val localCoefficients = coefficients
+    val localIntercept = intercept
+    val localFamilyAndLink = familyAndLink
 
     if (hasLinkPredictionCol) {
-      val predLinkUDF = udf((features: Vector, offset: Double) => predictLink(features, offset))
+      val predLinkUDF = udf((features: Vector, offset: Double) =>
+        GeneralizedLinearRegressionModel.predictLink(
+          features, offset, localCoefficients, localIntercept))
       outputData = outputData
         .withColumn($(linkPredictionCol), predLinkUDF(col($(featuresCol)), offset),
           outputSchema($(linkPredictionCol)).metadata)
@@ -1076,7 +1081,9 @@ class GeneralizedLinearRegressionModel private[ml] (
         outputData = outputData.withColumn($(predictionCol), predUDF(col($(linkPredictionCol))),
           outputSchema($(predictionCol)).metadata)
       } else {
-        val predUDF = udf((features: Vector, offset: Double) => predict(features, offset))
+        val predUDF = udf((features: Vector, offset: Double) =>
+          GeneralizedLinearRegressionModel.predict(
+            features, offset, localCoefficients, localIntercept, localFamilyAndLink))
         outputData = outputData.withColumn($(predictionCol), predUDF(col($(featuresCol)), offset),
           outputSchema($(predictionCol)).metadata)
       }
@@ -1177,6 +1184,24 @@ class GeneralizedLinearRegressionModel private[ml] (
 object GeneralizedLinearRegressionModel extends MLReadable[GeneralizedLinearRegressionModel] {
   private[ml] case class Data(intercept: Double, coefficients: Vector)
 
+  private def predict(
+      features: Vector,
+      offset: Double,
+      coefficients: Vector,
+      intercept: Double,
+      familyAndLink: GeneralizedLinearRegression.FamilyAndLink): Double = {
+    val eta = predictLink(features, offset, coefficients, intercept)
+    familyAndLink.fitted(eta)
+  }
+
+  private def predictLink(
+      features: Vector,
+      offset: Double,
+      coefficients: Vector,
+      intercept: Double): Double = {
+    BLAS.dot(features, coefficients) + intercept + offset
+  }
+
   private[ml] def serializeData(data: Data, dos: DataOutputStream): Unit = {
     import ReadWriteUtils._
     dos.writeDouble(data.intercept)


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