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 9e52cbda69 [VL] Fix host to GPU conversion in CudfVectorStream (#13067)
9e52cbda69 is described below
commit 9e52cbda69a83fec2a768385ede2bffaece30e72
Author: Reema <[email protected]>
AuthorDate: Fri Oct 23 11:37:15 2026 +0300
[VL] Fix host to GPU conversion in CudfVectorStream (#13067)
---
cpp/velox/operators/plannodes/CudfVectorStream.h | 39 +++-
cpp/velox/operators/plannodes/RowVectorStream.cc | 7 +-
cpp/velox/tests/CMakeLists.txt | 2 +
cpp/velox/tests/CudfValueStreamTest.cc | 206 +++++++++++++++++++++
cpp/velox/tests/ValueStreamDynamicFilterTest.cc | 32 ++++
.../GlutenBroadcastValueStreamTestSupport.scala | 29 +++
.../GlutenBroadcastValueStreamTestSupport.scala | 27 +++
.../GlutenBroadcastValueStreamTestSupport.scala | 29 +++
.../gluten/utils/velox/VeloxTestSettings.scala | 1 +
.../org/apache/spark/sql/GlutenSQLQuerySuite.scala | 52 ++++++
.../GlutenBroadcastValueStreamSuite.scala | 101 ++++++++++
11 files changed, 519 insertions(+), 6 deletions(-)
diff --git a/cpp/velox/operators/plannodes/CudfVectorStream.h
b/cpp/velox/operators/plannodes/CudfVectorStream.h
index 492b8556be..cdde9d8d48 100644
--- a/cpp/velox/operators/plannodes/CudfVectorStream.h
+++ b/cpp/velox/operators/plannodes/CudfVectorStream.h
@@ -24,6 +24,7 @@
#include "velox/exec/Operator.h"
#include "velox/exec/Task.h"
#include "velox/experimental/cudf/exec/CudfOperator.h"
+#include "velox/experimental/cudf/exec/GpuResources.h"
#include "velox/experimental/cudf/exec/Utilities.h"
#include "velox/experimental/cudf/exec/VeloxCudfInterop.h"
#include "velox/experimental/cudf/vector/CudfVector.h"
@@ -115,13 +116,41 @@ class CudfVectorStream : public CudfVectorStreamBase {
VELOX_DCHECK(vp != nullptr);
auto cudfVector =
std::dynamic_pointer_cast<facebook::velox::cudf_velox::CudfVector>(vp);
if (cudfVector == nullptr) {
- // The vector may comes from BroadcastExchange, in this case, it's not a
CudfVector.
- vp->setType(outputType_);
- return vp;
+ // BroadcastExchange may return a host RowVector; upload it for GPU
operators.
+ auto stream =
facebook::velox::cudf_velox::cudfGlobalStreamPool().get_stream();
+ if (outputType_->size() == 0) {
+ // Preserve row count because zero-column cuDF tables cannot store it.
+ return std::make_shared<facebook::velox::cudf_velox::CudfVector>(
+ vp->pool(), outputType_, vp->size(),
std::make_unique<cudf::table>(), stream);
+ }
+ // Broadcast preprojection can append join-key columns beyond the
stream's output schema.
+ VELOX_CHECK_GE(
+ vp->childrenSize(),
+ outputType_->size(),
+ "Value stream batch has fewer columns than the declared output
type");
+ std::vector<facebook::velox::VectorPtr> children(
+ vp->children().begin(), vp->children().begin() +
outputType_->size());
+ auto host = std::make_shared<facebook::velox::RowVector>(
+ vp->pool(), outputType_, facebook::velox::BufferPtr(0), vp->size(),
std::move(children));
+ auto table = facebook::velox::cudf_velox::with_arrow::toCudfTable(
+ host, host->pool(), stream,
facebook::velox::cudf_velox::get_output_mr());
+ return std::make_shared<facebook::velox::cudf_velox::CudfVector>(
+ vp->pool(), outputType_, vp->size(), std::move(table), stream);
+ }
+ // GPU broadcast deserialization can retain the same extra join-key
columns.
+ VELOX_CHECK_GE(
+ cudfVector->getTableView().num_columns(),
+ outputType_->size(),
+ "Value stream batch has fewer columns than the declared output type");
+ auto stream = cudfVector->stream();
+ auto table = cudfVector->release();
+ if (table->num_columns() > outputType_->size()) {
+ auto columns = table->release();
+ columns.resize(outputType_->size());
+ table = std::make_unique<cudf::table>(std::move(columns));
}
- VELOX_CHECK_NOT_NULL(cudfVector);
return std::make_shared<facebook::velox::cudf_velox::CudfVector>(
- vp->pool(), outputType_, vp->size(), cudfVector->release(),
cudfVector->stream());
+ vp->pool(), outputType_, vp->size(), std::move(table), stream);
}
};
diff --git a/cpp/velox/operators/plannodes/RowVectorStream.cc
b/cpp/velox/operators/plannodes/RowVectorStream.cc
index 4119eff037..38872620cc 100644
--- a/cpp/velox/operators/plannodes/RowVectorStream.cc
+++ b/cpp/velox/operators/plannodes/RowVectorStream.cc
@@ -102,8 +102,13 @@ facebook::velox::RowVectorPtr RowVectorStream::next() {
const std::shared_ptr<VeloxColumnarBatch>& vb =
VeloxColumnarBatch::from(pool_, cb);
auto vp = vb->getRowVector();
VELOX_DCHECK(vp != nullptr);
+ // Broadcast preprojection can append join-key columns beyond the stream's
output schema.
+ VELOX_CHECK_GE(
+ vp->childrenSize(), outputType_->size(), "Value stream batch has fewer
columns than the declared output type");
+ std::vector<facebook::velox::VectorPtr> children(
+ vp->children().begin(), vp->children().begin() + outputType_->size());
return std::make_shared<facebook::velox::RowVector>(
- vp->pool(), outputType_, facebook::velox::BufferPtr(0), vp->size(),
vp->children());
+ vp->pool(), outputType_, facebook::velox::BufferPtr(0), vp->size(),
std::move(children));
}
ValueStreamDataSource::ValueStreamDataSource(
diff --git a/cpp/velox/tests/CMakeLists.txt b/cpp/velox/tests/CMakeLists.txt
index df00a8db15..a5d7329830 100644
--- a/cpp/velox/tests/CMakeLists.txt
+++ b/cpp/velox/tests/CMakeLists.txt
@@ -106,6 +106,8 @@ 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_cudf_value_stream_test SOURCES
+ CudfValueStreamTest.cc)
endif()
set(VELOX_TEST_COMMON_SRCS JsonToProtoConverter.cc FilePathGenerator.cc)
diff --git a/cpp/velox/tests/CudfValueStreamTest.cc
b/cpp/velox/tests/CudfValueStreamTest.cc
new file mode 100644
index 0000000000..83a2de76fe
--- /dev/null
+++ b/cpp/velox/tests/CudfValueStreamTest.cc
@@ -0,0 +1,206 @@
+/*
+ * 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 <gtest/gtest.h>
+
+#include "operators/plannodes/CudfVectorStream.h"
+#include "velox/common/base/tests/GTestUtils.h"
+#include "velox/experimental/cudf/CudfConfig.h"
+#include "velox/experimental/cudf/exec/ToCudf.h"
+#include "velox/vector/tests/utils/VectorTestBase.h"
+
+using namespace facebook::velox;
+using namespace facebook::velox::exec;
+
+namespace facebook::velox::test {
+namespace {
+
+class TestBatchIterator final : public gluten::ColumnarBatchIterator {
+ public:
+ explicit TestBatchIterator(std::vector<RowVectorPtr> batches) :
batches_(std::move(batches)) {}
+
+ std::shared_ptr<gluten::ColumnarBatch> next() override {
+ if (index_ == batches_.size()) {
+ return nullptr;
+ }
+ return std::make_shared<gluten::VeloxColumnarBatch>(batches_[index_++]);
+ }
+
+ private:
+ std::vector<RowVectorPtr> batches_;
+ size_t index_ = 0;
+};
+
+class CudfValueStreamTest : public ::testing::Test, public VectorTestBase {
+ protected:
+ static void SetUpTestSuite() {
+
memory::MemoryManager::testingSetInstance(memory::MemoryManager::Options{});
+ cudf_velox::registerCudf();
+
Operator::registerOperator(std::make_unique<gluten::CudfVectorStreamOperatorTranslator>());
+ }
+
+ static void TearDownTestSuite() {
+ cudf_velox::unregisterCudf();
+ Operator::unregisterAllOperators();
+ }
+
+ std::shared_ptr<Task> makeTask(std::vector<RowVectorPtr> batches, const
RowTypePtr& outputType) {
+ auto iterator =
std::make_shared<gluten::ResultIterator>(std::make_unique<TestBatchIterator>(std::move(batches)));
+ auto node = std::make_shared<gluten::CudfValueStreamNode>("cudf-vs",
outputType, std::move(iterator));
+ // Inspect the source's GPU output before the driver adapter adds
CudfToVelox.
+ auto queryCtx =
+ core::QueryCtx::create(nullptr,
core::QueryConfig{{{cudf_velox::CudfConfig::kCudfEnabled, "false"}}});
+ return Task::create("test-cudf-value-stream", core::PlanFragment{node}, 0,
queryCtx, Task::ExecutionMode::kSerial);
+ }
+
+ void assertGpuValues(const RowVectorPtr& expected, const
cudf_velox::CudfVectorPtr& actual) {
+ auto host = cudf_velox::with_arrow::toVeloxColumn(
+ actual->getTableView(), pool(), asRowType(expected->type()), "",
actual->stream(), cudf_velox::get_output_mr());
+ assertEqualVectors(expected, host);
+ }
+};
+
+TEST_F(CudfValueStreamTest, convertsHostBatchToCudfVector) {
+ auto batch = makeRowVector({"id"}, {makeNullableFlatVector<int64_t>({10,
std::nullopt, 30})});
+ auto task = makeTask({batch}, asRowType(batch->type()));
+
+ ContinueFuture future = ContinueFuture::makeEmpty();
+ auto output = task->next(&future);
+ ASSERT_NE(output, nullptr);
+ auto cudfVector = std::dynamic_pointer_cast<cudf_velox::CudfVector>(output);
+ ASSERT_NE(cudfVector, nullptr);
+ EXPECT_EQ(cudfVector->type(), batch->type());
+ EXPECT_EQ(cudfVector->size(), 3);
+ EXPECT_EQ(cudfVector->getTableView().num_rows(), 3);
+ EXPECT_EQ(cudfVector->getTableView().num_columns(), 1);
+ assertGpuValues(batch, cudfVector);
+ EXPECT_EQ(task->next(&future), nullptr);
+}
+
+TEST_F(CudfValueStreamTest, trimsExtraHostColumns) {
+ auto batch =
+ makeRowVector({"id", "col_0"}, {makeFlatVector<int64_t>({10, 20, 30}),
makeFlatVector<int64_t>({101, 202, 303})});
+ auto outputType = ROW({"id"}, {BIGINT()});
+ auto expected = makeRowVector({"id"}, {batch->childAt(0)});
+ auto task = makeTask({batch}, outputType);
+
+ ContinueFuture future = ContinueFuture::makeEmpty();
+ auto output = task->next(&future);
+ ASSERT_NE(output, nullptr);
+ auto cudfVector = std::dynamic_pointer_cast<cudf_velox::CudfVector>(output);
+ ASSERT_NE(cudfVector, nullptr);
+ EXPECT_EQ(cudfVector->type(), outputType);
+ EXPECT_EQ(cudfVector->size(), 3);
+ EXPECT_EQ(cudfVector->getTableView().num_columns(), 1);
+ assertGpuValues(expected, cudfVector);
+ EXPECT_EQ(task->next(&future), nullptr);
+}
+
+TEST_F(CudfValueStreamTest, preservesZeroColumnRowCount) {
+ auto outputType = ROW({}, {});
+ auto batch = makeRowVector(outputType, 3);
+ auto task = makeTask({batch}, outputType);
+
+ ContinueFuture future = ContinueFuture::makeEmpty();
+ auto output = task->next(&future);
+ ASSERT_NE(output, nullptr);
+ auto cudfVector = std::dynamic_pointer_cast<cudf_velox::CudfVector>(output);
+ ASSERT_NE(cudfVector, nullptr);
+ EXPECT_EQ(cudfVector->type(), outputType);
+ EXPECT_EQ(cudfVector->size(), 3);
+ EXPECT_EQ(cudfVector->getTableView().num_columns(), 0);
+ EXPECT_EQ(task->next(&future), nullptr);
+}
+
+TEST_F(CudfValueStreamTest, rejectsHostBatchWithMissingColumns) {
+ for (int32_t numColumns : {0, 1}) {
+ SCOPED_TRACE(numColumns);
+ auto batch = numColumns == 0 ? makeRowVector(ROW({}, {}), 3)
+ : makeRowVector({"id"},
{makeFlatVector<int64_t>({10, 20, 30})});
+ auto task = makeTask({batch}, ROW({"id", "value"}, {BIGINT(), BIGINT()}));
+ ContinueFuture future = ContinueFuture::makeEmpty();
+ VELOX_ASSERT_THROW(task->next(&future), "Value stream batch has fewer
columns than the declared output type");
+ }
+}
+
+TEST_F(CudfValueStreamTest, loadsLazyHostColumns) {
+ auto lazy = makeLazyFlatVector<int64_t>(3, [](vector_size_t row) { return
(row + 1) * 10; });
+ auto batch = makeRowVector({"id"}, {lazy});
+ auto expected = makeRowVector({"id"}, {makeFlatVector<int64_t>({10, 20,
30})});
+ auto task = makeTask({batch}, asRowType(batch->type()));
+ EXPECT_FALSE(lazy->isLoaded());
+
+ ContinueFuture future = ContinueFuture::makeEmpty();
+ auto output = task->next(&future);
+ ASSERT_NE(output, nullptr);
+ auto cudfVector = std::dynamic_pointer_cast<cudf_velox::CudfVector>(output);
+ ASSERT_NE(cudfVector, nullptr);
+ EXPECT_TRUE(lazy->isLoaded());
+ assertGpuValues(expected, cudfVector);
+ EXPECT_EQ(task->next(&future), nullptr);
+}
+
+TEST_F(CudfValueStreamTest, preservesGpuBatchAndStream) {
+ auto host = makeRowVector({"id"}, {makeNullableFlatVector<int64_t>({10,
std::nullopt, 30})});
+ auto outputType = asRowType(host->type());
+ auto stream = cudf_velox::cudfGlobalStreamPool().get_stream();
+ auto table = cudf_velox::with_arrow::toCudfTable(host, pool(), stream,
cudf_velox::get_output_mr());
+ const auto* data = table->view().column(0).head<int64_t>();
+ auto batch = std::make_shared<cudf_velox::CudfVector>(pool(), outputType,
host->size(), std::move(table), stream);
+ auto task = makeTask({batch}, outputType);
+
+ ContinueFuture future = ContinueFuture::makeEmpty();
+ auto output = task->next(&future);
+ ASSERT_NE(output, nullptr);
+ auto cudfVector = std::dynamic_pointer_cast<cudf_velox::CudfVector>(output);
+ ASSERT_NE(cudfVector, nullptr);
+ EXPECT_EQ(cudfVector->type(), outputType);
+ EXPECT_EQ(cudfVector->size(), host->size());
+ EXPECT_EQ(cudfVector->stream(), stream);
+ EXPECT_EQ(cudfVector->getTableView().column(0).head<int64_t>(), data);
+ assertGpuValues(host, cudfVector);
+ EXPECT_EQ(task->next(&future), nullptr);
+}
+
+TEST_F(CudfValueStreamTest, trimsExtraGpuColumns) {
+ auto host = makeRowVector(
+ {"id", "col_0"},
+ {makeNullableFlatVector<int64_t>({10, std::nullopt, 30}),
makeFlatVector<int64_t>({11, 21, 31})});
+ auto outputType = ROW({"renamed_id"}, {BIGINT()});
+ auto expected = makeRowVector({"renamed_id"}, {host->childAt(0)});
+ auto stream = cudf_velox::cudfGlobalStreamPool().get_stream();
+ auto table = cudf_velox::with_arrow::toCudfTable(host, pool(), stream,
cudf_velox::get_output_mr());
+ const auto* data = table->view().column(0).head<int64_t>();
+ auto batch = std::make_shared<cudf_velox::CudfVector>(pool(), host->type(),
host->size(), std::move(table), stream);
+ auto task = makeTask({batch}, outputType);
+
+ ContinueFuture future = ContinueFuture::makeEmpty();
+ auto output = task->next(&future);
+ ASSERT_NE(output, nullptr);
+ auto cudfVector = std::dynamic_pointer_cast<cudf_velox::CudfVector>(output);
+ ASSERT_NE(cudfVector, nullptr);
+ EXPECT_EQ(cudfVector->type(), outputType);
+ EXPECT_EQ(cudfVector->size(), host->size());
+ EXPECT_EQ(cudfVector->stream(), stream);
+ ASSERT_EQ(cudfVector->getTableView().num_columns(), 1);
+ EXPECT_EQ(cudfVector->getTableView().column(0).head<int64_t>(), data);
+ assertGpuValues(expected, cudfVector);
+ EXPECT_EQ(task->next(&future), nullptr);
+}
+
+} // namespace
+} // namespace facebook::velox::test
diff --git a/cpp/velox/tests/ValueStreamDynamicFilterTest.cc
b/cpp/velox/tests/ValueStreamDynamicFilterTest.cc
index 5c1f156140..899214e935 100644
--- a/cpp/velox/tests/ValueStreamDynamicFilterTest.cc
+++ b/cpp/velox/tests/ValueStreamDynamicFilterTest.cc
@@ -116,6 +116,38 @@ TEST_F(ValueStreamDynamicFilterTest,
noFilterPassesAllRows) {
ASSERT_EQ(ids, (std::vector<int64_t>{10, 20, 30}));
}
+TEST_F(ValueStreamDynamicFilterTest, trimsExtraTrailingColumns) {
+ auto batch = makeRowVector(
+ {"id", "col_0"},
+ {makeNullableFlatVector<int64_t>({10, std::nullopt, 30}),
makeFlatVector<int64_t>({11, 21, 31})});
+ auto originalType = batch->type();
+ auto outputType = ROW({"renamed_id"}, {BIGINT()});
+ auto expected = makeRowVector({"renamed_id"},
{makeNullableFlatVector<int64_t>({10, std::nullopt, 30})});
+ auto scanNode = makeTableScanNode("vs-extra-columns", outputType);
+
+ auto queryCtx = core::QueryCtx::create();
+ auto task =
+ Task::create("test-extra-columns", core::PlanFragment{scanNode}, 0,
queryCtx, Task::ExecutionMode::kSerial);
+ task->addSplit(scanNode->id(), Split{makeSplit({batch})});
+ task->noMoreSplits(scanNode->id());
+
+ ContinueFuture future = ContinueFuture::makeEmpty();
+ auto output = task->next(&future);
+ ASSERT_NE(output, nullptr);
+ EXPECT_EQ(output->type(), outputType);
+ EXPECT_EQ(output->size(), 3);
+ ASSERT_EQ(output->childrenSize(), 1);
+ assertEqualVectors(expected, output);
+
+ // Broadcast batches can be shared; trimming must not modify the source
batch.
+ EXPECT_EQ(batch->type(), originalType);
+ EXPECT_EQ(batch->size(), 3);
+ ASSERT_EQ(batch->childrenSize(), 2);
+ assertEqualVectors(expected->childAt(0), batch->childAt(0));
+ assertEqualVectors(makeFlatVector<int64_t>({11, 21, 31}), batch->childAt(1));
+ EXPECT_EQ(task->next(&future), nullptr);
+}
+
TEST_F(ValueStreamDynamicFilterTest, emptySplitProducesNoRows) {
auto outputType = ROW({"id"}, {BIGINT()});
auto scanNode = makeTableScanNode("vs-empty-split", outputType);
diff --git
a/gluten-ut/spark35/src/test/backends-bolt/org/apache/gluten/GlutenBroadcastValueStreamTestSupport.scala
b/gluten-ut/spark35/src/test/backends-bolt/org/apache/gluten/GlutenBroadcastValueStreamTestSupport.scala
new file mode 100644
index 0000000000..ad8b557cef
--- /dev/null
+++
b/gluten-ut/spark35/src/test/backends-bolt/org/apache/gluten/GlutenBroadcastValueStreamTestSupport.scala
@@ -0,0 +1,29 @@
+/*
+ * 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
+
+import org.apache.gluten.columnarbatch.ColumnarBatches
+import org.apache.gluten.memory.arrow.alloc.ArrowBufferAllocators
+
+import org.apache.spark.sql.vectorized.ColumnarBatch
+
+trait GlutenBroadcastValueStreamTestSupport {
+
+ protected def loadBroadcastBatch(batch: ColumnarBatch): ColumnarBatch = {
+ ColumnarBatches.load(ArrowBufferAllocators.contextInstance(), batch)
+ }
+}
diff --git
a/gluten-ut/spark35/src/test/backends-clickhouse/org/apache/gluten/GlutenBroadcastValueStreamTestSupport.scala
b/gluten-ut/spark35/src/test/backends-clickhouse/org/apache/gluten/GlutenBroadcastValueStreamTestSupport.scala
new file mode 100644
index 0000000000..90980977dc
--- /dev/null
+++
b/gluten-ut/spark35/src/test/backends-clickhouse/org/apache/gluten/GlutenBroadcastValueStreamTestSupport.scala
@@ -0,0 +1,27 @@
+/*
+ * 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
+
+import org.apache.spark.sql.vectorized.ColumnarBatch
+
+trait GlutenBroadcastValueStreamTestSupport {
+
+ protected def loadBroadcastBatch(batch: ColumnarBatch): ColumnarBatch = {
+ // ClickHouse column vectors already expose Spark data types.
+ batch
+ }
+}
diff --git
a/gluten-ut/spark35/src/test/backends-velox/org/apache/gluten/GlutenBroadcastValueStreamTestSupport.scala
b/gluten-ut/spark35/src/test/backends-velox/org/apache/gluten/GlutenBroadcastValueStreamTestSupport.scala
new file mode 100644
index 0000000000..ad8b557cef
--- /dev/null
+++
b/gluten-ut/spark35/src/test/backends-velox/org/apache/gluten/GlutenBroadcastValueStreamTestSupport.scala
@@ -0,0 +1,29 @@
+/*
+ * 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
+
+import org.apache.gluten.columnarbatch.ColumnarBatches
+import org.apache.gluten.memory.arrow.alloc.ArrowBufferAllocators
+
+import org.apache.spark.sql.vectorized.ColumnarBatch
+
+trait GlutenBroadcastValueStreamTestSupport {
+
+ protected def loadBroadcastBatch(batch: ColumnarBatch): ColumnarBatch = {
+ ColumnarBatches.load(ArrowBufferAllocators.contextInstance(), batch)
+ }
+}
diff --git
a/gluten-ut/spark35/src/test/scala/org/apache/gluten/utils/velox/VeloxTestSettings.scala
b/gluten-ut/spark35/src/test/scala/org/apache/gluten/utils/velox/VeloxTestSettings.scala
index a16b97c357..08c584714a 100644
---
a/gluten-ut/spark35/src/test/scala/org/apache/gluten/utils/velox/VeloxTestSettings.scala
+++
b/gluten-ut/spark35/src/test/scala/org/apache/gluten/utils/velox/VeloxTestSettings.scala
@@ -507,6 +507,7 @@ class VeloxTestSettings extends BackendTestSettings {
enableSuite[GlutenOuterJoinSuiteForceShjOff]
enableSuite[FallbackStrategiesSuite]
enableSuite[GlutenBroadcastExchangeSuite]
+ enableSuite[GlutenBroadcastValueStreamSuite]
enableSuite[GlutenLocalBroadcastExchangeSuite]
enableSuite[GlutenCoalesceShufflePartitionsSuite]
// Rewrite for Gluten. Change details are in the inline comments in
individual tests.
diff --git
a/gluten-ut/spark35/src/test/scala/org/apache/spark/sql/GlutenSQLQuerySuite.scala
b/gluten-ut/spark35/src/test/scala/org/apache/spark/sql/GlutenSQLQuerySuite.scala
index 8f397c517e..e3269994d8 100644
---
a/gluten-ut/spark35/src/test/scala/org/apache/spark/sql/GlutenSQLQuerySuite.scala
+++
b/gluten-ut/spark35/src/test/scala/org/apache/spark/sql/GlutenSQLQuerySuite.scala
@@ -16,6 +16,10 @@
*/
package org.apache.spark.sql
+import org.apache.gluten.config.GlutenConfig
+import org.apache.gluten.execution.{BroadcastHashJoinExecTransformerBase,
TakeOrderedAndProjectExecTransformer}
+import org.apache.gluten.utils.BackendTestUtils
+
import org.apache.spark.SparkException
import org.apache.spark.sql.execution.columnar.InMemoryTableScanExec
import org.apache.spark.sql.internal.SQLConf
@@ -23,6 +27,54 @@ import org.apache.spark.sql.internal.SQLConf
class GlutenSQLQuerySuite extends SQLQuerySuite with GlutenSQLTestsTrait {
import testImplicits._
+ testGluten("broadcast expression join followed by aggregate and top N") {
+ withTempPath {
+ dir =>
+ val leftPath = s"${dir.getCanonicalPath}/left"
+ val rightPath = s"${dir.getCanonicalPath}/right"
+ Seq((1, 10), (2, 20), (3, 30), (4, 40), (2, 5))
+ .toDF("id", "value")
+ .repartition(2)
+ .write
+ .parquet(leftPath)
+ Seq(0, 1, 2).toDF("id").write.parquet(rightPath)
+
+ withTempView("top_n_left", "top_n_right") {
+ spark.read.parquet(leftPath).createOrReplaceTempView("top_n_left")
+ spark.read.parquet(rightPath).createOrReplaceTempView("top_n_right")
+
+ val isVelox = BackendTestUtils.isVeloxBackendLoaded()
+ val cudfModes = if (isVelox) Seq(false, true) else Seq(false)
+ for (cudfEnabled <- cudfModes; buildOnce <- Seq(true, false)) {
+ withClue(s"cuDF=$cudfEnabled, buildOnce=$buildOnce: ") {
+ withSQLConf(
+ // Exercise CPU execution with both cuDF broadcast settings on
CPU CI.
+ SQLConf.ADAPTIVE_EXECUTION_ENABLED.key -> "false",
+ SQLConf.SHUFFLE_PARTITIONS.key -> "2",
+ SQLConf.FILES_OPEN_COST_IN_BYTES.key -> "134217728",
+ GlutenConfig.COLUMNAR_CUDF_ENABLED.key -> cudfEnabled.toString,
+ "spark.gluten.velox.buildHashTableOncePerExecutor.enabled" ->
buildOnce.toString,
+ "spark.gluten.velox.offHeapBroadcastBuildRelation.enabled" ->
"false"
+ ) {
+ // Exercise both broadcast expression-key projection and the
exchange before TopN.
+ val df = sql("""
+ |SELECT /*+ BROADCAST(r) */ l.id, SUM(l.value)
AS total
+ |FROM top_n_left l JOIN top_n_right r ON l.id =
r.id + 1
+ |GROUP BY l.id
+ |ORDER BY total DESC, l.id
+ |LIMIT 2
+ |""".stripMargin)
+ val plan = getExecutedPlan(df)
+
assert(plan.exists(_.isInstanceOf[BroadcastHashJoinExecTransformerBase]))
+
assert(plan.exists(_.isInstanceOf[TakeOrderedAndProjectExecTransformer]))
+ checkAnswer(df, Seq(Row(3, 30L), Row(2, 25L)))
+ }
+ }
+ }
+ }
+ }
+ }
+
testGluten("SPARK-28156: self-join should not miss cached view") {
withTable("table1") {
withView("table1_vw") {
diff --git
a/gluten-ut/spark35/src/test/scala/org/apache/spark/sql/execution/GlutenBroadcastValueStreamSuite.scala
b/gluten-ut/spark35/src/test/scala/org/apache/spark/sql/execution/GlutenBroadcastValueStreamSuite.scala
new file mode 100644
index 0000000000..dfc1871c69
--- /dev/null
+++
b/gluten-ut/spark35/src/test/scala/org/apache/spark/sql/execution/GlutenBroadcastValueStreamSuite.scala
@@ -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.
+ */
+package org.apache.spark.sql.execution
+
+import org.apache.gluten.GlutenBroadcastValueStreamTestSupport
+import org.apache.gluten.config.GlutenConfig
+import org.apache.gluten.execution.{BroadcastHashJoinExecTransformerBase,
TakeOrderedAndProjectExecTransformer}
+
+import org.apache.spark.sql.{GlutenSQLTestsTrait, Row}
+import org.apache.spark.sql.execution.joins.BuildSideRelation
+import org.apache.spark.sql.internal.SQLConf
+import org.apache.spark.task.TaskResources
+
+class GlutenBroadcastValueStreamSuite
+ extends GlutenSQLTestsTrait
+ with GlutenBroadcastValueStreamTestSupport {
+ import testImplicits._
+
+ testGluten("broadcast expression join preserves batch schema") {
+ withTempPath {
+ dir =>
+ val leftPath = s"${dir.getCanonicalPath}/left"
+ val rightPath = s"${dir.getCanonicalPath}/right"
+ Seq((1, 10), (2, 20), (3, 30), (4, 40), (2, 5))
+ .toDF("id", "value")
+ .repartition(2)
+ .write
+ .parquet(leftPath)
+ Seq(0, 1, 2).toDF("id").write.parquet(rightPath)
+
+ withTempView("top_n_left", "top_n_right") {
+ spark.read.parquet(leftPath).createOrReplaceTempView("top_n_left")
+ spark.read.parquet(rightPath).createOrReplaceTempView("top_n_right")
+
+ for (cudfEnabled <- Seq(false, true); buildOnce <- Seq(true, false))
{
+ withClue(s"cuDF=$cudfEnabled, buildOnce=$buildOnce: ") {
+ withSQLConf(
+ // Exercise CPU execution with both cuDF broadcast settings on
CPU CI.
+ SQLConf.ADAPTIVE_EXECUTION_ENABLED.key -> "false",
+ SQLConf.SHUFFLE_PARTITIONS.key -> "2",
+ SQLConf.FILES_OPEN_COST_IN_BYTES.key -> "134217728",
+ GlutenConfig.COLUMNAR_CUDF_ENABLED.key -> cudfEnabled.toString,
+ "spark.gluten.velox.buildHashTableOncePerExecutor.enabled" ->
buildOnce.toString,
+ "spark.gluten.velox.offHeapBroadcastBuildRelation.enabled" ->
"false"
+ ) {
+ // Exercise both broadcast expression-key projection and the
exchange before TopN.
+ val df = sql("""
+ |SELECT /*+ BROADCAST(r) */ l.id, SUM(l.value)
AS total
+ |FROM top_n_left l JOIN top_n_right r ON l.id =
r.id + 1
+ |GROUP BY l.id
+ |ORDER BY total DESC, l.id
+ |LIMIT 2
+ |""".stripMargin)
+ val plan = getExecutedPlan(df)
+
assert(plan.exists(_.isInstanceOf[BroadcastHashJoinExecTransformerBase]))
+
assert(plan.exists(_.isInstanceOf[TakeOrderedAndProjectExecTransformer]))
+ checkAnswer(df, Seq(Row(3, 30L), Row(2, 25L)))
+
+ val exchanges = plan.collect { case e:
ColumnarBroadcastExchangeExec => e }
+ assert(exchanges.size == 1)
+ val exchange = exchanges.head
+ val relation =
exchange.doExecuteBroadcast[BuildSideRelation]().value
+ val outputTypes = exchange.output.map(_.dataType)
+ // Broadcast preprojection appends the expression key in
either cuDF mode.
+ val expectedColumns = outputTypes.size + (if (buildOnce) 1
else 0)
+
+ TaskResources.runUnsafe {
+ // The iterator recycles loaded batches and closes its
serializer when drained.
+ val batches = relation.asReadOnlyCopy().deserialized
+ var rows = 0
+ while (batches.hasNext) {
+ val batch = batches.next()
+ assert(batch.numCols() == expectedColumns)
+ val loaded = loadBroadcastBatch(batch)
+ val types = (0 until loaded.numCols()).map(i =>
loaded.column(i).dataType())
+ assert(types.take(outputTypes.size) == outputTypes)
+ rows += loaded.numRows()
+ }
+ assert(rows == 3)
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]