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

jinchengchenghh pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/gluten.git


The following commit(s) were added to refs/heads/main by this push:
     new 381c806ec2 [VL] Extend cuDF functions in Gluten with 
row_constructor_with_null (#13104)
381c806ec2 is described below

commit 381c806ec2d65ed3262b67407f2e604902f11e49
Author: Reema <[email protected]>
AuthorDate: Wed Oct 28 15:15:31 2026 +0300

    [VL] Extend cuDF functions in Gluten with row_constructor_with_null (#13104)
---
 .../gluten/execution/CudfAggregateSuite.scala      |  73 ++++++++++
 cpp/velox/CMakeLists.txt                           |   1 +
 cpp/velox/compute/VeloxBackend.cc                  |   2 +
 cpp/velox/cudf/CudfFunctions.cc                    | 101 ++++++++++++++
 cpp/velox/cudf/CudfFunctions.h                     |  25 ++++
 cpp/velox/tests/CMakeLists.txt                     |   1 +
 cpp/velox/tests/CudfFunctionsTest.cc               | 151 +++++++++++++++++++++
 7 files changed, 354 insertions(+)

diff --git 
a/backends-velox/src/test/scala/org/apache/gluten/execution/CudfAggregateSuite.scala
 
b/backends-velox/src/test/scala/org/apache/gluten/execution/CudfAggregateSuite.scala
new file mode 100644
index 0000000000..fe937d3b20
--- /dev/null
+++ 
b/backends-velox/src/test/scala/org/apache/gluten/execution/CudfAggregateSuite.scala
@@ -0,0 +1,73 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.gluten.execution
+
+import org.apache.gluten.tags.CudfTest
+
+import org.apache.spark.SparkConf
+import org.apache.spark.sql.execution.adaptive.ColumnarAQEShuffleReadExec
+
+/** GPU regression coverage for Gluten-specific aggregate intermediate rows. */
+@CudfTest
+class CudfAggregateSuite extends VeloxWholeStageTransformerSuite {
+
+  override protected val resourcePath: String = "/tpch-data-parquet"
+  override protected val fileFormat: String = "parquet"
+
+  override protected def sparkConf: SparkConf = {
+    super.sparkConf
+      .set("spark.gluten.sql.columnar.cudf", "true")
+      .set("spark.gluten.sql.columnar.backend.velox.cudf.allowCpuFallback", 
"false")
+      .set("spark.sql.shuffle.partitions", "1")
+      .set("spark.shuffle.manager", 
"org.apache.spark.shuffle.sort.ColumnarShuffleManager")
+      .set("spark.memory.offHeap.enabled", "true")
+      .set("spark.memory.offHeap.size", "4g")
+  }
+
+  override def beforeAll(): Unit = {
+    super.beforeAll()
+    createTPCHNotNullTables()
+  }
+
+  test("cuDF supports row_constructor_with_null for average states") {
+    // Three nullable AVG states mirror the row-construction shape in TPC-H q1
+    // without introducing the separate decimal or legacy-cast paths.
+    val query =
+      """
+        |SELECT
+        |  l_returnflag,
+        |  avg(CASE WHEN l_returnflag = 'R' THEN CAST(1 AS DOUBLE) END) AS 
avg_r,
+        |  avg(CASE WHEN l_returnflag = 'A' THEN CAST(2 AS DOUBLE) END) AS 
avg_a,
+        |  avg(CASE WHEN l_returnflag = 'N' THEN CAST(3 AS DOUBLE) END) AS 
avg_n
+        |FROM lineitem
+        |GROUP BY l_returnflag
+        |""".stripMargin
+
+    runQueryAndCompare(query) {
+      df =>
+        val plan = df.queryExecution.executedPlan
+        val aggregates = collect(plan) { case aggregate: 
HashAggregateExecTransformer => aggregate }
+        assert(aggregates.nonEmpty, s"expected an offloaded hash aggregate, 
got:\n$plan")
+
+        val readers =
+          getExecutedPlan(df).collect { case reader: 
ColumnarAQEShuffleReadExec => reader }
+        assert(
+          readers.exists(_.executionMode == MockGPUStageMode),
+          s"expected a cuDF-tagged aggregate stage, got:\n$plan")
+    }
+  }
+}
diff --git a/cpp/velox/CMakeLists.txt b/cpp/velox/CMakeLists.txt
index fd5ff0a9d4..5f416eadcc 100644
--- a/cpp/velox/CMakeLists.txt
+++ b/cpp/velox/CMakeLists.txt
@@ -229,6 +229,7 @@ if(ENABLE_GPU)
     APPEND
     VELOX_SRCS
     cudf/CudfPlanValidator.cc
+    cudf/CudfFunctions.cc
     cudf/GpuLock.cc
     operators/plannodes/CudfVectorStream.cc
     shuffle/VeloxGpuAsyncShuffleReader.cc
diff --git a/cpp/velox/compute/VeloxBackend.cc 
b/cpp/velox/compute/VeloxBackend.cc
index 7633216249..e9db41783c 100644
--- a/cpp/velox/compute/VeloxBackend.cc
+++ b/cpp/velox/compute/VeloxBackend.cc
@@ -35,6 +35,7 @@
 #include "utils/qat/QatCodec.h"
 #endif
 #ifdef GLUTEN_ENABLE_GPU
+#include "cudf/CudfFunctions.h"
 #include "cudf/GpuLock.h"
 #include "operators/plannodes/CudfVectorStream.h"
 #include "velox/experimental/cudf/CudfConfig.h"
@@ -227,6 +228,7 @@ void VeloxBackend::init(
       
velox::exec::Operator::registerOperator(std::make_unique<CudfVectorStreamOperatorTranslator>());
       velox::cudf_velox::registerSparkFunctions("");
       velox::cudf_velox::registerSparkAggregateFunctions("");
+      registerCudfFunctions();
     } else {
       LOG(WARNING) << "No Cuda device found. Skip Cudf initialization.";
     }
diff --git a/cpp/velox/cudf/CudfFunctions.cc b/cpp/velox/cudf/CudfFunctions.cc
new file mode 100644
index 0000000000..b0e8d838c4
--- /dev/null
+++ b/cpp/velox/cudf/CudfFunctions.cc
@@ -0,0 +1,101 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "cudf/CudfFunctions.h"
+
+#include "operators/functions/RowConstructorWithNull.h"
+#include "velox/common/base/Exceptions.h"
+#include "velox/experimental/cudf/expression/ExpressionEvaluator.h"
+
+#include <cudf/column/column_factories.hpp>
+#include <cudf/null_mask.hpp>
+#include <cudf/table/table_view.hpp>
+
+#include <algorithm>
+#include <memory>
+#include <string>
+#include <utility>
+#include <variant>
+#include <vector>
+
+namespace gluten {
+namespace {
+
+using facebook::velox::core::TypedExprPtr;
+using facebook::velox::cudf_velox::ColumnOrView;
+using facebook::velox::cudf_velox::CudfFunction;
+
+class RowConstructorWithNullFunction final : public CudfFunction {
+ public:
+  explicit RowConstructorWithNullFunction(std::shared_ptr<CudfFunction> 
rowConstructor)
+      : rowConstructor_(std::move(rowConstructor)) {
+    VELOX_CHECK_NOT_NULL(rowConstructor_);
+  }
+
+  ColumnOrView eval(std::vector<ColumnOrView>& inputColumns, cuda::stream_ref 
stream, rmm::device_async_resource_ref mr)
+      const override {
+    auto result = rowConstructor_->eval(inputColumns, stream, mr);
+    auto* ownedResult = std::get_if<std::unique_ptr<cudf::column>>(&result);
+    VELOX_CHECK_NOT_NULL(ownedResult, "row_constructor must return an owning 
cuDF column");
+
+    auto structColumn = std::move(*ownedResult);
+    VELOX_CHECK_NOT_NULL(structColumn);
+    VELOX_CHECK(structColumn->type().id() == cudf::type_id::STRUCT, 
"row_constructor must return a cuDF STRUCT column");
+
+    const auto numRows = structColumn->size();
+    auto contents = structColumn->release();
+    std::vector<cudf::column_view> childViews;
+    childViews.reserve(contents.children.size());
+    for (const auto& child : contents.children) {
+      childViews.push_back(child->view());
+    }
+
+    auto [nullMask, nullCount] = 
cudf::bitmask_and(cudf::table_view{childViews}, stream, mr);
+    // Rebuild through the struct factory instead of setting the parent mask in
+    // place. The factory superimposes parent nulls on every child, which is
+    // required when aggregate finalization reads a child column directly.
+    return cudf::make_structs_column(numRows, std::move(contents.children), 
nullCount, std::move(nullMask), stream, mr);
+  }
+
+ private:
+  std::shared_ptr<CudfFunction> rowConstructor_;
+};
+
+bool canEvaluateRowConstructorWithNull(const TypedExprPtr& expr) {
+  return expr->type()->isRow() && !expr->inputs().empty() &&
+      std::any_of(
+             expr->inputs().begin(), expr->inputs().end(), [](const auto& 
input) { return !input->isConstantKind(); });
+}
+
+} // namespace
+
+void registerCudfFunctions() {
+  facebook::velox::cudf_velox::registerCudfFunction(
+      RowConstructorWithNullCallToSpecialForm::kRowConstructorWithNull,
+      [](const std::string&,
+         const TypedExprPtr& expr,
+         facebook::velox::memory::MemoryPool* pool) -> 
std::shared_ptr<CudfFunction> {
+        auto rowConstructor = 
facebook::velox::cudf_velox::createCudfFunction("row_constructor", expr, pool);
+        VELOX_CHECK_NOT_NULL(rowConstructor, "cuDF row_constructor must be 
registered first");
+        return 
std::make_shared<RowConstructorWithNullFunction>(std::move(rowConstructor));
+      },
+      {},
+      false,
+      canEvaluateRowConstructorWithNull);
+}
+
+} // namespace gluten
diff --git a/cpp/velox/cudf/CudfFunctions.h b/cpp/velox/cudf/CudfFunctions.h
new file mode 100644
index 0000000000..dda05ae9fd
--- /dev/null
+++ b/cpp/velox/cudf/CudfFunctions.h
@@ -0,0 +1,25 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+namespace gluten {
+
+/// Registers Gluten-specific cuDF expression implementations.
+void registerCudfFunctions();
+
+} // namespace gluten
diff --git a/cpp/velox/tests/CMakeLists.txt b/cpp/velox/tests/CMakeLists.txt
index a5d7329830..82af2bacbc 100644
--- a/cpp/velox/tests/CMakeLists.txt
+++ b/cpp/velox/tests/CMakeLists.txt
@@ -106,6 +106,7 @@ if(ENABLE_GPU)
   add_velox_gpu_test(velox_gpu_shuffle_writer_test SOURCES
                      VeloxGpuShuffleWriterTest.cc)
   add_velox_gpu_test(velox_gpu_lock_test SOURCES GpuLockTest.cc)
+  add_velox_gpu_test(velox_gpu_cudf_functions_test SOURCES 
CudfFunctionsTest.cc)
   add_velox_gpu_test(velox_cudf_value_stream_test SOURCES
                      CudfValueStreamTest.cc)
 endif()
diff --git a/cpp/velox/tests/CudfFunctionsTest.cc 
b/cpp/velox/tests/CudfFunctionsTest.cc
new file mode 100644
index 0000000000..df62906c3b
--- /dev/null
+++ b/cpp/velox/tests/CudfFunctionsTest.cc
@@ -0,0 +1,151 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "cudf/CudfFunctions.h"
+#include "operators/functions/RowConstructorWithNull.h"
+#include "velox/experimental/cudf/exec/GpuResources.h"
+#include "velox/experimental/cudf/exec/ToCudf.h"
+#include "velox/experimental/cudf/exec/VeloxCudfInterop.h"
+#include "velox/experimental/cudf/expression/ExpressionEvaluator.h"
+#include "velox/vector/tests/utils/VectorTestBase.h"
+
+#include <gtest/gtest.h>
+
+#include <memory>
+#include <optional>
+#include <string>
+#include <vector>
+
+namespace gluten {
+namespace {
+
+using namespace facebook::velox;
+
+class CudfFunctionsTest : public ::testing::Test, public test::VectorTestBase {
+ protected:
+  static void SetUpTestCase() {
+    
memory::MemoryManager::testingSetInstance(memory::MemoryManager::Options{});
+    cudf_velox::registerCudf();
+    registerCudfFunctions();
+  }
+
+  static void TearDownTestCase() {
+    cudf_velox::unregisterFunctions();
+    cudf_velox::unregisterCudf();
+  }
+
+  RowVectorPtr evaluateOnGpu(const core::TypedExprPtr& expr, const 
RowVectorPtr& input) {
+    auto stream = cudf_velox::cudfGlobalStreamPool().get_stream();
+    auto mr = cudf_velox::get_output_mr();
+    auto cudfTable = cudf_velox::with_arrow::toCudfTable(input, pool_.get(), 
stream, mr);
+    core::QueryConfig queryConfig{{}};
+    auto evaluator = cudf_velox::createCudfExpression(expr, input->rowType(), 
pool_.get(), queryConfig);
+
+    auto ownedColumns = cudfTable->release();
+    std::vector<cudf::column_view> inputViews;
+    inputViews.reserve(ownedColumns.size());
+    for (const auto& column : ownedColumns) {
+      inputViews.push_back(column->view());
+    }
+
+    auto output = evaluator->eval(inputViews, stream, mr);
+    cudf::table_view outputTable{{cudf_velox::asView(output)}};
+    auto outputType = ROW({"result"}, {expr->type()});
+    auto result = cudf_velox::with_arrow::toVeloxColumn(
+        outputTable, pool_.get(), outputType, "", stream, 
cudf::get_current_device_resource_ref());
+    stream.sync();
+    result->setType(outputType);
+    return result;
+  }
+
+  core::TypedExprPtr makeRowConstructorWithNull(const RowTypePtr& resultType) {
+    return std::make_shared<core::CallTypedExpr>(
+        resultType,
+        std::vector<core::TypedExprPtr>{
+            std::make_shared<core::FieldAccessTypedExpr>(INTEGER(), "c0"),
+            std::make_shared<core::FieldAccessTypedExpr>(VARCHAR(), "c1")},
+        RowConstructorWithNullCallToSpecialForm::kRowConstructorWithNull);
+  }
+};
+
+TEST_F(CudfFunctionsTest, rowConstructorWithNull) {
+  auto integers = makeNullableFlatVector<int32_t>({1, std::nullopt, 3, 
std::nullopt});
+  auto strings = makeNullableFlatVector<std::string>({"a", "b", std::nullopt, 
std::nullopt});
+  auto input = makeRowVector({"c0", "c1"}, {integers, strings});
+  auto resultType = ROW({"left", "right"}, {INTEGER(), VARCHAR()});
+  auto rowConstructor = makeRowConstructorWithNull(resultType);
+
+  ASSERT_TRUE(cudf_velox::canExprRunOnGpu(rowConstructor, nullptr, 
pool_.get()));
+  auto output = evaluateOnGpu(rowConstructor, input);
+  auto result = output->childAt(0)->as<RowVector>();
+  ASSERT_NE(result, nullptr);
+  EXPECT_FALSE(result->isNullAt(0));
+  EXPECT_TRUE(result->isNullAt(1));
+  EXPECT_TRUE(result->isNullAt(2));
+  EXPECT_TRUE(result->isNullAt(3));
+
+  // cuDF structs require parent nulls to be superimposed on their children.
+  EXPECT_TRUE(result->childAt(0)->isNullAt(2));
+  EXPECT_TRUE(result->childAt(1)->isNullAt(1));
+
+  auto firstField = std::make_shared<core::DereferenceTypedExpr>(INTEGER(), 
rowConstructor, 0);
+  auto dereferenceOutput = evaluateOnGpu(firstField, input);
+  auto actual = dereferenceOutput->childAt(0);
+  auto expected = makeNullableFlatVector<int32_t>({1, std::nullopt, 
std::nullopt, std::nullopt});
+  test::assertEqualVectors(expected, actual);
+}
+
+TEST_F(CudfFunctionsTest, rowConstructorWithNullLiteral) {
+  auto integers = makeNullableFlatVector<int32_t>({1, std::nullopt, 3});
+  auto input = makeRowVector({"c0"}, {integers});
+  auto resultType = ROW({"left", "right"}, {INTEGER(), VARCHAR()});
+  auto rowConstructor = std::make_shared<core::CallTypedExpr>(
+      resultType,
+      std::vector<core::TypedExprPtr>{
+          std::make_shared<core::FieldAccessTypedExpr>(INTEGER(), "c0"),
+          std::make_shared<core::ConstantTypedExpr>(VARCHAR(), 
variant::null(TypeKind::VARCHAR))},
+      RowConstructorWithNullCallToSpecialForm::kRowConstructorWithNull);
+
+  ASSERT_TRUE(cudf_velox::canExprRunOnGpu(rowConstructor, nullptr, 
pool_.get()));
+  auto output = evaluateOnGpu(rowConstructor, input);
+  auto result = output->childAt(0)->as<RowVector>();
+  ASSERT_NE(result, nullptr);
+  for (vector_size_t row = 0; row < result->size(); ++row) {
+    EXPECT_TRUE(result->isNullAt(row));
+    EXPECT_TRUE(result->childAt(0)->isNullAt(row));
+    EXPECT_TRUE(result->childAt(1)->isNullAt(row));
+  }
+}
+
+TEST_F(CudfFunctionsTest, rejectsAllLiteralRow) {
+  auto resultType = ROW({"left", "right"}, {INTEGER(), VARCHAR()});
+  auto rowConstructor = std::make_shared<core::CallTypedExpr>(
+      resultType,
+      std::vector<core::TypedExprPtr>{
+          std::make_shared<core::ConstantTypedExpr>(INTEGER(), 
variant(int32_t{1})),
+          std::make_shared<core::ConstantTypedExpr>(VARCHAR(), variant("x"))},
+      RowConstructorWithNullCallToSpecialForm::kRowConstructorWithNull);
+
+  EXPECT_FALSE(cudf_velox::canExprRunOnGpu(rowConstructor, nullptr, 
pool_.get()));
+  EXPECT_EQ(
+      cudf_velox::createCudfFunction(
+          RowConstructorWithNullCallToSpecialForm::kRowConstructorWithNull, 
rowConstructor, pool_.get()),
+      nullptr);
+}
+
+} // namespace
+} // namespace gluten


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

Reply via email to