feat: Support lossless string identifiers in SAR - #2594
Conversation
|
Azure Pipelines: There may be pipelines that require an authorized user to comment /azp run to run. |
|
Hey @ranadeepsingh 👋! We use semantic commit messages to streamline the release process. Examples of commit messages with semantic prefixes:
To test your commit locally, please follow our guild on building from source. |
|
/azp run |
|
Azure Pipelines: Successfully started running 1 pipeline(s). |
Refs microsoft#2275 Refs microsoft#2283 ## Summary Add deterministic, reversible user and item identifier mappings to SAR so string and wide numeric IDs are never cast into lossy caller-visible values. Persist mappings with the model, preserve identifier types in scores and recommendations, define null and unknown-ID behavior, restore typed item recommendation APIs, and add Scala and Python regression coverage. ## Prompting Intent Recreate the intent of the stale SAR string-ID change on current master without copying its lossy casts. Keep the SparkML API coherent and backward compatible for numeric users, use TDD, validate serialization and schema behavior, expose Python wrappers, and exercise targeted compile, style, code generation, Scala, and Python/JVM checks before opening a replacement PR. ## Linked Sources - Feature request: microsoft#2275 - Original pull request: microsoft#2283 - Current SAR implementation at the starting revision: https://github.com/microsoft/SynapseML/tree/7d9fabcc/core/src/main/scala/com/microsoft/azure/synapse/ml/recommendation - Repository review policy: .github/skills/code-review/SKILL.md ## Rationale Use model-owned typed mappings instead of composing RecommendationIndexer because that stage stringifies numeric identifiers, exposes index columns, and cannot recover every original type. Contiguous deterministic indices keep the existing matrix implementation viable, while persisted DataFrame parameters make decoding reversible after save/load. Inner mapping joins intentionally drop null or unseen scoring IDs, strict type validation prevents ambiguous conversions, and legacy numeric models fall back to identity mappings. The approach accepts a deterministic global sort and persisted mapping storage in exchange for lossless, reproducible SparkML behavior. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
fd59c19 to
47245d7
Compare
|
/azp run |
|
Azure Pipelines: Successfully started running 1 pipeline(s). |
There was a problem hiding this comment.
Pull request overview
Adds lossless support for string and wide numeric user/item identifiers in the SAR recommender by introducing deterministic, model-owned ID↔index mappings, and ensuring all caller-visible outputs decode back to the original identifier types.
Changes:
- Build and persist deterministic (contiguous) ID mappings during SAR fitting; run all internal computations on indices and decode outputs back to original identifier types.
- Extend SARModel recommendation APIs (including item-subset recommendations) and add corresponding Python wrappers.
- Add Scala/Python test coverage for direct string IDs, wide numeric IDs, mapping determinism, and save/load behavior.
Show a summary per file
| File | Description |
|---|---|
| core/src/main/scala/com/microsoft/azure/synapse/ml/recommendation/SAR.scala | Implements deterministic ID mapping and updates SAR fit/schema validation to support string/numeric identifiers. |
| core/src/main/scala/com/microsoft/azure/synapse/ml/recommendation/SARModel.scala | Adds model-owned mappings, decoding, subset recommendation support, and deterministic ranking. |
| core/src/main/python/synapse/ml/recommendation/SARModel.py | Exposes additional SARModel recommendation methods to Python. |
| core/src/test/scala/com/microsoft/azure/synapse/ml/recommendation/SARSpec.scala | Adjusts existing SAR tests for deterministic/sorted DataFrame equality and updated identifier handling. |
| core/src/test/scala/com/microsoft/azure/synapse/ml/recommendation/SARIdentifierSpec.scala | New Scala test suite validating identifier type preservation, determinism, and save/load. |
| core/src/test/python/synapsemltest/recommendation/test_ranking.py | Adds Python tests for direct string identifier training/scoring and save/load. |
Review details
- Files reviewed: 6/6 changed files
- Comments generated: 4
- Review effort level: Lite
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #2594 +/- ##
==========================================
+ Coverage 84.79% 84.95% +0.15%
==========================================
Files 334 334
Lines 17806 18056 +250
Branches 1623 1609 -14
==========================================
+ Hits 15099 15339 +240
- Misses 2707 2717 +10 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
Refs microsoft#2275 Refs microsoft#2594 ## Summary Resolve the four independent review findings on SAR string identifier support. Preserve typed IDs in ranking train/validation splits, accept only round-trip-safe numeric scoring casts, retain established integer recommendation schemas for safely representable numeric IDs, and rank only factor IDs that have real mappings. Add focused Scala and Python regressions and remove unnecessary mapping cache and interaction-count work identified during review. ## Prompting Intent The engineer asked to fix all medium correctness and compatibility findings on PR microsoft#2594, add a regression for each, rerun targeted Scala, code generation, formatting, and Python/JVM validation, then update the existing PR and request re-review without weakening lossless string or wide numeric behavior. ## Linked Sources - Pull request and review context: microsoft#2594 - Feature request: microsoft#2275 - Original pull request: microsoft#2283 - Repository review policy: .github/skills/code-review/SKILL.md ## Rationale Use Spark structs and array functions instead of Double UDF payloads so split schemas remain typed. Numeric scoring IDs are temporarily cast only when casting back reproduces the input, preventing overflow and fractional aliasing while retaining unknown-ID drop semantics. Recommendation decoding conditionally uses the historical integer schema only when every ID round-trips through Int; strings and wide or fractional numeric IDs remain lossless. Candidate indices are intersected with both factors and mappings before top-K so gaps cannot consume recommendation slots. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
Review fixes are ready in
Focused regressions were added; 35/35 targeted Scala tests, codegen/package, repository style, Black, and the final Python/JVM smoke pass. A separate read-only correctness review found no remaining issues. Re-review requested. |
|
/azp run |
|
Azure Pipelines: Successfully started running 1 pipeline(s). |
Refs microsoft#2275 Refs microsoft#2594 ## Summary Use ANSI-safe try_cast expressions for numeric identifier compatibility and legacy mappings. Persist whether model-owned user and item mappings safely round-trip through IntegerType, reuse those flags when selecting recommendation output schemas, and limit destination-index collection to mapping-less legacy models. Add ANSI overflow, persisted-flag, legacy-default, and recommendation-planning regressions. ## Prompting Intent The engineer asked to resolve the second independent review of PR microsoft#2594: prevent CAST_OVERFLOW under spark.sql.ansi.enabled=true, eliminate repeated mapped-model recommendation scans and index collection, add focused regressions, rerun Scala/codegen/Python validation, update the existing PR, trigger Azure Pipelines, and request another re-review. ## Linked Sources - Pull request and review context: microsoft#2594 - Feature request: microsoft#2275 - Original pull request: microsoft#2283 - Repository review policy: .github/skills/code-review/SKILL.md ## Rationale Use Spark SQL try_cast in both cast directions rather than pre-cast comparisons so out-of-range values become null and are filtered even with ANSI mode enabled. Compute compatibility once while fitting and persist it with conservative false defaults for legacy models, avoiding full mapping scans on every recommendation call. New model mappings are contiguous, so mapped models rank the score vector directly; only mapping-less legacy models collect actual candidate indices to preserve gapped-ID correctness. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
Second follow-up review fixes are now in 622fa44: ANSI-safe try_cast handling prevents CAST_OVERFLOW, and persisted compatibility flags remove mapped-model mapping scans/index collection while preserving legacy gap filtering. Local validation passed (34/34 targeted Scala tests, 13/13 final SAR identifier tests, codegen/package, both Scalastyle checks, repository-wide Black, and packaged-JAR Python/JVM smoke). @eisber, please re-review when available. |
|
/azp run |
|
Azure Pipelines: Successfully started running 1 pipeline(s). |
Context
Closes #2275.
This is a current-master replacement for the intent of #2283. The original PR is intentionally left untouched; its direct string-to-integer casts were not reused because nonnumeric and wide identifiers could not round-trip safely.
What changed
originalID/indexmappings as model-ownedDataFrameParams.Int.Independent review fixes
The first follow-up review findings are addressed in
15d0be60c0:RankingTrainValidationSplit.splitDFuses typed Spark structs, arrays, slicing, and exploding in both rated and unrated branches; string IDs are no longer coerced throughDoubleUDFs.Int; strings and non-round-tripping numeric IDs remain lossless.The second follow-up review findings are addressed in
622fa446d3:try_castin both directions, so wide values are filtered rather than throwingCAST_OVERFLOWwhenspark.sql.ansi.enabled=true.IntegerTypeis computed once during fitting, persisted with the model, and given conservative defaults for legacy models. Recommendation planning no longer scans mapped-model mappings or collects their destination indices; index collection remains only for mapping-less legacy models.The reviews also prompted removal of an unmanaged mapping cache/materialization, replacement of an unused grouped count with
distinct, and alignment of the PythonrecommendForAllItems(numItems=...)keyword.API and design notes
SAR owns the mappings rather than requiring
RecommendationIndexer. Composing that stage would stringify numeric values, expose index columns to pipeline users, and still be unable to recover every original Spark type. Existing Scala method signatures are retained, including the legacynumItemsparameter name onrecommendForAllItems.The deterministic global mapping sort and serialized mapping data are deliberate costs for reproducible save/load behavior. SAR already materializes its interaction structures on the driver, so this does not introduce a new distributed-to-driver boundary in the core algorithm.
Validation
SARIdentifierSpec,SARSpec,RankingTrainValidationSpec,RankingEvaluatorSpec, andRecommendationIndexerSpec)SARIdentifierSpec: 13/13 passedcore / codegencore / Compile / packageBinblack==22.3.0 --check --extend-exclude 'docs/' .: 179 files passedRegression coverage
transformSchema, and Python wrappers