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/09/02 00:42:12 UTC

(spark) branch master updated: [SPARK-59152][ML] Create CountVectorizer broadcast per transform

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 065397779ce4 [SPARK-59152][ML] Create CountVectorizer broadcast per transform
065397779ce4 is described below

commit 065397779ce409996abed999e1d6b46ee98a6c34
Author: Ruifeng Zheng <ru...@apache.org>
AuthorDate: Wed Sep 2 08:41:59 2026 +0800

    [SPARK-59152][ML] Create CountVectorizer broadcast per transform
    
    ### What changes were proposed in this pull request?
    
    This PR creates the `CountVectorizerModel` dictionary and its broadcast for each `transform` call
    instead of caching the broadcast on the model.
    
    ### Why are the changes needed?
    
    The dictionary broadcast is transformation-only execution state. Retaining it on the model keeps the
    broadcast alive for the model's lifetime and ties later transformations to the `SparkContext` used by
    the first transformation. A transform-scoped broadcast follows the lifecycle of the returned lazy
    DataFrame. This is consistent with the other explicit inference broadcasts in
    `org.apache.spark.ml`: `Word2VecModel` broadcasts its word-vector model per transform, while
    `RandomForestRegressionModel` and `GBTRegressionModel` broadcast their models per transform and
    share each broadcast between the output UDFs created by that transform.
    
    ### Does this PR introduce _any_ user-facing change?
    
    No.
    
    ### How was this patch tested?
    
    No tests were added because this is an internal broadcast lifecycle change and the transformation
    logic is unchanged. The patch was checked with `git diff --check` and source line-length and
    non-ASCII scans.
    
    ### Was this patch authored or co-authored using generative AI tooling?
    
    Generated-by: OpenAI Codex (GPT-5)
    
    Closes #58452 from zhengruifeng/ml-countvectorizer-transform-broadcast-dev-5.
    
    Authored-by: Ruifeng Zheng <ru...@apache.org>
    Signed-off-by: Ruifeng Zheng <ru...@foxmail.com>
---
 .../apache/spark/ml/feature/CountVectorizer.scala  | 25 ++++++++--------------
 1 file changed, 9 insertions(+), 16 deletions(-)

diff --git a/mllib/src/main/scala/org/apache/spark/ml/feature/CountVectorizer.scala b/mllib/src/main/scala/org/apache/spark/ml/feature/CountVectorizer.scala
index bd1917bb9e34..35ec4afcb421 100644
--- a/mllib/src/main/scala/org/apache/spark/ml/feature/CountVectorizer.scala
+++ b/mllib/src/main/scala/org/apache/spark/ml/feature/CountVectorizer.scala
@@ -22,7 +22,6 @@ import java.util.{HashMap => JHashMap}
 import org.apache.hadoop.fs.Path
 
 import org.apache.spark.annotation.Since
-import org.apache.spark.broadcast.Broadcast
 import org.apache.spark.ml.{Estimator, Model}
 import org.apache.spark.ml.attribute.{Attribute, AttributeGroup, NumericAttribute}
 import org.apache.spark.ml.linalg.{SQLDataTypes, Vectors}
@@ -297,31 +296,25 @@ class CountVectorizerModel(
   @Since("2.0.0")
   def setBinary(value: Boolean): this.type = set(binary, value)
 
-  /** Dictionary created from [[vocabulary]] and its indices, broadcast once for [[transform()]] */
-  private var broadcastDict: Option[Broadcast[JHashMap[String, Integer]]] = None
-
   @Since("2.0.0")
   override def transform(dataset: Dataset[_]): DataFrame = {
     val outputSchema = transformSchema(dataset.schema, logging = true)
-    if (broadcastDict.isEmpty) {
-      val dict = new JHashMap[String, Integer](math.ceil(vocabulary.length / 0.75).toInt)
-      var index = 0
-      while (index < vocabulary.length) {
-        dict.put(vocabulary(index), index)
-        index += 1
-      }
-      broadcastDict = Some(dataset.sparkSession.sparkContext.broadcast(dict))
+    val dict = new JHashMap[String, Integer](math.ceil(vocabulary.length / 0.75).toInt)
+    var index = 0
+    while (index < vocabulary.length) {
+      dict.put(vocabulary(index), index)
+      index += 1
     }
-    val dictBr = broadcastDict.get
+    val bcDict = dataset.sparkSession.sparkContext.broadcast(dict)
     val localMinTF = $(minTF)
     val localBinary = $(binary)
     val vectorizer = udf { document: Seq[String] =>
-      val dict = dictBr.value
-      val dictSize = dict.size()
+      val localDict = bcDict.value
+      val dictSize = localDict.size()
       val termCounts = new OpenHashMap[Int, Int]
       var tokenCount = 0L
       document.foreach { term =>
-        val index = dict.get(term)
+        val index = localDict.get(term)
         if (index != null) {
           termCounts.changeValue(index.intValue(), 1, _ + 1)
         }


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