Skip to content

[VL] Fix empty GPU broadcast joins and CudfVector host read crashes in the cuDF backend#12471

Open
ReemaAlzaid wants to merge 5 commits into
apache:mainfrom
ReemaAlzaid:cudf-meterialize-brodcast
Open

[VL] Fix empty GPU broadcast joins and CudfVector host read crashes in the cuDF backend#12471
ReemaAlzaid wants to merge 5 commits into
apache:mainfrom
ReemaAlzaid:cudf-meterialize-brodcast

Conversation

@ReemaAlzaid

@ReemaAlzaid ReemaAlzaid commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

What changes are proposed in this pull request?

Broadcast hash joins on the cuDF (GPU) backend were silently returning empty
results, and GPU batches were crashing whenever CPU code tried to read them.
This PR fixes both:

1. Broadcast joins built from an empty stream (VeloxBroadcastBuildSideRDD.scala)
For broadcast joins, Gluten puts the build-side data into a prebuilt CPU hash
table and feeds the plan an empty iterator. The CPU join uses that prebuilt
table but the GPU join (CudfHashJoin) doesn't know it exists, so it built
its hash table from the empty iterator and every broadcast join returned 0 rows.
Fix: when cuDF is enabled, stream the broadcast batches into the plan, same as
shuffle joins already do.

2. GPU vectors crashing on host reads (new CudfVectorUtils.h + call sites)
A CudfVector on the GPU and has no host side children, so host code
reading or serializing it saw garbage (the childAt crashes). Added a small
helper that copies the vector to host first, and applied it at every
host read site including the ColumnarToRow converter, which was causing a
JVM segfault in q15.

Needs a Velox build with the multi-column hash_with_seed fix
facebookincubator/velox#18047

How was this patch tested?

sbin/gluten-it.sh queries-compare \
  --local --preset=velox --benchmark-type=h --error-on-memleak \
  --off-heap-size=10g -s=1 --threads=1 --iterations=1 \
  --decimal-as-double=true \
  --extra-conf=spark.gluten.sql.columnar.backend.velox.cudf.allowCpuFallback=false \
  --extra-conf=spark.gluten.sql.columnar.cudf=true

gluten-it queries-compare vs vanilla Spark (2× NVIDIA L40S, Spark 3.5,
pure GPU: allowCpuFallback=false, broadcast joins enabled):

  • TPC-H: 3/22 → 18/22 passing
  • TPC-DS: 2/103 → 43/103 passing, 828 crashes → 0
  • TPC-H SF10 with GPU scans: ~5.5× faster than vanilla Spark overall
    (e.g. q21: 82s → 11s)

Remaining failures are pre-existing cuDF expression gaps
(row_constructor_with_null, substring), not related to this change.

cc: @marin-ma @zhouyuan

ReemaAlzaid and others added 4 commits July 6, 2026 15:15
Add materializeVeloxRowVector() (cpp/velox/utils/CudfVectorUtils.h) and call
it at every GPU->CPU boundary that reads RowVector children on the host:
CudfVectorStream/RowVectorStream::next, VeloxColumnarBatch
ensureFlattened/compose/select/toUnsafeRow, VeloxBatchResizer, JniHashTable
nativeHashTableBuild, VeloxJniWrapper prune, VeloxColumnarBatchSerializer
append/framedSerializeWithStats, HashTableBuilder::addInput. Eliminates the
broadcast/shuffle childAt crash on GPU-resident CudfVector (0 host children).
Also wires cudf.allowCpuFallback through VeloxBackend/GlutenConfig.
VeloxColumnarToRowConverter::convert read the raw (device-resident) CudfVector
via getRowVector(); switch to getFlattenedRowVector() so it materializes to host
first. This is the one host-read site the materialize set missed. It feeds both
the broadcast build-side relation (UnsafeColumnarBuildSideRelation columnar->row)
and result return, so the gap caused empty broadcast joins and VARCHAR-offset
SIGSEGVs (e.g. q15).
For offloaded broadcast hash joins the build-side RDD fed Iterator.empty into
the native plan and stashed the data in a prebuilt CPU hash table
(VeloxBroadcastBuildSideCache -> OpaqueHashTable on the HashJoinNode).
CudfHashJoin has no knowledge of OpaqueHashTable and builds from the
build-side value stream, so every GPU broadcast join built from an empty
stream and silently returned zero rows.

When spark.gluten.sql.columnar.cudf=true, stream the deserialized broadcast
batches instead (the same path shuffle joins use on GPU) and skip the CPU
cache build. Hybrid stays correct: with no cached table the HashJoinNode
carries no reusable table, so a CPU-fallback join builds from the stream.
@github-actions github-actions Bot added the VELOX label Jul 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant