Skip to content

feat: support learning-to-rank tuning in TuneHyperparameters - #2778

Merged
Rana Singh (ranadeepsingh) merged 7 commits into
microsoft:masterfrom
leninworld:feat-learning-to-rank-tuning-issue-934
Oct 22, 2026
Merged

Rana Singh (ranadeepsingh) merged 7 commits into
microsoft:masterfrom
leninworld:feat-learning-to-rank-tuning-issue-934

Conversation

@leninworld

@leninworld Lenin Mookiah (leninworld) commented Oct 20, 2026 •

Copy link
Copy Markdown
Contributor

TuneHyperparameters currently rejects rankers, so Python users cannot use it to tune LightGBMRanker. This PR adds query-preserving cross-validation, graded NDCG evaluation, and a Python example while leaving classification and regression tuning unchanged.

Related Issues/PRs

#934. This covers learning-to-rank tuning and its Python example; additional input-format examples and external optimization-library integrations remain outside this PR.

What changes are proposed in this pull request?

TuneHyperparameters currently rejects rankers during evaluation. Add a ranking-only route that keeps each query group in one fold, evaluates graded NDCG (ndcg or ndcg@k), selects the best run and refits it on the full input. The classifier and regressor route is unchanged.

The route rejects mixed estimator kinds, inconsistent query/label/prediction columns, structural search-space changes and custom label gains. Fresh estimator copies discard prior reference-dataset binning. On a failed parallel run, sibling Spark job groups are cancelled and running futures are interrupted. The caller waits for actual worker termination, even beyond the shutdown wait interval, before releasing the tuner’s cached data and exposing the original cause. Estimators remain responsible for their own interruption handling and internal cleanup; an estimator that ignores interrupts can delay return.

The website includes a Python LightGBMRanker example and the metric/scaling limits. The metric uses LightGBM's default gain and discount and zero-IDCG convention, but does not promise identical native evaluation: it uses pessimistic prediction ties and equal query weights. Labels must be integer relevance values in [0, 30]. Fold assignment sorts distinct query groups in one partition. getBestModelInfo() remains unsupported for rankers.

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

The history-preserving integration commit 9f55e5deb2bc8b9beb052407815eecd0b75b6d02 merges current master e8b71d877fa9e5517d6e5a4e384a3994a179b315. All five contributor commits remain intact; that integration preserved the six-file contribution patch byte-for-byte.

The focused follow-up b1a37c48976e845e841a1d1e9d961501c82d1383 addresses the subsequent prediction-column review: candidates must agree on their effective prediction column before training, so a different winner cannot invalidate the tuner's advertised output column. It adds three regressions and clarifies the documented contract; classification/regression behavior is untouched.

Local validation used WSL Linux, JDK 11, Spark 3.5.0 and Scala 2.12.17:

  • sbt core/compile core/Test/compile lightgbm/compile lightgbm/Test/compile — passed on the final follow-up source.
  • sbt "core/testOnly com.microsoft.azure.synapse.ml.automl.VerifyRankingTuning" — passed all 26 tests on the final follow-up source, with no canceled, ignored, or pending tests.
  • sbt "lightgbm/testOnly com.microsoft.azure.synapse.ml.lightgbm.split1.VerifyLightGBMRankingTuning" — passed all six native tests on the final follow-up source, with no canceled, ignored, or pending tests.
  • sbt core/scalastyle core/Test/scalastyle lightgbm/Test/scalastyle — passed on the final follow-up source with zero errors or warnings.
  • sbt 'core/testOnly com.microsoft.azure.synapse.ml.automl.VerifyRankingTuning -- -z "prediction column"' — before the guard, both incompatible-column regressions failed because fitting incorrectly succeeded; the compatible-column winner control passed. All three pass with the guard as part of the full 26-test suite. The negative tests verify zero training calls, including an incompatible candidate not selected by numRuns.
  • python3 -m black --check lightgbm/src/test/python/synapsemltest/lightgbm/test_ranking_tuning.py — passed.
  • sbt core/codegen lightgbm/codegen — passed on the unchanged contributor source.
  • python3 pr2778_baseline_replay.py — the base TuneHyperparameters.scala implementation fails the public fit regression with Model type not supported for evaluation; the same regression passes against the PR implementation. Baseline classes were compiled separately; repository source was not mutated.
  • python3 pr2778_python_smoke.py — passed the contributor's generated-Python regression with JVM class provenance verified against this checkout. Additional real LightGBM fitting produced 24 finite predictions and NDCG 1.0; model save/load preserved predictions and the metric.

The two Python replay scripts and logs are retained in the maintainer's session evidence, not committed as new product tests. Native local tests use single-task training; they do not establish distributed multi-executor sibling-cancellation behavior.

Full Azure validation build 242160268 passed all 65 jobs for the follow-up after /azp run (reason=pullRequest, source 4191bc66722effc89ae586d282571990d9dd6df0, with verified current-target and final-head parents).

Published individual results confirm 26/26 core ranking tests (including all three prediction-column regressions), 6/6 native ranking tests, and test_tune_ranker_from_python in the 16/16 LightGBM Python suite. All 111 website code-block tests passed, including all three _AutoML.md blocks. The existing VerifyTuneHyperparameters suite also passed 13/13 tests. Relevant run IDs are 1183508397, 1183512101, 1183527263, 1183519282 and 1183508399, respectively.

The first UnitTests nn attempt passed all 40 tests but failed coverage publication because of a TLS certificate-name mismatch. Only that failed job was retried, on the same source SHA; its second attempt passed tests and publication. That retry changed no source, workflow, dependency pin or check policy. Three unrelated deep-learning tests retain their explicit pre-existing skips; no ranking or website scenario was skipped.

GitHub validation passed, including compilation/style, Python formatting, both CodeQL language analyses, dependency review, website build and link checking. Website deployment is intentionally skipped for PRs; the separate CodeQL aggregate is skipped while both language analyses pass. The final-head automated review has no open or suppressed findings, and all review threads are resolved. Contributor sign-off and the required human approval remain outstanding.

Earlier integration build 242154399 published 23 passing core and six passing native ranking tests. Its RTests core job failed obtaining an OIDC token because of ERR_TLS_CERT_ALTNAME_INVALID; the R test task was skipped and coverage was consequently absent. That infrastructure failure is not counted as a passing check; this older run was superseded when the follow-up was pushed.

Contributor's original and historical validation evidence

The test-teardown follow-up always interrupts the tuner caller in finally, before releasing the worker cleanup latch and joining. Production code is unchanged. A controlled startup-assertion failure left the caller alive with the preceding teardown; the validation harness then rescued it. With the corrected teardown, the caller had already stopped. Both injected runs intentionally report the assertion failure and exit after the observation/rescue checks; they are not counted as passing test suites.

On the exact restored one-line patch, fresh Docker validation passes 23 core ranking tests, six native LightGBM tests, affected compilation and three style lanes. Focused interruption/failure/cleanup cases pass in three fresh JVMs per module. Temporary fault-injection variants were restored before these checks.

The preceding worker-interruption correction keeps worker and caller interruption separate. A worker-thrown InterruptedException propagates as the exact original exception without setting the caller's interrupt flag. Actual caller interruptions are restored after cleanup; caller-side ExecutionException values are not accidentally unwrapped.

Fresh Docker validation on that two-file correction passed 23 core ranking tests, six native LightGBM tests, affected main/test compilation and three Scala style lanes. Focused interruption/failure/cleanup cases pass in three fresh JVMs per module. The new worker-only regression fails on published predecessor 91c720b8 (0/1), then passes with this patch. It verifies exception identity, a clear caller interrupt flag, a subsequent blocking call and cached-RDD cleanup. A real interrupted caller also waits for worker cleanup and returns with its interrupt flag restored.

Worker-interruption validation from the preceding production revision (rendered summary of retained Docker logs; not a terminal capture):

Worker interruption validation

Prior cancellation/cleanup revision (historical validation):

The cancellation/cleanup revision interrupted running sibling futures, releases ranking-score caches on the caller, and waits for actual worker termination before releasing shared folds. Caller interruption is restored after cleanup, and an existing worker failure remains the primary exception. Estimators remain responsible for their own internal cleanup under interruption.

On that revision, 18 core ranking tests and six native LightGBM tests passed, along with affected Scala compilation and style checks. Failure/cleanup cases pass in three fresh JVMs per affected module. The interruption regression fails on the preceding published source; the slow-cleanup tests reject a one-wait shutdown mutant. Native failure controls run a single failing LightGBM run (default transfer without validation data, and bulk transfer with validation data) and check cached RDD cleanup; they do not exercise sibling interruption inside LightGBM or audit broadcast cleanup.

One initial isolated native failure-only JVM stalled after its passing assertions and was stopped; the published-code control and an unchanged-candidate repeat both exited normally. The full suite and the three required fresh-JVM runs exited successfully.

The quoted label_gain guards from the earlier review remain covered. The broader validation and two original feature images below are historical; Python, documentation and classifier/regressor source is unchanged.

Docker on Linux AMD64, Spark 3.5.0, Java 11.0.32, Scala 2.12, Python 3.11.8 and LightGBM 3.3.510:

  • Exact-base core and native fixtures fail with unsupported ranker evaluation; a stale-reference regression also fails before its correction.
  • Scala main/test compilation and affected style checks pass.
  • Six ranking core tests, four real LightGBM tests and two local discovery/cache checks pass.
  • Thirteen existing tuning benchmarks pass with the reference CSV byte-identical. These ran before the final ranking-private reference-reset change; the non-ranking body is unchanged.
  • The generated Python regression passes using JVM-verified branch-built JARs, including fit/transform over 24 rows, metric bounds and native-memory cleanup.
  • Website: all three Python code-block tests, 37 website tests and the full optimized build pass; generated HTML contains the new sample and limitations.

Hosted CI and the full all-module fuzzing suite have not run locally.

Latest cancellation/cleanup validation (rendered summary of retained Docker logs; not a terminal capture):

Cancellation and cleanup validation

Original feature validation summaries (historical typed summaries of retained Docker logs; not terminal captures):

Scala and website validation summary

Generated Python API runtime summary

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.

The Python learning-to-rank example is in docs/Quick Examples/estimators/core/_AutoML.md, with the website test marker and existing API table.

  1. 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.
  2. Follow the pattern in markdown file and add another section for your new API, including pyspark, scala (and .NET potentially) samples.
  3. Make sure the DocTable points to correct API link.
  4. Navigate to website folder, and run npm start to make sure the website renders correctly.
  5. Don't forget to add <!--pytest-codeblocks:cont--> before each python code blocks to enable auto-tests for python samples.
  6. Make sure the WebsiteSamplesTests job pass in the pipeline.

Add a ranking-only route with whole-query cross-validation and
graded NDCG, then refit the selected ranker on the full input.
Keep existing classifier and regressor tuning unchanged.

Clear copied reference datasets so a previously fitted ranker
does not reuse unrelated binning. Cover Scala, native LightGBM
and the generated Python API, and add a website example.

Refs microsoft#934
Copilot AI balanced review requested due to automatic review settings October 20, 2026 23:50
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
There may be pipelines that require an authorized user to comment /azp run to run.

@github-actions

Copy link
Copy Markdown

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.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Quoted gain keys bypass validation, allowing training gains to differ silently from the tuning metric.

1 open issue
What changed in this PR

Adds learning-to-rank support to TuneHyperparameters, addressing the tuning and Python-example portion of #934 while preserving classification and regression behavior.

Changes:

  • Adds query-preserving folds, graded NDCG evaluation, and best-model refitting.
  • Adds ranking validation and failure cleanup.
  • Adds Scala/Python tests and a documented Python example.
File Description
lightgbm/​src/​test/​scala/​com/​microsoft/​azure/​synapse/​ml/​lightgbm/​split1/​VerifyLightGBMRankingTuning.scala Tests native ranker tuning and gain validation.
lightgbm/​src/​test/​python/​synapsemltest/​lightgbm/​test_ranking_tuning.py Tests the generated Python tuning API.
docs/​Quick Examples/​estimators/​core/​_AutoML.md Documents ranking usage and limitations.
core/​src/​test/​scala/​com/​microsoft/​azure/​synapse/​ml/​automl/​VerifyRankingTuning.scala Tests folds, metrics, validation, and cleanup.
core/​src/​main/​scala/​com/​microsoft/​azure/​synapse/​ml/​automl/​TuneHyperparameters.scala Routes rankers to ranking-specific tuning.
core/​src/​main/​scala/​com/​microsoft/​azure/​synapse/​ml/​automl/​RankingTuning.scala Implements ranking evaluation and model selection.

Review effort: Balanced

Comment thread core/src/main/scala/com/microsoft/azure/synapse/ml/automl/RankingTuning.scala Outdated
Normalize pass-through gain keys consistently with LightGBM, including
sampled parameters. Cancel Spark jobs while allowing sibling driver
cleanup to complete before bounded forced shutdown.

Add quoted-key and interruption regressions. Docker validation passes:
16 core and four native LightGBM tests, affected Scala compilation/style,
and both cleanup tests in three fresh JVM runs.

Refs microsoft#934
Copilot AI balanced review requested due to automatic review settings October 21, 2026 00:28

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Failed parallel evaluations can leave sibling work running after shared data is released.

1 open issue
1 resolved since last review

Review effort: Balanced

Comment thread core/src/main/scala/com/microsoft/azure/synapse/ml/automl/RankingTuning.scala Outdated
Copilot AI balanced review requested due to automatic review settings October 21, 2026 18:56

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

Worker-only interruption handling remains unresolved, and concurrent native cleanup warrants final human review.

1 open issue
1 resolved since last review

Review effort: Balanced

Comment thread core/src/main/scala/com/microsoft/azure/synapse/ml/automl/RankingTuning.scala Outdated
Copilot AI balanced review requested due to automatic review settings October 21, 2026 19:23

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

Distributed cancellation and native-training cleanup warrant human review, with a test-teardown issue still unresolved.

1 open issue
1 resolved since last review

Review effort: Balanced

Copilot AI balanced review requested due to automatic review settings October 21, 2026 19:47

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

Parallel Spark cancellation and native-training cleanup warrant human review, with hosted CI still pending.

No open issues 🎉

1 resolved since last review

Review effort: Balanced

## Summary
Merge master at e8b71d8 into the
existing learning-to-rank tuning contribution. Preserve all five
contributor commits and leave their production code, tests and example
unchanged.

## Prompting Intent
Prepare microsoft#2778 using the external-contributor review
and PR-readiness workflows, retaining the contributor's ownership and
running validation on the existing pull request.

## Linked Sources
- Pull request: microsoft#2778
- Feature request: microsoft#934
- Integrated target: microsoft@e8b71d8
- Contributor head: microsoft@e2708b2

## Rationale
Use a history-preserving merge instead of assuming permission to rewrite
an external contributor's commits. This permits an ordinary fast-forward
push to the existing branch. The merge only incorporates dependency and
workflow updates already accepted on master; it does not change the
ranking implementation or expand the contribution's scope.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 8a191824-df94-4aa3-b2a0-391782cfaa15
Copilot AI balanced review requested due to automatic review settings October 22, 2026 07:50
@ranadeepsingh

Copy link
Copy Markdown
Collaborator

/azp run

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 1 pipeline(s).

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

An unresolved output-schema inconsistency needs correction, and concurrent cancellation and native-training cleanup still warrant maintainer review and hosted CI confirmation.

1 open issue

Review effort: Balanced

## Summary
Reject inconsistent effective prediction columns across all ranking candidates
and sampled runs before training. Add two rejection regressions, a shared-column
winner control, and document the candidate-column contract.

## Prompting Intent
Make microsoft#2778 ready using the external-contributor review and
PR-loop safeguards. Preserve the contributor's commits and implementation
intent, address verified review findings, and prove public behavior before
pushing a focused maintainer follow-up to the existing branch.

## Linked Sources
- Contribution: microsoft#2778
- Original request: microsoft#934
- Schema review: microsoft#2778 (comment)

## Rationale
The tuner advertises the first estimator's output schema but returns the winning
model. A later candidate with a different prediction column could therefore
break downstream pipeline stages. Validate the common column in the existing
ranking plan instead of changing the shared classification/regression route
or renaming the winner's output after training.

The regressions cover both an unsampled incompatible candidate and a
better-scoring second candidate. They assert that rejection performs no
training. The compatible control checks the winner, schema shape and
materialized prediction values without conflating Spark's model-only metadata
with an output-column mismatch.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 8a191824-df94-4aa3-b2a0-391782cfaa15
Copilot AI balanced review requested due to automatic review settings October 22, 2026 08:27
@ranadeepsingh

Copy link
Copy Markdown
Collaborator

/azp run

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 1 pipeline(s).

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

Supplied evidence reports hosted CI as pending and does not establish distributed multi-executor sibling-cancellation behavior, so final maintainer validation is needed.

No open issues 🎉

1 resolved since last review

Review effort: Balanced

@ranadeepsingh

Copy link
Copy Markdown
Collaborator

Thanks Lenin Mookiah (@leninworld) for adding query-preserving ranking tuning and the Python example! This makes TuneHyperparameters usable with LightGBMRanker while keeping the classification/regression route unchanged.

I preserved all five of your commits and added two focused maintainer follow-ups:

  • 9f55e5d merges current master without rewriting your history; the contribution patch was unchanged by that integration.
  • b1a37c4 addresses the subsequent prediction-column review: incompatible candidate columns are rejected before training. It adds three focused regressions and documents the contract.

The original unsupported-ranker regression fails against the base implementation and passes with your change. The two new column-rejection regressions likewise fail before the guard and pass afterward; the shared-column winner control passes both before and after.

Azure build 242160268, triggered with /azp run, passed all 65 jobs for final head b1a37c48976e845e841a1d1e9d961501c82d1383 through its verified merge with current master. Published results confirm 26 core ranking tests, six native ranking tests, the generated-Python ranker regression, and all three AutoML website blocks passed. Local native checks also materialized 24 finite predictions and verified save/load parity. One unrelated coverage-publication TLS failure required an isolated job retry; the retry passed without source or policy changes.

GitHub checks are green, and the current-head automated review has no open or suppressed findings. Native tests do not establish distributed multi-executor sibling-cancellation behavior; the documented estimator-interruption responsibility remains unchanged.

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 commits, or let me know and I will revert them. Contributor sign-off and the required human maintainer approval remain; this comment is not a merge approval. #934 retains its separate input-format and external-optimizer example scope.

@leninworld

Copy link
Copy Markdown
Contributor Author

Thanks Lenin Mookiah (@leninworld) for adding query-preserving ranking tuning and the Python example! This makes TuneHyperparameters usable with LightGBMRanker while keeping the classification/regression route unchanged.

I preserved all five of your commits and added two focused maintainer follow-ups:

  • 9f55e5d merges current master without rewriting your history; the contribution patch was unchanged by that integration.

  • b1a37c4 addresses the subsequent prediction-column review: incompatible candidate columns are rejected before training. It adds three focused regressions and documents the contract.

The original unsupported-ranker regression fails against the base implementation and passes with your change. The two new column-rejection regressions likewise fail before the guard and pass afterward; the shared-column winner control passes both before and after.

Azure build 242160268, triggered with /azp run, passed all 65 jobs for final head b1a37c48976e845e841a1d1e9d961501c82d1383 through its verified merge with current master. Published results confirm 26 core ranking tests, six native ranking tests, the generated-Python ranker regression, and all three AutoML website blocks passed. Local native checks also materialized 24 finite predictions and verified save/load parity. One unrelated coverage-publication TLS failure required an isolated job retry; the retry passed without source or policy changes.

GitHub checks are green, and the current-head automated review has no open or suppressed findings. Native tests do not establish distributed multi-executor sibling-cancellation behavior; the documented estimator-interruption responsibility remains unchanged.

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 commits, or let me know and I will revert them. Contributor sign-off and the required human maintainer approval remain; this comment is not a merge approval. #934 retains its separate input-format and external-optimizer example scope.

LGTM. Thank you!

@ranadeepsingh
Rana Singh (ranadeepsingh) merged commit 71346cf into microsoft:master Oct 22, 2026
80 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants