You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Related to #2206. The reported eager-buffering failure is reproduced with synthetic notebook-shaped data; the reporter's original notebook run has not been independently reproduced.
What changes are proposed in this pull request?
Generate TabularSHAP sample rows incrementally instead of materializing a sample array for each instance/background pair. This avoids the large buffers created when Spark fuses the broadcast join and array expansion.
Contributor's change: use Dataset.mapPartitions with a row encoder. Preserve the sampler, sample budget, weights, coalition vectors, column order, target-class handling and public API.
Maintainer follow-up: preserve nulls recursively inside background arrays, maps and structs; add a real Spark sampling-path regression and public nested-null regressions; explain remaining scoring cost in the notebook. The submitted row encoder otherwise rejected valid nested background nulls.
Preserve the contributor's commits. Merge the current target rather than rewriting the fork branch.
The lazy iterator test remains as a separate consumption check. The added Spark-path test executes createSamples with two broadcast background rows and checks encoding before the first result, without heap-size or timing assertions.
How is this patch tested?
I have written tests (not required for typo or doc fix) and confirmed the proposed feature/bug-fix/change works.
Maintainer validation: Windows amd64, JDK 11, Spark 3.5.0, Scala 2.12.17.
Passed; generated Python wrapper import/public-setter smoke passed with PySpark 3.5.0
black --check --workers 2 --extend-exclude 'docs/' . (22.3.0)
Passed, 209 files unchanged; explicit asyncio loop initialization used for the host Python
Edited notebook JSON parsing
Passed
Fail-before/pass-after: the nested-null public-transform test fails on the submitted head at the row encoder and passes after the follow-up. Replaying the original eager createSamples implementation makes the Spark-path regression fail: it encodes 20,000 feature values before returning one sample. The lazy implementation passes. Scalar-null/non-null controls and genuine incompatible-type rejection were also checked.
Memory and runtime: a local public-Spark harness used 13 mixed string/integer features, 5 explained rows, 100 background rows, 5,000 samples and 8 local workers. With a 1 GiB heap, the pre-PR sampling implementation terminates with OutOfMemoryError: Java heap space; the lazy path completes all 5 rows in 21.7 seconds, with finite explanations and the expected baseline/sum. This is a replay of the old sampling method with unchanged shared code, not a full old-release installation.
With a 4 GiB heap, the same workload's two warm runs were 10.44/10.16 seconds before and 11.38/10.33 seconds after (about 5% slower on their median); cold runs were 17.72/20.91 seconds. A smaller 20-background-row workload showed about 17% warm overhead. This is a measured memory/runtime trade-off, not a universal speedup. Maintainer changes add schema work once per plan, not per sample.
The contributor separately reported Docker/Linux fail-before/pass-after runs on the original implementation and released 1.0.4/1.1.3, plus deterministic SHAP parity checks:
CI for 9e66546:Azure build 240826424, triggered with /azp run, completed against merge 856d25b (parents: current master d51118a and this head). Published results contain 3,746 passed, 22 skipped, zero failed. Both added regressions ran and passed in TabularSHAP's 8/8 test run; VectorSHAP passed 5/5, and Databricks CPU/GPU notebook tests passed 43/43 and 4/4.
Azure reports partiallySucceeded because three non-gating dependency-cache tasks encountered SSL errors in UnitTests misc; that job's 502 tests and test publishing passed. All 22 skipped test cases also skipped in the successful prior master build 240603209. The website coverage publisher reported no coverage file; website tests passed. Fabric E2E and Internal compatibility were intentionally excluded by existing fork-PR conditions.
GitHub compile/style checks, website build/tests, dependency review and both configured CodeQL analyses passed. Website deployment is intentionally skipped for PRs. The aggregate CodeQL check is neutral because a separate master API-upload configuration is absent on this PR. Current-head automated review has zero open or suppressed findings; its earlier CI-pending note is now superseded by these results.
Spark Connect and port-runtime validation were not performed locally. VectorSHAP's existing sampling implementation is outside this focused change. Contributor sign-off and required human review remain outstanding.
Does this PR change any dependencies?
No. You can skip this section.
Yes. Make sure the dependencies are resolved correctly, and list changes here.
Does this PR add a new feature? If so, have you added samples on website?
No. You can skip this section.
Yes. Make sure you have added samples following below steps.
Find the corresponding markdown file for your new feature in website/docs/documentation folder.
Make sure you choose the correct class estimators/transformers and namespace.
Follow the pattern in markdown file and add another section for your new API, including pyspark, scala (and .NET potentially) samples.
Make sure the DocTable points to correct API link.
Navigate to website folder, and run npm start to make sure the website renders correctly.
Don't forget to add <!--pytest-codeblocks:cont--> before each python code blocks to enable auto-tests for python samples.
Make sure the WebsiteSamplesTests job pass in the pipeline.
Hey Lenin Mookiah (@leninworld) 👋!
Thank you so much for contributing to our repository 🙌.
Someone from SynapseML Team will be reviewing this pull request soon.
We use semantic commit messages to streamline the release process.
Before your pull request can be merged, you should make sure your first commit and PR title start with a semantic prefix.
This helps us to create release messages and credit you for your hard work!
Examples of commit messages with semantic prefixes:
fix: Fix LightGBM crashes with empty partitions
feat: Make HTTP on Spark back-offs configurable
docs: Update Spark Serving usage
build: Add codecov support
perf: improve LightGBM memory usage
refactor: make python code generation rely on classes
style: Remove nulls from CNTKModel
test: Add test coverage for CNTKModel
To test your commit locally, please follow our guild on building from source.
Check out the developer guide for additional guidance on testing your change.
This regression passes vacuously if the nullable-background path returns no explanations, because foreach performs no assertions on an empty result. Assert the expected row count before validating the values so the test proves all three input rows survive the new expansion path.
## Summary
Merge master d51118a to validate the contributor branch against the current target.
## Prompting Intent
Improve microsoft#2768 with focused, verified follow-ups while preserving the contributor's implementation and history.
## Linked Sources
- Contribution: microsoft#2768
- Target: microsoft@d51118a
## Rationale
Use a merge rather than rewriting the contributor's four commits. This imports only already-landed workflow pin updates; no new workflow or dependency change is proposed.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 2765695e-c815-4211-ad89-e0be56141b31
## Summary
Keep the contributor's lazy Dataset sampling and recursively relax nested sample nullability. Add public nested-null regressions and an execution-path test that observes bounded encoding before the first sample. Explain sampling cost and null handling in the tabular explainer notebook.
## Prompting Intent
Improve microsoft#2768 with meaningful, focused changes without bloating the contribution. Verify correctness and measure performance trade-offs rather than assuming a plausible memory fix is sufficient.
## Linked Sources
- Contribution: microsoft#2768
- Reported problem: microsoft#2206
- Production-path regression finding: microsoft#2768 (comment)
- Local review of 1ac2df5: public array/map/struct background-null encoding regression
## Rationale
Only sample schema construction changes in production; sampler behavior, public signatures, sample budgets, weights and column order stay intact. The execution-path test fails with 20000 encoded values on the original eager UDF without requiring an OOM or timing threshold. The nested regression fails on the submitted PR and passes with recursive nullability. Preserve the helper test as a separate iterator-consumption check.
Core compilation, main/test Scalastyle, all 13 targeted SHAP tests, core code generation, pinned Black and generated Python imports pass. A 1 GiB notebook-shaped synthetic workload OOMs with the old sampling implementation and completes in 21.7 seconds with lazy sampling. With sufficient heap, representative warm time is about 5 percent slower, an explicit trade-off for bounded expansion memory rather than a claim of universal speedup.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 2765695e-c815-4211-ad89-e0be56141b31
Azure Pipelines will not run the associated pipelines, because the pull request was updated after the run command was issued. Review the pull request again and issue a new run command.
Thanks Lenin Mookiah (@leninworld) for fixing the eager sample buffering! Your lazy sampling path avoids an out-of-memory failure in a synthetic workload shaped like #2206. I have kept the issue linkage conditional because I have not independently reproduced the reporter's original notebook.
I preserved your commits and lazy sampling design, merged the current target without rewriting your branch, and added 9e66546:
Make the sample encoder's nested struct/array/map types nullable so valid background nulls remain supported.
Add a real Spark sampling-path regression and a public-transform nested-null regression.
Add a short notebook note about streaming's memory benefit and remaining model-scoring cost.
Both regressions have fail-before/pass-after evidence. The old sampling method replay ran out of heap at 1 GiB; the lazy path completed. At 4 GiB, two warm runs showed about 5% median overhead at the larger workload and 17% at the smaller one. The description records this as a measured memory/runtime trade-off, not a universal speedup.
Azure build 240826424, triggered with /azp run, has finished for this head's verified merge with master: 3,746 passed, 22 pre-existing skips, zero failed. Its partiallySucceeded status is from non-gating cache SSL warnings; the affected job's tests passed. Published TabularSHAP results confirm both new regressions passed:
TabularSHAP createSamples streams encoded rows before expanding the sample budget
TabularKernelSHAP preserves nulls inside background features
All 13 TabularSHAP/VectorSHAP tests passed. Required GitHub checks pass, and the current-head automated review has no open or suppressed findings. Its CI-pending note is now superseded by the completed build.
Please review these follow-ups and sign off if they fit your intent. If they do not fit your vision for the PR, feel free to revert my commit, or let me know and I will revert it. Contributor sign-off and required human review are still outstanding; I have not merged the PR.
Thanks Lenin Mookiah (Lenin Mookiah (@leninworld)) for fixing the eager sample buffering! Your lazy sampling path avoids an out-of-memory failure in a synthetic workload shaped like #2206. I have kept the issue linkage conditional because I have not independently reproduced the reporter's original notebook.
I preserved your commits and lazy sampling design, merged the current target without rewriting your branch, and added 9e66546:
Make the sample encoder's nested struct/array/map types nullable so valid background nulls remain supported.
Add a real Spark sampling-path regression and a public-transform nested-null regression.
Add a short notebook note about streaming's memory benefit and remaining model-scoring cost.
Both regressions have fail-before/pass-after evidence. The old sampling method replay ran out of heap at 1 GiB; the lazy path completed. At 4 GiB, two warm runs showed about 5% median overhead at the larger workload and 17% at the smaller one. The description records this as a measured memory/runtime trade-off, not a universal speedup.
Azure build 240826424, triggered with /azp run, has finished for this head's verified merge with master: 3,746 passed, 22 pre-existing skips, zero failed. Its partiallySucceeded status is from non-gating cache SSL warnings; the affected job's tests passed. Published TabularSHAP results confirm both new regressions passed:
TabularSHAP createSamples streams encoded rows before expanding the sample budget
TabularKernelSHAP preserves nulls inside background features
All 13 TabularSHAP/VectorSHAP tests passed. Required GitHub checks pass, and the current-head automated review has no open or suppressed findings. Its CI-pending note is now superseded by the completed build.
Please review these follow-ups and sign off if they fit your intent. If they do not fit your vision for the PR, feel free to revert my commit, or let me know and I will revert it. Contributor sign-off and required human review are still outstanding; I have not merged the PR.
Thanks again, Rana Singh (@ranadeepsingh), for the follow-up. I independently rechecked 9e66546 in Linux amd64 Docker with Java 11.0.32, Spark 3.5.0, and Scala 2.12.18, and the additions fit the intent of the PR:
TabularSHAPExplainerSuite passed 8/8 and VectorSHAPExplainerSuite passed 5/5. Core compilation, test compilation, packaging, and both Scalastyle checks also passed.
Without recursive nullability, the nested-null regression fails with The 0th field 'value' of input row cannot be null. With the original array/explode sampling path, it instead produces an incorrect base value of approximately 0 rather than 10.
Replaying the original eager createSamples implementation makes the new Spark-path regression fail after encoding 20,000 feature values before returning its first sample.
With a 1 GiB driver heap and 16 cores, the notebook-shaped synthetic run—5 explained rows × 100 background rows × 5,000 samples—completed all five rows in 77.9 seconds without an out-of-memory error. Fully enumerated parity cases matched the pre-PR implementation within 3e-21.
The recursive nullability correction, complementary Spark-path coverage, and documentation updates all align with the intended bounded-memory change. Thank you for strengthening the implementation and its regression coverage.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Related Issues/PRs
Related to #2206. The reported eager-buffering failure is reproduced with synthetic notebook-shaped data; the reporter's original notebook run has not been independently reproduced.
What changes are proposed in this pull request?
Generate
TabularSHAPsample rows incrementally instead of materializing a sample array for each instance/background pair. This avoids the large buffers created when Spark fuses the broadcast join and array expansion.Dataset.mapPartitionswith a row encoder. Preserve the sampler, sample budget, weights, coalition vectors, column order, target-class handling and public API.The lazy iterator test remains as a separate consumption check. The added Spark-path test executes
createSampleswith two broadcast background rows and checks encoding before the first result, without heap-size or timing assertions.How is this patch tested?
Maintainer validation: Windows amd64, JDK 11, Spark 3.5.0, Scala 2.12.17.
sbt core/compile core/Test/compilesbt core/scalastyle core/Test/scalastylesbt "core/testOnly com.microsoft.azure.synapse.ml.explainers.split1.TabularSHAPExplainerSuite com.microsoft.azure.synapse.ml.explainers.split1.VectorSHAPExplainerSuite"sbt core/codegenblack --check --workers 2 --extend-exclude 'docs/' .(22.3.0)Fail-before/pass-after: the nested-null public-transform test fails on the submitted head at the row encoder and passes after the follow-up. Replaying the original eager
createSamplesimplementation makes the Spark-path regression fail: it encodes 20,000 feature values before returning one sample. The lazy implementation passes. Scalar-null/non-null controls and genuine incompatible-type rejection were also checked.Memory and runtime: a local public-Spark harness used 13 mixed string/integer features, 5 explained rows, 100 background rows, 5,000 samples and 8 local workers. With a 1 GiB heap, the pre-PR sampling implementation terminates with
OutOfMemoryError: Java heap space; the lazy path completes all 5 rows in 21.7 seconds, with finite explanations and the expected baseline/sum. This is a replay of the old sampling method with unchanged shared code, not a full old-release installation.With a 4 GiB heap, the same workload's two warm runs were 10.44/10.16 seconds before and 11.38/10.33 seconds after (about 5% slower on their median); cold runs were 17.72/20.91 seconds. A smaller 20-background-row workload showed about 17% warm overhead. This is a measured memory/runtime trade-off, not a universal speedup. Maintainer changes add schema work once per plan, not per sample.
The contributor separately reported Docker/Linux fail-before/pass-after runs on the original implementation and released 1.0.4/1.1.3, plus deterministic SHAP parity checks:
CI for
9e66546: Azure build 240826424, triggered with/azp run, completed against merge856d25b(parents: current masterd51118aand this head). Published results contain 3,746 passed, 22 skipped, zero failed. Both added regressions ran and passed in TabularSHAP's 8/8 test run; VectorSHAP passed 5/5, and Databricks CPU/GPU notebook tests passed 43/43 and 4/4.Azure reports
partiallySucceededbecause three non-gating dependency-cache tasks encountered SSL errors inUnitTests misc; that job's 502 tests and test publishing passed. All 22 skipped test cases also skipped in the successful prior master build 240603209. The website coverage publisher reported no coverage file; website tests passed. Fabric E2E and Internal compatibility were intentionally excluded by existing fork-PR conditions.GitHub compile/style checks, website build/tests, dependency review and both configured CodeQL analyses passed. Website deployment is intentionally skipped for PRs. The aggregate CodeQL check is neutral because a separate master API-upload configuration is absent on this PR. Current-head automated review has zero open or suppressed findings; its earlier CI-pending note is now superseded by these results.
Spark Connect and port-runtime validation were not performed locally.
VectorSHAP's existing sampling implementation is outside this focused change. Contributor sign-off and required human review remain outstanding.Does this PR change any dependencies?
Does this PR add a new feature? If so, have you added samples on website?
website/docs/documentationfolder.Make sure you choose the correct class
estimators/transformersand namespace.DocTablepoints to correct API link.npm startto make sure the website renders correctly.<!--pytest-codeblocks:cont-->before each python code blocks to enable auto-tests for python samples.WebsiteSamplesTestsjob pass in the pipeline.