-
Notifications
You must be signed in to change notification settings - Fork 242
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
HybridParquetScan: Refine filter push down to avoid double evaluation #12000
base: branch-25.02
Are you sure you want to change the base?
Conversation
Signed-off-by: Haoyang Li <[email protected]>
Signed-off-by: Haoyang Li <[email protected]>
Signed-off-by: Haoyang Li <[email protected]>
Signed-off-by: Haoyang Li <[email protected]>
Signed-off-by: Haoyang Li <[email protected]>
Signed-off-by: Haoyang Li <[email protected]>
sql-plugin/src/main/scala/com/nvidia/spark/rapids/GpuOverrides.scala
Outdated
Show resolved
Hide resolved
Signed-off-by: Haoyang Li <[email protected]>
Signed-off-by: Haoyang Li <[email protected]>
Signed-off-by: Haoyang Li <[email protected]>
sql-plugin/src/main/scala/org/apache/spark/rapids/hybrid/HybridExecutionUtils.scala
Outdated
Show resolved
Hide resolved
…dExecutionUtils.scala Co-authored-by: Alfred Xu <[email protected]>
} | ||
|
||
val supportedByHybridFilters = { | ||
// Only fully supported functions are listed here |
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.
Have a link to the supporting list in the comments.
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.
done
} | ||
} | ||
|
||
def recursivelySupportsHybridFilters(condition: Expression): Boolean = { |
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.
nit: isExprSupportedByHybridScan
better name?
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.
done
} | ||
} | ||
|
||
def recursivelySupportsHybridFilters(condition: Expression): Boolean = { |
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.
There is a way to register UDF in Gluten as well (link). Hmm, probably we can have a whitelist configuration allowing pre-registered function into the pushed-down filters.
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.
Added a whitelist config as the gluten doc seems to be out of date for some expressions. Maybe this will allow UDF to be pushed down to the CPU, but I haven't tested it. Will file a follow up for this.
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.
LGTM. But I have not worked on the GpuOverrides
for a long time......
build |
sql-plugin/src/main/scala/com/nvidia/spark/rapids/RapidsConf.scala
Outdated
Show resolved
Hide resolved
…cala Co-authored-by: Alfred Xu <[email protected]>
Signed-off-by: Haoyang Li <[email protected]>
with_cpu_session( | ||
lambda spark: gen_df(spark, [('a', StringGen(pattern='[0-9]{1,5}'))]).write.parquet(data_path), | ||
conf=rebase_write_corrected_conf) | ||
# filter conditions should remain on the GPU |
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.
How to verify the executed plan?
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.
I assumed that the startsWith is not supported in CPU but actually it does but not in their doc. If we pushed a unsupported operator to CPU the test should failed. I will try to find an expr that hybrid is not supported in this test.
Do you think it is necessary to write some UT to verify the executed plan?
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.
It will be straightforward if checking execution plan using UT.
IMO, we may use UT instead of IT. @sperlingxx what do you think?
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.
Changed this one to a pandas_udf for now. I think some IT are still necessary, will try to write some UT later.
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.
Updated the integration tests to verify the executed plan, PTAL
lambda spark: gen_df(spark, [('a', StringGen(pattern='[0-9]{1,5}'))]).write.parquet(data_path), | ||
conf=rebase_write_corrected_conf) | ||
# filter conditions should be pushed down to the CPU, so the ascii will not fall back to CPU in the FilterExec | ||
assert_gpu_and_cpu_are_equal_collect( |
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.
How to verify the executed plan?
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.
ascii
is not supported, so if it doesn't push down the test will fail.
|
||
with_cpu_session(lambda spark: spark.udf.register("udf_fallback", udf_fallback)) | ||
|
||
assert_gpu_and_cpu_are_equal_collect( |
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.
How to verify the executed plan?
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.
It is an UDF and not supported in CPU so if it pushed down the test should failed.
* support it. After that we can remove the condition from one side to avoid duplicate execution | ||
* or unnecessary fallback/crash. | ||
*/ | ||
def applyHybridScanRules(plan: SparkPlan, conf: RapidsConf): SparkPlan = { |
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.
Rename to tryToApplyHybridScanRules
?
And at the first line of this function, check if Hybrid feature is enabled to avoid executing the following code.
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.
done
|
||
def canBePushedToHybrid(child: SparkPlan, conf: RapidsConf): String = { | ||
child match { | ||
case fsse: FileSourceScanExec if HybridFileSourceScanExecMeta.useHybridScan(conf, fsse) => |
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.
Better to move HybridFileSourceScanExecMeta.useHybridScan
to outer function.
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.
How about moving the function useHybridScan to HybridExecutionUtils?
And maybe all other functions in object HybridFileSourceScanExecMeta can be moved there too, what do you think?
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.
How about moving the function useHybridScan to HybridExecutionUtils?
Yes, good idea.
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.
done
Signed-off-by: Haoyang Li <[email protected]>
Did a NDS test, total time improved from 709s to 704s. |
Signed-off-by: Haoyang Li <[email protected]>
Signed-off-by: Haoyang Li <[email protected]>
build |
1 similar comment
build |
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.
LGTM! Good job!
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.
LGTM
Signed-off-by: Haoyang Li <[email protected]>
build |
Addressed some comments from @res-life in offline sync, pls take another look |
build |
Signed-off-by: Haoyang Li <[email protected]>
build |
Closes #11892
In the current code, a HybridParquetScan followed by a Filter will result in all conditions being pushed down to the CPU, but still remaining in the Filter at the same time, so the Filter conditions are evaluated twice. Usually the second evaluation is quite fast, so this won't be a big problem. But if there are some conditions that are not supported by CPU or GPU, it will cause some problems.
This PR adds a rule to check each condition in filterExec before overriding:
The
supportedByHybridFilters
is from velox-backend-support-progress in Gluten. Here is the script to extract CPU supported exprs, gist.For example:
startswith
is not supported in CPU so it will be kept in GPU, andascii
pushed down to CPU. The check is recursive.and
if all filters are supported, the FilterExec will be removed.