-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Run Polars TPC benchmarks in CI #22848
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
475b7cd
c94a0dc
ebcb626
e04ef1c
c72714b
4dc4c16
b89f51d
494fa40
d9d00b4
79dc8b8
1be401f
63dda74
8a333f8
9a55a91
87c8460
22b4131
32c4b4f
6cdfe35
4b444a5
664153c
aedb32a
00eeef4
c792edf
facd442
6724c90
d013cff
75b3d51
ae470f1
ae10992
63cfddc
1c88b62
21fb8ed
096f223
0bf566f
670a78c
b296f57
73aeb3e
dde3b2f
95da769
6bfdcb7
ded1a0d
03207a1
cd07205
c843e99
69aac80
ea82fef
1467cc5
82825c5
74cdd1c
d2ebd23
fa9a0a4
cddd9d7
7ebca1b
ab57e72
53bf6ad
84c3d23
b910f04
b21116b
ed1a3bd
6a1c5bb
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Reminder that these changes should be removed before the PR is merged. We only want to run these tests nightly |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,50 @@ | ||
| #!/bin/bash | ||
| # SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. | ||
| # SPDX-License-Identifier: Apache-2.0 | ||
|
|
||
| set -euo pipefail | ||
|
|
||
| source rapids-init-pip | ||
|
|
||
| rapids-logger "Download wheels" | ||
|
|
||
| RAPIDS_PY_CUDA_SUFFIX="$(rapids-wheel-ctk-name-gen "${RAPIDS_CUDA_VERSION}")" | ||
| LIBCUDF_WHEELHOUSE=$(rapids-download-from-github "$(rapids-artifact-name wheel_cpp libcudf cudf --cuda "$RAPIDS_CUDA_VERSION")") | ||
| PYLIBCUDF_WHEELHOUSE=$(rapids-download-from-github "$(rapids-artifact-name wheel_python pylibcudf cudf --stable --cuda "$RAPIDS_CUDA_VERSION")") | ||
| CUDF_POLARS_WHEELHOUSE=$(rapids-download-from-github "$(rapids-artifact-name wheel_python cudf-polars cudf --pure --arch any --cuda "$RAPIDS_CUDA_VERSION")") | ||
| LIBCUDF_STREAMING_WHEELHOUSE=$(rapids-download-from-github "$(rapids-artifact-name wheel_cpp libcudf-streaming cudf --cuda "$RAPIDS_CUDA_VERSION")") | ||
| CUDF_STREAMING_WHEELHOUSE=$(rapids-download-from-github "$(rapids-artifact-name wheel_python cudf-streaming cudf --stable --cuda "$RAPIDS_CUDA_VERSION")") | ||
|
|
||
| rapids-generate-pip-constraints py_test_cudf_polars "${PIP_CONSTRAINT}" | ||
|
|
||
| rapids-logger "Installing cudf_polars and TPC test dependencies" | ||
|
|
||
| TPCH_REQUIREMENTS=$(mktemp --suffix=.txt) | ||
| rapids-dependency-file-generator \ | ||
| --config dependencies.yaml \ | ||
| --file-key test_cudf_polars_tpch \ | ||
| --output requirements \ | ||
| --matrix "cuda=${RAPIDS_CUDA_VERSION%.*};arch=$(arch);py=${RAPIDS_PY_VERSION}" \ | ||
| > "${TPCH_REQUIREMENTS}" | ||
|
|
||
| rapids-pip-retry install \ | ||
| -v \ | ||
| --prefer-binary \ | ||
| --constraint "${PIP_CONSTRAINT}" \ | ||
| "$(echo "${CUDF_POLARS_WHEELHOUSE}"/cudf_polars_"${RAPIDS_PY_CUDA_SUFFIX}"*.whl)[test]" \ | ||
| "$(echo "${LIBCUDF_WHEELHOUSE}"/libcudf_"${RAPIDS_PY_CUDA_SUFFIX}"*.whl)" \ | ||
| "$(echo "${PYLIBCUDF_WHEELHOUSE}"/pylibcudf_"${RAPIDS_PY_CUDA_SUFFIX}"*.whl)" \ | ||
| "$(echo "${LIBCUDF_STREAMING_WHEELHOUSE}"/libcudf_streaming_"${RAPIDS_PY_CUDA_SUFFIX}"*.whl)" \ | ||
| "$(echo "${CUDF_STREAMING_WHEELHOUSE}"/cudf_streaming_"${RAPIDS_PY_CUDA_SUFFIX}"*.whl)" \ | ||
| -r "${TPCH_REQUIREMENTS}" | ||
|
|
||
| rapids-logger "Check GPU usage" | ||
| nvidia-smi | ||
|
|
||
| rapids-logger "Running TPC-H and TPC-DS validation tests" | ||
|
|
||
| cd python/cudf_polars | ||
|
|
||
| python -m pytest tests/streaming/test_tpch.py tests/streaming/test_tpcds.py \ | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I decided to make running and validating the benchmark proper pytests so we get the benefits of using pytest. Alternatively, we could run the benchmarks in using Sample output of running the tests |
||
| --iterations 2 \ | ||
| -v | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -475,6 +475,14 @@ files: | |
| - test_python_common | ||
| - test_python_cudf_polars | ||
| - cudf_polars_trace | ||
| py_tpch_cudf_polars: | ||
| output: pyproject | ||
| pyproject_dir: python/cudf_polars | ||
| extras: | ||
| table: project.optional-dependencies | ||
| key: tpch | ||
| includes: | ||
| - test_cudf_polars_tpch | ||
| py_trace_cudf_polars: | ||
| output: pyproject | ||
| matrix: | ||
|
|
@@ -724,6 +732,10 @@ files: | |
| polars_compat_version: ["1.35", "1.36", "1.37", "1.38", "1.39", "1.40", "1.41", "1.42"] | ||
| includes: | ||
| - test_cudf_polars_compat | ||
| test_cudf_polars_tpch: | ||
| output: none | ||
| includes: | ||
| - test_cudf_polars_tpch | ||
| channels: | ||
| - rapidsai-nightly | ||
| - rapidsai | ||
|
|
@@ -1321,6 +1333,15 @@ dependencies: | |
| packages: | ||
| # Used by timeout_with_stack.py utility | ||
| - gdb | ||
| test_cudf_polars_tpch: | ||
| common: | ||
| - output_types: [conda, requirements, pyproject] | ||
| packages: | ||
| - duckdb | ||
| - pyarrow | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Where do we use pyarrow for the tpch benchmarks? |
||
| # added lower bound pinning to get pytest-subtests for free | ||
| - pytest>=9 | ||
| - tpchgen-cli>=3.0.0 | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
| test_python_narwhals: | ||
| common: | ||
| - output_types: [conda, requirements, constraints, pyproject] | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -324,6 +324,10 @@ def sort_for_comparison(df: pl.DataFrame) -> pl.DataFrame: | |
| pl.col(col).lt(val - 2 * abs_tol) | ||
| | pl.col(col).gt(val + 2 * abs_tol) | ||
| ) | ||
| elif val is None: | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fix for None boundary comparison in asserts.py: assert_tpch_result_equal emitted a UserWarning when the sort tie boundary value was None, because it called pl.col(col).lt(None). The fix treats a None boundary as meaning all rows are ties by appending pl.lit(False) instead. |
||
| filter_exprs.append( | ||
| pl.col(col).is_not_null() if nulls_last else pl.lit(value=False) | ||
| ) | ||
| else: | ||
| if desc: | ||
| # then "before" means "greater than" | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -31,18 +31,10 @@ | |
| if e.name is not None and not e.name.startswith("cudf_polars"): | ||
| raise | ||
|
|
||
|
|
||
| if TYPE_CHECKING: | ||
| from types import ModuleType | ||
|
|
||
| # Without this setting, the first IO task to run | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I moved these because setting them at module scope had bad interation with tests because we not import directly from this module in the tests. |
||
| # on each worker takes ~15 sec extra | ||
| os.environ["KVIKIO_COMPAT_MODE"] = os.environ.get("KVIKIO_COMPAT_MODE", "on") | ||
| os.environ["KVIKIO_NTHREADS"] = os.environ.get("KVIKIO_NTHREADS", "8") | ||
| # TODO: consider raising the rapidsmpf built-in default from 1 to 8. | ||
| os.environ["RAPIDSMPF_NUM_STREAMING_THREADS"] = os.environ.get( | ||
| "RAPIDSMPF_NUM_STREAMING_THREADS", "8" | ||
| ) | ||
|
|
||
|
|
||
| def valid_query(name: str) -> bool: | ||
| """Return True for valid query names eg. 'q9', 'q65', etc.""" | ||
|
|
@@ -75,12 +67,18 @@ class PDSDSQueries(metaclass=PDSDSQueriesMeta): | |
|
|
||
| q_impl: str | ||
| name: str = "pdsds" | ||
| num_queries: int = 99 | ||
|
|
||
|
|
||
| class PDSDSPolarsQueries(PDSDSQueries): | ||
| """Polars Queries.""" | ||
|
|
||
| q_impl = "polars_impl" | ||
| # Queries expected to fail on GPU due to known bugs. Keys are query numbers; | ||
| # values are reasons for the failures. These queries will be skipped in GPU runs. | ||
| EXPECTED_FAILURES_TPCDS: ClassVar[dict[int, str]] = { | ||
| 5: "GPU execution failure (packed data cannot be empty): https://github.com/rapidsai/cudf/issues/22073", | ||
| } | ||
| # See comments for EXPECTED_CASTS and EXPECTED_CASTS_DECIMAL | ||
| # in cudf/python/cudf_polars/cudf_polars/streaming/benchmarks/pdsh.py | ||
| # for more details. | ||
|
|
@@ -111,18 +109,9 @@ class PDSDSPolarsQueries(PDSDSQueries): | |
| pl.col("total net profit").cast(pl.Decimal(18, 2)), | ||
| ], | ||
| 19: [pl.col("ext_price").cast(pl.Decimal(18, 2))], | ||
| 20: [ | ||
| pl.col("itemrevenue").cast(pl.Decimal(18, 2)), | ||
| pl.col("revenueratio").cast(pl.Decimal(38, 2)), | ||
| ], | ||
| 20: [pl.col("revenueratio").cast(pl.Decimal(38, 2))], | ||
| 24: [pl.col("paid").cast(pl.Decimal(18, 2))], | ||
| 30: [pl.col("ctr_total_return").cast(pl.Decimal(18, 2))], | ||
| 31: [ | ||
| pl.col("web_q1_q2_increase").cast(pl.Decimal(38, 2)), | ||
| pl.col("store_q1_q2_increase").cast(pl.Decimal(38, 2)), | ||
| pl.col("web_q2_q3_increase").cast(pl.Decimal(38, 2)), | ||
| pl.col("store_q2_q3_increase").cast(pl.Decimal(38, 2)), | ||
| ], | ||
| 32: [pl.col("excess discount amount").cast(pl.Decimal(18, 2))], | ||
| 33: [pl.col("total_sales").cast(pl.Decimal(18, 2))], | ||
| 42: [pl.col("sum(ss_ext_sales_price)").cast(pl.Decimal(18, 2))], | ||
|
|
@@ -239,6 +228,11 @@ class PDSDSPolarsQueries(PDSDSQueries): | |
| pl.col("inv_before").cast(pl.Int32), | ||
| pl.col("inv_after").cast(pl.Int32), | ||
| ], | ||
| 29: [ | ||
| pl.col("store_sales_quantity").cast(pl.Int64), | ||
| pl.col("store_returns_quantity").cast(pl.Int64), | ||
| pl.col("catalog_sales_quantity").cast(pl.Int64), | ||
| ], | ||
| 34: [pl.col("cnt").cast(COUNT_DTYPE)], | ||
| 35: [ | ||
| pl.col("cnt1").cast(COUNT_DTYPE), | ||
|
|
@@ -331,7 +325,16 @@ class PDSDSDuckDBQueries(PDSDSQueries): | |
|
|
||
|
|
||
| if __name__ == "__main__": | ||
| parser = build_parser(num_queries=99) | ||
| # Without this setting, the first IO task to run | ||
| # on each worker takes ~15 sec extra | ||
| os.environ["KVIKIO_COMPAT_MODE"] = os.environ.get("KVIKIO_COMPAT_MODE", "on") | ||
| os.environ["KVIKIO_NTHREADS"] = os.environ.get("KVIKIO_NTHREADS", "8") | ||
| # TODO: consider raising the rapidsmpf built-in default from 1 to 8. | ||
| os.environ["RAPIDSMPF_NUM_STREAMING_THREADS"] = os.environ.get( | ||
| "RAPIDSMPF_NUM_STREAMING_THREADS", "8" | ||
| ) | ||
|
|
||
| parser = build_parser(num_queries=PDSDSQueries.num_queries) | ||
| args = parse_args(parser=parser) | ||
| if args.frontend not in _CPU_ENGINES: | ||
| os.environ["POLARS_MAX_THREADS"] = os.environ.get("POLARS_MAX_THREADS", "1") | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,4 +1,27 @@ | ||
| # SPDX-FileCopyrightText: Copyright (c) 2025, NVIDIA CORPORATION & AFFILIATES. | ||
| # SPDX-FileCopyrightText: Copyright (c) 2025-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. | ||
| # SPDX-License-Identifier: Apache-2.0 | ||
|
|
||
| """DuckDB and Polars queries.""" | ||
|
|
||
| from __future__ import annotations | ||
|
|
||
| import polars as pl | ||
|
|
||
|
|
||
| def sql_sum(expr: str | pl.Expr) -> pl.Expr: | ||
| """ | ||
| Sum that returns NULL for all-null/empty groups, matching SQL SUM semantics. | ||
|
|
||
| Polars sum() returns 0 for all-null or empty groups; SQL returns NULL. | ||
| See https://github.com/rapidsai/cudf/issues/19560. | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I didn't replace every sum expression with |
||
|
|
||
| Parameters | ||
| ---------- | ||
| expr | ||
| Column name or expression to sum. If a string, wraps in ``pl.col``. | ||
| Pass a conditional expression (e.g. ``pl.when(...).then(...).otherwise(None)``) | ||
| to implement SQL ``SUM(CASE WHEN ... END)`` without ``.filter()`` inside | ||
| a groupby, which is not supported on GPU. | ||
| """ | ||
| e = pl.col(expr) if isinstance(expr, str) else expr | ||
| return pl.when(e.count() > 0).then(e.sum()).otherwise(None) | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ignore changes in this file. Will remove them before this PR is merged
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Reminder that these changes should be removed before the PR is merged. We only want to run these tests nightly