Skip to content

Commit 8256bc7

Browse files
committed
Merge branch 'main' into regex-transpiler-bugfixes
Signed-off-by: Igor Peshansky <ipeshansky@nvidia.com>
2 parents f2ecd39 + 54eac26 commit 8256bc7

239 files changed

Lines changed: 3024 additions & 164 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/blossom-ci.yml

Lines changed: 28 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -34,51 +34,52 @@ jobs:
3434

3535
# This job only runs for pull request comments
3636
if: |
37+
github.event.issue.pull_request &&
3738
github.event.comment.body == 'build' &&
3839
(
3940
github.actor == 'abellina' ||
41+
github.actor == 'amahussein' ||
4042
github.actor == 'anfeng' ||
43+
github.actor == 'binmahone' ||
44+
github.actor == 'cindyyuanjiang' ||
4145
github.actor == 'firestarman' ||
4246
github.actor == 'GaryShen2008' ||
47+
github.actor == 'gerashegalov' ||
48+
github.actor == 'HaoYang670' ||
49+
github.actor == 'hyperbolic2346' ||
50+
github.actor == 'igorpeshansky' ||
51+
github.actor == 'jihoonson' ||
52+
github.actor == 'knoguchi22' ||
4353
github.actor == 'kuhushukla' ||
54+
github.actor == 'liurenjie1024' ||
55+
github.actor == 'mattahrens' ||
4456
github.actor == 'mythrocks' ||
4557
github.actor == 'nartal1' ||
58+
github.actor == 'nvliyuan' ||
59+
github.actor == 'NVnavkumar' ||
4660
github.actor == 'NvTimLiu' ||
47-
github.actor == 'razajafri' ||
61+
github.actor == 'parthosa' ||
62+
github.actor == 'patilkishorv' ||
63+
github.actor == 'pmattione-nvidia' ||
64+
github.actor == 'pxLi' ||
65+
github.actor == 'res-life' ||
4866
github.actor == 'revans2' ||
67+
github.actor == 'rishic3' ||
4968
github.actor == 'sameerz' ||
69+
github.actor == 'sdrp713' ||
70+
github.actor == 'sperlingxx' ||
71+
github.actor == 'SurajAralihalli' ||
5072
github.actor == 'tgravescs' ||
73+
github.actor == 'thirtiseven' ||
74+
github.actor == 'ttnghia' ||
75+
github.actor == 'viadea' ||
5176
github.actor == 'wbo4958' ||
77+
github.actor == 'winningsix' ||
5278
github.actor == 'wjxiz1992' ||
53-
github.actor == 'sperlingxx' ||
54-
github.actor == 'hyperbolic2346' ||
55-
github.actor == 'gerashegalov' ||
56-
github.actor == 'ttnghia' ||
57-
github.actor == 'nvliyuan' ||
58-
github.actor == 'res-life' ||
59-
github.actor == 'HaoYang670' ||
60-
github.actor == 'NVnavkumar' ||
61-
github.actor == 'amahussein' ||
62-
github.actor == 'mattahrens' ||
6379
github.actor == 'YanxuanLiu' ||
64-
github.actor == 'cindyyuanjiang' ||
65-
github.actor == 'thirtiseven' ||
66-
github.actor == 'winningsix' ||
67-
github.actor == 'viadea' ||
6880
github.actor == 'yinqingh' ||
69-
github.actor == 'parthosa' ||
70-
github.actor == 'liurenjie1024' ||
71-
github.actor == 'binmahone' ||
7281
github.actor == 'zpuller' ||
73-
github.actor == 'pxLi' ||
74-
github.actor == 'SurajAralihalli' ||
75-
github.actor == 'jihoonson' ||
76-
github.actor == 'knoguchi22' ||
77-
github.actor == 'patilkishorv' ||
78-
github.actor == 'pmattione-nvidia' ||
79-
github.actor == 'rishic3' ||
80-
github.actor == 'sdrp713' ||
81-
github.actor == 'igorpeshansky'
82+
false
8283
)
8384
steps:
8485
- name: Check if comment is issued by authorized person

datagen/src/main/spark400/scala/org/apache/spark/sql/tests/datagen/DataGenExprShims.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
{"spark": "403"}
2323
{"spark": "411"}
2424
{"spark": "412"}
25+
{"spark": "420"}
2526
spark-rapids-shim-json-lines ***/
2627
package org.apache.spark.sql.tests.datagen
2728

docs/dev/adaptive-query.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,8 @@ On startup, the `SQLExecPlugin` plugin registers two distinct sets of
5050
optimizer rules:
5151

5252
```scala
53-
extensions.injectColumnar(_ => ColumnarOverrideRules())
54-
extensions.injectQueryStagePrepRule(_ => GpuQueryStagePrepOverrides())
53+
extensions.injectColumnar(session => ColumnarOverrideRules(session))
54+
extensions.injectQueryStagePrepRule(session => GpuQueryStagePrepOverrides(session))
5555
```
5656

5757
The `ColumnarOverrideRules` are used whether AQE is enabled or not, and the

integration_tests/src/main/python/ast_test.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@
1717
from asserts import assert_cpu_and_gpu_are_equal_collect_with_capture, assert_gpu_and_cpu_are_equal_collect
1818
from data_gen import *
1919
from marks import approximate_float, datagen_overrides, ignore_order, disable_ansi_mode
20-
from spark_session import with_cpu_session, is_spark_403, is_spark_412_or_later
21-
20+
from spark_session import with_cpu_session, is_spark_403, is_spark_412_or_later, \
21+
is_spark_420_or_later
2222
import pyspark.sql.functions as f
2323

2424
# Each descriptor contains a list of data generators and a corresponding boolean
@@ -213,9 +213,10 @@ def test_acos(data_descr):
213213
def test_atan(data_descr):
214214
assert_unary_ast(data_descr, lambda df: df.selectExpr('atan(a)'))
215215

216-
# AST is not expressive enough to support the ASINH Spark emulation expression
216+
# AST is not expressive enough to support the ASINH Spark emulation expression.
217+
# Spark 4.2 uses the improved ASINH path by default, which is AST-compatible.
217218
@approximate_float
218-
@pytest.mark.parametrize('data_descr', [(double_gen, False)], ids=idfn)
219+
@pytest.mark.parametrize('data_descr', [(double_gen, is_spark_420_or_later())], ids=idfn)
219220
def test_asinh(data_descr):
220221
assert_unary_ast(data_descr, lambda df: df.selectExpr('asinh(a)'))
221222

integration_tests/src/main/python/date_time_test.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -760,7 +760,7 @@ def test_formats_for_legacy_mode_other_formats_tz_rules():
760760
'spark.rapids.sql.incompatibleDateFormats.enabled': True})
761761

762762
@disable_ansi_mode
763-
@allow_non_gpu('ProjectExec')
763+
@allow_non_gpu('ProjectExec', 'GetTimestamp')
764764
def test_to_timestamp_legacy_millisecond_format_fallback():
765765
conf = {
766766
'spark.sql.legacy.timeParserPolicy': 'LEGACY',

integration_tests/src/main/python/explain_test.py

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Copyright (c) 2021-2025, NVIDIA CORPORATION.
1+
# Copyright (c) 2021-2026, NVIDIA CORPORATION.
22
#
33
# Licensed under the Apache License, Version 2.0 (the "License");
44
# you may not use this file except in compliance with the License.
@@ -19,7 +19,7 @@
1919
from marks import *
2020
from pyspark.sql.functions import *
2121
from pyspark.sql.types import *
22-
from spark_session import with_cpu_session, with_gpu_session
22+
from spark_session import is_spark_420_or_later, with_cpu_session, with_gpu_session
2323

2424
# mark this test as ci_1 for mvn verify sanity check in pre-merge CI
2525
pytestmark = pytest.mark.premerge_ci_1
@@ -67,11 +67,20 @@ def do_explain(spark):
6767
df = spark.createDataFrame([(1, "John Doe", 21)], ("id", "name", "age"))
6868
df2 = df.select(slen("name").alias("slen(name)"), to_upper("name"), add_one("age"))
6969
explain_str = spark.sparkContext._jvm.com.nvidia.spark.rapids.ExplainPlan.explainPotentialGpuPlan(df2._jdf, "ALL")
70-
# udf shouldn't be on GPU
71-
udf_str_not = 'cannot run on GPU because GPU does not currently support the operator class org.apache.spark.sql.execution.python.BatchEvalPythonExec'
72-
assert udf_str_not in explain_str
70+
# udf shouldn't be fully on GPU. Spark 4.2 enables Arrow-optimized Python
71+
# UDFs here, so regular udf(...) is planned as ArrowEvalPythonExec instead
72+
# of the older BatchEvalPythonExec.
73+
udf_exec = 'ArrowEvalPythonExec' if is_spark_420_or_later() else 'BatchEvalPythonExec'
74+
udf_str_not = \
75+
'cannot run on GPU because GPU does not currently support the operator class ' + \
76+
f'org.apache.spark.sql.execution.python.{udf_exec}'
77+
if is_spark_420_or_later():
78+
assert f'Exec <{udf_exec}> will partially run on GPU' in explain_str
79+
else:
80+
assert udf_str_not in explain_str
7381
not_on_gpu_str = spark.sparkContext._jvm.com.nvidia.spark.rapids.ExplainPlan.explainPotentialGpuPlan(df2._jdf, "NOT")
74-
assert udf_str_not in not_on_gpu_str
82+
if not is_spark_420_or_later():
83+
assert udf_str_not in not_on_gpu_str
7584
assert "will run on GPU" not in not_on_gpu_str
7685

7786
with_cpu_session(do_explain)

integration_tests/src/main/python/fastparquet_compatibility_test.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -245,7 +245,9 @@ def test_reading_file_written_with_gpu(spark_tmp_path, column_gen):
245245
try:
246246
# For now, this compares the results of reading back the GPU-written data, via fastparquet and GPU.
247247
assert_gpu_and_cpu_are_equal_collect(read_parquet(data_path=data_path, local_data_path=local_data_path),
248-
conf=conf)
248+
conf=conf,
249+
result_canonicalize_func_before_compare=
250+
get_fastparquet_result_canonicalizer())
249251
finally:
250252
# Clean up local copy of data.
251253
delete_local_directory(local_base_path)

integration_tests/src/main/python/fastparquet_utils.py

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Copyright (c) 2023, NVIDIA CORPORATION.
1+
# Copyright (c) 2023-2026, NVIDIA CORPORATION.
22
#
33
# Licensed under the Apache License, Version 2.0 (the "License");
44
# you may not use this file except in compliance with the License.
@@ -12,9 +12,11 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414

15+
import math
1516
import pytest
1617

1718
from pyspark.sql import Row
19+
from spark_session import is_spark_420_or_later
1820

1921
def get_fastparquet_result_canonicalizer():
2022
# Get a converter like fastparquet that flattens the GPU results.
@@ -35,9 +37,35 @@ def _convert_fastparquet_result(fastparquet_cpu, gpu):
3537
:param gpu: the gpu result, it's a Spark Row list
3638
:return: (fastparquet, converted_gpu) tuple
3739
"""
38-
new_gpu = [_convert_gpu_row(gpu_row) for gpu_row in gpu]
40+
converted_gpu = [_convert_gpu_row(gpu_row) for gpu_row in gpu]
41+
if not is_spark_420_or_later():
42+
return (fastparquet_cpu, converted_gpu)
43+
new_gpu = [
44+
_normalize_gpu_nans_like_fastparquet(
45+
fastparquet_cpu[idx] if idx < len(fastparquet_cpu) else None,
46+
gpu_row)
47+
for idx, gpu_row in enumerate(converted_gpu)]
3948
return (fastparquet_cpu, new_gpu)
4049

50+
def _normalize_gpu_nans_like_fastparquet(cpu_value, gpu_value):
51+
# Spark 4.2 converts pandas NaN values from fastparquet into Spark nulls.
52+
# Normalize only those matching GPU NaN values so other null mismatches still fail.
53+
if cpu_value is None:
54+
return None if isinstance(gpu_value, float) and math.isnan(gpu_value) else gpu_value
55+
if isinstance(cpu_value, Row) and isinstance(gpu_value, Row):
56+
cpu_dict = cpu_value.asDict()
57+
gpu_dict = gpu_value.asDict()
58+
return Row(**{
59+
key: _normalize_gpu_nans_like_fastparquet(cpu_dict.get(key), gpu_dict.get(key))
60+
for key in gpu_value.__fields__})
61+
if isinstance(cpu_value, list) and isinstance(gpu_value, list):
62+
return [
63+
_normalize_gpu_nans_like_fastparquet(
64+
cpu_value[idx] if idx < len(cpu_value) else None,
65+
gpu_item)
66+
for idx, gpu_item in enumerate(gpu_value)]
67+
return gpu_value
68+
4169
def _convert_gpu_row(gpu_row):
4270
converted_dict = _get_converted_dict(gpu_row)
4371
return Row(**converted_dict)

integration_tests/src/main/python/hash_aggregate_test.py

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@
2626
from pyspark.sql.types import *
2727
from marks import *
2828
import pyspark.sql.functions as f
29-
from spark_session import is_databricks104_or_later, with_cpu_session, is_spark_340_or_later
29+
from spark_session import is_databricks104_or_later, with_cpu_session, is_spark_340_or_later, \
30+
is_spark_420_or_later
3031

3132
pytestmark = pytest.mark.nightly_resource_consuming_test
3233

@@ -862,6 +863,33 @@ def doit(spark):
862863
doit,
863864
conf={'spark.sql.execution.useObjectHashAggregateExec': str(use_obj_hash_agg).lower()})
864865

866+
867+
@pytest.mark.skipif(not is_spark_420_or_later(),
868+
reason='collect_list/array_agg RESPECT NULLS is introduced in Spark 4.2')
869+
@allow_non_gpu("ProjectExec")
870+
@ignore_order(local=True)
871+
@pytest.mark.parametrize('use_obj_hash_agg', [True, False], ids=idfn)
872+
def test_hash_groupby_collect_list_respect_nulls(use_obj_hash_agg):
873+
def doit(spark):
874+
return spark.sql("""
875+
SELECT a,
876+
sort_array(collect_list(b) RESPECT NULLS) AS respect_list,
877+
sort_array(array_agg(b) RESPECT NULLS) AS respect_array
878+
FROM VALUES
879+
(1, 1),
880+
(1, NULL),
881+
(1, 3),
882+
(2, NULL),
883+
(2, 5)
884+
AS tab(a, b)
885+
GROUP BY a
886+
""")
887+
888+
assert_gpu_and_cpu_are_equal_collect(
889+
doit,
890+
conf={'spark.sql.execution.useObjectHashAggregateExec': str(use_obj_hash_agg).lower()})
891+
892+
865893
@ignore_order(local=True)
866894
@pytest.mark.parametrize('use_obj_hash_agg', [True, False], ids=idfn)
867895
def test_hash_groupby_collect_list_of_maps(use_obj_hash_agg):

integration_tests/src/main/python/hybrid_parquet_test.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,13 @@
2929
3. BinaryType is NOT supported
3030
4. MapType wrapped by NestedType (Struct of Map/Array of Map/Map of Map) is NOT fully supported
3131
"""
32+
33+
python_udf_non_gpu_execs = ['BatchEvalPythonExec']
34+
if is_spark_420_or_later():
35+
# Spark 4.2 can plan regular Python udf(...) as ArrowEvalPythonExec when
36+
# Arrow-optimized Python UDFs are enabled.
37+
python_udf_non_gpu_execs.append('ArrowEvalPythonExec')
38+
3239
parquet_gens_list = [
3340
[byte_gen, short_gen, int_gen, long_gen, float_gen, double_gen,
3441
string_gen, boolean_gen, date_gen,
@@ -266,7 +273,7 @@ def test_hybrid_parquet_filter_pushdown_cpu(spark_tmp_path):
266273
lambda spark: spark.read.parquet(data_path).filter(f.col("a").startswith('1') & (f.ascii(f.col("a")) >= 50) & (f.col("a") < '1000')),
267274
conf=filter_split_conf)
268275

269-
@allow_non_gpu('FilterExec', 'BatchEvalPythonExec', 'PythonUDF')
276+
@allow_non_gpu('FilterExec', *python_udf_non_gpu_execs, 'PythonUDF')
270277
@pytest.mark.skipif(is_databricks_runtime(), reason="Hybrid feature does not support Databricks currently")
271278
@pytest.mark.skipif(not is_hybrid_backend_loaded(), reason="HybridScan specialized tests")
272279
@hybrid_test

0 commit comments

Comments
 (0)