Skip to content

fix: scale and harden LightGBM validation data transfer - #2664

Merged
Rana Singh (ranadeepsingh) merged 32 commits into
microsoft:masterfrom
ranadeepsingh:copilot/issue-2294-validation-data-scaling
Aug 27, 2026
Merged

fix: scale and harden LightGBM validation data transfer#2664
Rana Singh (ranadeepsingh) merged 32 commits into
microsoft:masterfrom
ranadeepsingh:copilot/issue-2294-validation-data-scaling

Conversation

@ranadeepsingh

@ranadeepsingh Rana Singh (ranadeepsingh) commented Aug 18, 2026

Copy link
Copy Markdown
Collaborator

Summary

Fixes #2294.

Large LightGBM validation sets no longer have to be collected into the Spark
driver heap and broadcast as a complete Array[Row]. Validation partitions are
streamed to an authenticated driver-local spool, represented by a small
versioned descriptor, and streamed from that spool to each native LightGBM
Dataset owner.

This preserves exact validation metrics and early-stopping behavior while
removing the spark.driver.maxResultSize and driver-heap bottleneck reported for
large sparse validation data.

What changed

  • Execute validation ingestion with Dataset mapPartitions; no new RDD
    implementation is introduced and validation rows never become Spark task
    results.
  • Write each Spark task attempt to a separate spool file. The task waits for a
    driver acknowledgement, returns only
    (partitionId, taskAttemptId, rowCount), and the driver promotes only the
    attempts Spark actually committed.
  • Broadcast only a versioned endpoint descriptor. Genuine legacy
    Broadcast[Array[Row]] values remain readable, while unknown future
    descriptor versions fail clearly.
  • Authenticate ingest and serving with separate random UUID tokens and bind
    both listeners to the resolved driver host.
  • Bound concurrent transfers to eight, reuse fixed 64 KiB buffers, and enforce
    write-inactivity deadlines with one daemon watchdog per JVM. Queued readers
    start their read timeout after admission rather than timing out behind healthy
    transfers.
  • Preserve the first asynchronous/training failure and attach later cleanup
    failures as suppressed exceptions.
  • Reject null validation indicators inside the existing Catalyst filter action,
    avoiding a separate full-data preflight scan.

File-by-file necessity

File Why it is required
.github/skills/synapseml-branches/references/branch-spark3p5.md Records Fabric Runtime 1.3 Python lightgbm 4.3.0 versus JVM lightgbmlib 3.3.510.
.github/skills/synapseml-branches/references/branch-spark4-common.md Records Runtime 2.0 Python lightgbm 4.6.0, the same JVM artifact, and the Spark 4.0 comparison rule.
.pipelines/release-compat-prerequisites.txt Replays the three Spark 4.1 Dataset-ownership prerequisites required by this change.
BulkPartitionTask.scala Ensures validation data is read only by native Dataset owners and keeps the transport comment accurate.
LightGBMBase.scala Removes driver collection, splits training/validation with DataFrame expressions, and scopes the server/broadcast lifecycle.
NetworkManagerSocketSupport.scala Preserves primary asynchronous failures and provides bounded write-inactivity cancellation.
StreamingPartitionTask.scala Consumes the descriptor-backed validation iterator with deterministic native Dataset cleanup.
ValidationDataIngest.scala Implements acknowledged, task-attempt-aware partition ingestion and successful-attempt promotion.
ValidationDataServer.scala Owns the authenticated spool ingest/serve lifecycle, bounded transfers, descriptor compatibility, and cleanup.
ValidationDataSpool.scala Validates and orders the canonical contiguous part-N spool files.
LightGBMValidationDataSuite.scala Covers public classifier/ranker behavior, persistence, null handling, and no-driver-collection scaling.
ValidationDataIngestRetrySuite.scala Reproduces a task that sends all rows and then fails before Spark commit; retry rows must win.
ValidationDataServerFlowControlSuite.scala Proves a ninth reader survives queueing behind the eight-transfer cap while active clients keep progressing.
ValidationDataServerLifecycleSuite.scala Covers authentication, malformed frames, timeouts, setup failures, stalled clients, and cleanup ordering.
ValidationDataServerSupportSuite.scala Covers first-failure retention, write watchdog behavior, buffer reuse, and spool validation.
pipeline.yaml Replays prerequisite renames as delete/add changes with git diff --no-renames.
tools/ci/tests/test_pipeline_yaml.py Proves the Spark 4.1 prerequisite replay preserves renamed-file semantics.

Every changed file supports production behavior, a required cross-version
prerequisite, documentation requested for branch decisions, or a regression
that directly protects those paths.

Security and credentials

  • No Key Vault, tenant, account, certificate, workspace-resolution, or
    INTEGRATION_* behavior changes in this PR.
  • The existing credential chain remains unchanged:
    fabrictest-cert-admin-kv -> bami-tenant-adminuser -> SemPy AdminUser01.
  • No secret values were added. The final diff contains no secret-like
    assignments, and retained Fabric logs contain token-related diagnostic names
    but no token values.
  • Tokens are not logged, dynamic spool names use validated numeric IDs, attempt
    files use atomic CREATE_NEW, and both server paths close active sockets
    before spool deletion.
  • A dedicated security review found no concrete vulnerability in binding,
    authentication, protocol framing, filesystem paths, cleanup, serialization,
    legacy compatibility, or credential handling.

Fabric LightGBM versions

Fabric baseline Spark Python package JVM/SWIG artifact
Runtime 1.3 3.5 lightgbm 4.3.0 com.microsoft.ml.lightgbm:lightgbmlib:3.3.510
Runtime 2.0 4.1 lightgbm 4.6.0 com.microsoft.ml.lightgbm:lightgbmlib:3.3.510
Spark 4.0 port No separate managed Fabric runtime Use Runtime 2.0 as the comparison point lightgbmlib:3.3.510

Both managed runtimes load a lightgbmlib-3.3.510.jar whose SHA-256 is
f2b1b13172699832594303ab4c04f3bc8fc2d24737e3e8c11d98d69a88c09272,
byte-for-byte identical to Maven Central. The 4.3.0 and 4.6.0 values are Python
package versions, not Maven/JNI versions, so this PR correctly leaves
lightgbmlib unchanged.

Compatibility and limits

  • No public estimator parameters, JVM signatures, generated Python API, or
    serialized parameter shapes change.
  • TrainingContext.validationData keeps its existing type and constructor
    position.
  • Model save/load behavior remains unchanged.
  • The fix removes O(validation data) driver heap/result-size pressure. It
    intentionally still requires O(validation data) driver disk, one complete
    network transfer per native Dataset owner, and native-worker memory because
    exact metrics such as AUC require the complete validation set.

Validation

Final target: master at 3498243d0b5534117eb24c0c3cebde3eb1c1a963
Final head: 8b90ff513749fdf5a2c2521e06d1f0e785f71d34

  • Branch state: 32 commits ahead and 0 behind current master.
  • Spark 3.5 / Scala 2.12 / JDK 11:
    • LightGBM compile, test-compile, main scalastyle, and test scalastyle passed.
    • Focused public-estimator, retry, flow-control, protocol, lifecycle,
      streaming-ownership, and reference-Dataset coverage passed 53/53.
    • The exact-head null-indicator assertion was rerun after making its exception
      supertype portable across Spark versions.
  • Spark 4.1 / Scala 2.13 / JDK 17 compatibility replay:
    • All three required prerequisite commits retained.
    • Compile, test-compile, main scalastyle, and test scalastyle passed.
    • The same focused suites passed 53/53 with the required
      java.prefs/java.util.prefs module opening.
  • Pipeline/release-replay contracts: 50 passed, 27 platform-specific skips.
  • Black 22.3.0: 201 files unchanged.
  • Reviews:
    • Dedicated security review: no concrete vulnerabilities.
    • General code review found three scale-path defects (retry-admission timeout,
      unbounded blocked writes, and an extra null scan); all three were fixed and
      now have passing regressions.
    • Automated review covered the complete 17-file change set. Its Boolean-cast
      and tautological-condition findings were fixed in 8b90ff51 and passed
      targeted Spark 3.5 and Spark 4.1 compile, style, and 10/10 tests on each
      baseline.
    • A follow-up automated review covers exact final head 8b90ff51; it reported
      no new or suppressed findings. All 11 review threads are resolved.
  • Managed Fabric lightgbm-streaming on exact final jars:
    • MSIT tenant, DONT_DELETE_SynapseML_Build workspace, Fabric Spark
      3.5.5.5.4.20260807.1.
    • Application application_1787794769102_0001.
    • 4,000 rows, four partitions, two repeated fits, prediction counts
      4,000 / 4,000.
    • Runtime class and native-library provenance names the supplied core,
      lightgbmlib-3.3.510, and final-head LightGBM jars.
    • JUnit 1/1 passed; driver/executor logs retained; unique scratch lakehouse
      deleted successfully (cleanupExitCode: 0).
    • Evidence:
      pr-2664-fabric-e2e-8b90ff51/evidence.json in the retained session
      artifacts.
  • Current-head GitHub and Azure validation:
    • Azure pull-request build 233012758 completed successfully for
      8b90ff513749fdf5a2c2521e06d1f0e785f71d34; all 76 PR check contexts pass
      with no failed, pending, or missing required checks.
    • The first RTests core attempt was blocked before tests by a transient
      Azure-agent TLS mismatch (msdata.visualstudio.com received an unrelated
      *.azureedge.net certificate). Retrying the failed job in the same build
      executed the workload and published 70/70 passing JUnit tests.
    • Final repository readiness reports zero unresolved review threads, zero
      suppressed final-head findings, and completeness.complete = true.

Repository policy still requires human approval before merge.

@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 Rana Singh (@ranadeepsingh) 👋!
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.

@ranadeepsingh
Rana Singh (ranadeepsingh) force-pushed the copilot/issue-2294-validation-data-scaling branch from fbe4a0a to b33dcc4 Compare August 18, 2026 12:49
Rana Singh (ranadeepsingh) pushed a commit to ranadeepsingh/SynapseML that referenced this pull request Aug 18, 2026
## Summary
Track and forcibly close active validation clients, use daemon executors with verified termination, and make spool ownership exception-safe across socket, executor, and server-start failures. Add deterministic lifecycle coverage for stalled and cancelled clients, construction failures, accept timeouts, real serving failures, and nontermination.

## Prompting Intent
Fix the resource-lifecycle blockers found in PR microsoft#2664 without changing exact LightGBM validation semantics: prevent blocked socket writes and JVM thread leaks, delete spools on every safe construction-failure path, preserve genuine serving failures, and tolerate expected task cancellation or speculation.

## Linked Sources
- Issue: microsoft#2294
- Draft PR and lifecycle review: microsoft#2664
- Related issue: microsoft#924
- Related issue: microsoft#978

## Rationale
Closing tracked sockets before executor shutdown is the only reliable way to unblock socket writes; thread interruption alone is insufficient. Spool deletion is gated on confirmed executor termination so no worker can read deleted files. Expected socket disconnects and accept polling timeouts are nonterminal, while file and generic I/O failures remain visible to await(). Injectable resource factories make every ownership transition deterministic to test without environmental port races.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@ranadeepsingh

Rana Singh (ranadeepsingh) commented Aug 18, 2026

Copy link
Copy Markdown
Collaborator Author

Lifecycle follow-up pushed at 3eaca96. Active ingest/serve sockets are now closed before executor shutdown; spool deletion requires confirmed termination; construction ownership is covered through bind, executor, and start failures; cancellation/accept timeouts remain nonterminal while real I/O failures propagate. JDK 11 compile/test-compile and both scalastyle tasks passed, Black passed, and the two targeted suites passed 13/13. Copilot automated review was requested and polled twice for 10 minutes but no review exists for this head. Azure was intentionally not triggered. Merge #2662 first, then rebase #2664 and preserve both cleanup changes.

@ranadeepsingh
Rana Singh (ranadeepsingh) marked this pull request as ready for review August 18, 2026 18:59
Copilot AI lite review requested due to automatic review settings August 18, 2026 18:59
@azure-pipelines

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

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.

Pull request overview

This PR fixes LightGBM training failures when validationIndicatorCol is used with large/sparse validation sets by eliminating the driver-side collect() + broadcast of all validation rows. Instead, it spools preprocessed validation rows to driver-local disk and streams them to executors/tasks that construct native LightGBM validation Datasets, preserving full validation semantics (each worker still receives the complete validation set).

Changes:

  • Add a driver-side ValidationDataServer that ingests per-partition validation rows via sockets into a spool directory, then serves the spool to executors via authenticated streaming.
  • Update streaming and bulk partition tasks to consume validation rows via ValidationDataServer.read(...) (iterator + explicit close) and to use the streamed row count.
  • Add lifecycle and end-to-end tests covering cleanup, persistence, bulk/stream modes, and null validation indicators.
Show a summary per file
File Description
lightgbm/src/main/scala/com/microsoft/azure/synapse/ml/lightgbm/ValidationDataServer.scala New spool + stream server/descriptor format for scalable validation data transfer.
lightgbm/src/main/scala/com/microsoft/azure/synapse/ml/lightgbm/LightGBMBase.scala Replaces validation collect() broadcast path with server-backed broadcast descriptor and explicit null-indicator rejection.
lightgbm/src/main/scala/com/microsoft/azure/synapse/ml/lightgbm/StreamingPartitionTask.scala Streams validation rows into the shared streaming validation Dataset and closes the iterator.
lightgbm/src/main/scala/com/microsoft/azure/synapse/ml/lightgbm/BulkPartitionTask.scala Streams validation rows for bulk-mode aggregation and closes the iterator.
lightgbm/src/test/scala/com/microsoft/azure/synapse/ml/lightgbm/split1/ValidationDataServerLifecycleSuite.scala New deterministic tests for server lifecycle cleanup and failure handling.
lightgbm/src/test/scala/com/microsoft/azure/synapse/ml/lightgbm/split1/LightGBMValidationDataSuite.scala New integration tests ensuring validation streaming avoids driver result-size issues and covers persistence/bulk/null-indicator behavior.

Review details

💡 Configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Suppressed comments (1)

lightgbm/src/main/scala/com/microsoft/azure/synapse/ml/lightgbm/ValidationDataServer.scala:689

  • Row lengths come from the network stream; a negative value other than the EndOfStream marker should be rejected explicitly. Without a guard this can throw NegativeArraySizeException (or lead to unexpected allocation behavior) instead of a clear I/O error.
          val length = input.readInt()
          if (length == EndOfStream) {
            close()
            None
          } else {
  • Files reviewed: 6/6 changed files
  • Comments generated: 2
  • Review effort level: Lite

Rana Singh (ranadeepsingh) pushed a commit to ranadeepsingh/SynapseML that referenced this pull request Aug 18, 2026
## Summary
Reject malformed negative row lengths in both validation ingest and executor read paths, and preserve training or await failures when broadcast and server cleanup also fail. Add deterministic malformed-frame and exception-suppression coverage.

## Prompting Intent
Resolve every exact-head Copilot review finding on PR microsoft#2664 without adding a semantics-changing frame-size cap: validate network-provided row lengths before allocation or copy, keep primary training diagnostics intact across cleanup, test both active and suppressed findings, and maintain cross-version compatibility.

## Linked Sources
- Issue: microsoft#2294
- Pull request: microsoft#2664
- Frame-length review: microsoft#2664 (comment)
- Cleanup review: microsoft#2664 (comment)
- Suppressed executor-read finding: Copilot review 4964793710 on PR microsoft#2664

## Rationale
A shared row-length decoder applies the protocol invariant consistently without imposing an arbitrary upper bound that could reject valid serialized sparse rows. Existing cleanup helpers attach broadcast and server cleanup failures to training or await exceptions, retaining actionable root-cause diagnostics while still exposing cleanup evidence. The helper is package-private and leaves public JVM and serialized APIs unchanged.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings August 18, 2026 20:13
@ranadeepsingh

Copy link
Copy Markdown
Collaborator Author

Suppressed finding from Copilot review 4964793710 is explicitly fixed in db086b5: executor-side validation row lengths now pass through the same protocol validator as ingest, rejecting negative values other than the -1 end marker before allocation. A deterministic malformed executor-stream test covers that exact path. No upper cap was invented because the protocol has no established maximum serialized-row size and a new cap could reject valid sparse rows. Both active threads were also fixed, replied to, and resolved. Local evidence: Scala 2.12 main/test compile; full patch on spark4.0 Scala 2.13 main/test compile; 17/17 lifecycle/integration tests; both scalastyle tasks; Black. Azure was not triggered. #2662 must still merge first, followed by rebasing #2664 while preserving both StreamingPartitionTask cleanup changes.

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.

Review details

Suppressed comments (1)

Previously missed (1) — in code that hasn't changed since the last review.

lightgbm/src/main/scala/com/microsoft/azure/synapse/ml/lightgbm/ValidationDataServer.scala:678

  • The ValidationDataServer.read(params) iterator can leak an open socket if authentication/stream setup fails before input is initialized (e.g., socket.getOutputStream, writeUTF, or flush throws). Wrap initialization in a try/catch that closes the socket on failure and only keep input as a field.
      private val socket = connect(params.host, params.port, params.timeoutMillis)
      socket.setKeepAlive(true)
      socket.setTcpNoDelay(true)
      socket.setSoTimeout(params.timeoutMillis)
      private val auth = new DataOutputStream(new BufferedOutputStream(socket.getOutputStream))
      auth.writeUTF(params.token)
      auth.flush()
      private val input = new DataInputStream(new BufferedInputStream(socket.getInputStream))
  • Files reviewed: 6/6 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

@ranadeepsingh

Copy link
Copy Markdown
Collaborator Author

/azp run

@azure-pipelines

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

Rana Singh (ranadeepsingh) pushed a commit to ranadeepsingh/SynapseML that referenced this pull request Aug 18, 2026
## Summary
Make executor validation-stream initialization exception-safe so authentication or input-stream setup failures close the connected socket. Add deterministic coverage for an authentication output failure.

## Prompting Intent
Resolve the suppressed exact-head Copilot finding on PR microsoft#2664, preserve the original setup exception, prove the socket is closed, revalidate Scala 2.12 and Scala 2.13 builds, and keep Azure validation delegated to the coordinating parent.

## Linked Sources
- Issue: microsoft#2294
- Pull request: microsoft#2664
- Suppressed review: Copilot review on commit db086b5

## Rationale
Stream initialization now uses the existing failure-preserving cleanup helper around socket configuration and authentication. This closes the socket on every pre-input failure while suppressing any close error onto the original setup exception, matching the lifecycle guarantees used elsewhere in the server.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings August 18, 2026 21:09
@ranadeepsingh

Copy link
Copy Markdown
Collaborator Author

Addressed the new-head suppressed socket-setup finding in 965f55d. ValidationDataServer.read now closes its connected socket through the existing primary-preserving cleanup helper if socket configuration, authentication output/write/flush, or input-stream construction fails. A deterministic authentication-output failure test proves the original IOException remains primary and the socket is closed. Final validation: 18/18 lifecycle/integration tests; Scala 2.12 main/test compile; the full PR patch applied cleanly and main/test compiled on spark4.0 Scala 2.13/JDK 17; both scalastyle tasks; Black. Azure remains intentionally untriggered. #2662 still merges first, then #2664 rebases while retaining both StreamingPartitionTask cleanup changes.

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.

Review details

Suppressed comments (2)

Previously missed (2) — in code that hasn't changed since the last review.

lightgbm/src/main/scala/com/microsoft/azure/synapse/ml/lightgbm/ValidationDataServer.scala:478

  • timeoutMillis is calculated using IngestPollTimeoutMillis as the seconds→milliseconds conversion factor. This couples the socket timeout semantics to the accept-poll constant, so changing the poll interval would silently change all timeouts. Use an explicit seconds→ms conversion instead (e.g., timeoutSeconds * 1000.0).
      val timeoutMillis = (timeoutSeconds * IngestPollTimeoutMillis).toLong
      socket.setSoTimeout(Math.max(IngestPollTimeoutMillis, timeoutMillis).min(Int.MaxValue).toInt)

lightgbm/src/main/scala/com/microsoft/azure/synapse/ml/lightgbm/ValidationDataServer.scala:658

  • rowCount converts a Long to Int via Math.toIntExact, which will throw a bare ArithmeticException if validation row count exceeds Int.MaxValue. Since downstream LightGBM APIs require Int row counts anyway, it would be clearer to fail with a targeted IllegalArgumentException explaining the limit.
  def rowCount(data: Broadcast[Array[Row]]): Int = {
    ValidationDataParams.fromBroadcast(data)
      .map(params => Math.toIntExact(params.rowCount))
      .getOrElse(data.value.length)
  }
  • Files reviewed: 6/6 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

Rana Singh (ranadeepsingh) pushed a commit to ranadeepsingh/SynapseML that referenced this pull request Aug 18, 2026
## Summary
Decouple seconds-to-milliseconds conversion from the accept-poll interval and replace overflow-prone validation row-count conversion with a targeted supported-range error. Add deterministic timeout and overflow tests.

## Prompting Intent
Resolve both suppressed findings from the exact-head Copilot review on PR microsoft#2664, keep timeout semantics stable if polling changes, and make the native Int row-count limit actionable without changing valid validation behavior.

## Linked Sources
- Issue: microsoft#2294
- Pull request: microsoft#2664
- Suppressed review: Copilot review on commit 965f55d

## Rationale
An explicit milliseconds-per-second constant documents the unit conversion independently of server polling. Validation counts already must fit the downstream native Int API, so validating the range and throwing a descriptive IllegalArgumentException preserves the existing limit while replacing an opaque ArithmeticException.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings August 18, 2026 21:44
@ranadeepsingh

Copy link
Copy Markdown
Collaborator Author

Addressed both suppressed findings from the 965f55d review in bcae907. Socket timeout conversion now uses an explicit seconds-to-milliseconds constant rather than the accept-poll interval, and validation row counts outside 0..Int.MaxValue fail with a targeted IllegalArgumentException describing the downstream native limit. Deterministic tests cover 2.5 seconds -> 2500 ms and Int overflow. Final Scala 2.12 evidence: main/test compile, both scalastyle tasks, Black, 20/20 lifecycle/integration tests. Azure remains intentionally untriggered; #2662 still merges first, then #2664 rebases preserving both StreamingPartitionTask cleanup changes.

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.

Review details

Suppressed comments (2)

Previously missed (2) — in code that hasn't changed since the last review.

lightgbm/src/main/scala/com/microsoft/azure/synapse/ml/lightgbm/StreamingPartitionTask.scala:165

  • rows.close() can throw (socket/input close), which would mask an exception from insertRowsIntoDataset and make failures harder to diagnose. Suppress non-fatal close failures so the primary training/validation error remains primary.
    } finally {
      rows.close()
    }

lightgbm/src/main/scala/com/microsoft/azure/synapse/ml/lightgbm/BulkPartitionTask.scala:53

  • rows.close() can throw (socket/input close), which would mask an exception from getChunkedColumns / mergeChunksIntoAggregatedArrays. Suppress non-fatal close failures so the primary error remains primary.
      } finally {
        rows.close()
      }
  • Files reviewed: 6/6 changed files
  • Comments generated: 1
  • Review effort level: Lite

Rana Singh (ranadeepsingh) pushed a commit to ranadeepsingh/SynapseML that referenced this pull request Aug 18, 2026
## Summary
Use millisecond-precise ingest deadlines and preserve row-processing failures when validation iterator cleanup also fails in streaming and bulk modes. Add deterministic regression coverage.

## Prompting Intent
Resolve the active and suppressed findings from the exact-head Copilot review on PR microsoft#2664 while keeping validation semantics and cleanup guarantees intact.

## Linked Sources
- Issue: microsoft#2294
- Pull request: microsoft#2664
- Active review: microsoft#2664 (comment)
- Suppressed findings: Copilot review on commit bcae907

## Rationale
The ingest deadline now derives from the already-normalized socket timeout, retaining fractional seconds. Both validation consumers share the existing primary-preserving cleanup helper, so close errors are suppressed onto processing failures rather than replacing them.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings August 18, 2026 22:14
SynapseML CI and others added 9 commits August 26, 2026 01:27
## Summary
In bulk single-dataset mode, stream the complete validation spool only to the active task on each executor while helper tasks satisfy shared synchronization without downloading duplicate validation data.

## Prompting Intent
Resolve the exact-head suppressed review finding on PR microsoft#2664 that helper-only bulk tasks unnecessarily consumed full validation transfers. Preserve exact validation semantics, keep non-single-dataset mode unchanged, avoid new unbounded resources, and prove the public estimator path on Spark 3.5 and Spark 4.1.

## Linked Sources
- Issue: microsoft#2294
- Pull request: microsoft#2664
- Exact-head Copilot review on 263da87

## Rationale
Single-dataset mode creates one native training and validation Dataset per executor, so only the active executor task needs the complete validation stream. Helpers still decrement the existing validation preparation latch so the active task cannot hang. Non-single-dataset bulk mode continues transferring the complete validation set to every training task, preserving native semantics. A direct policy assertion is paired with a public bulk-estimator regression for both single- and per-task Dataset modes.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
## Summary
Reduce the validation regression fixtures from four LightGBM tasks to two so every worker can enter the network-manager rendezvous on the two-slot Azure test runner.

## Prompting Intent
Rebase PR microsoft#2664 onto the current master branch and make it engineering-ready by diagnosing and fixing real product or test defects from the exact PR CI run without weakening the validation-data scaling regression.

## Linked Sources
- Issue: microsoft#2294
- Pull request: microsoft#2664
- Azure LightGBM1 job: https://msdata.visualstudio.com/b9b2accc-2d1c-45b3-9d24-0eb5d78cc47f/_build/results?buildId=231829385&view=logs&jobId=320c32d5-b58a-5840-324d-2225935ebd3a

## Rationale
The four-task fixtures deadlocked when the first two Spark tasks occupied every available slot while waiting for workers three and four to join the LightGBM topology. Two tasks are sufficient to exercise multi-worker streaming and single-Dataset active/helper behavior, and they make each legacy collected result larger, so the sparse regression still exceeds the 128 KiB driver-result limit. Reducing fixture parallelism is more reliable than attempting to replace the shared SparkContext master inside the suite.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
## Summary
Declare the three rebased commits from merged PR microsoft#2662 as release-compatibility prerequisites so the Spark 4.1 replay receives the Dataset ownership helpers before applying PR microsoft#2664.

## Prompting Intent
Rebase PR microsoft#2664 onto the current master branch and make every engineering gate reproducibly green, including the repository's Spark 4.1 release-compatibility replay.

## Linked Sources
- Issue: microsoft#2294
- Pull request: microsoft#2664
- Prerequisite pull request: microsoft#2662
- Release compatibility gate: https://github.com/microsoft/SynapseML/blob/master/pipeline.yaml

## Rationale
PR microsoft#2664 composes PR microsoft#2662's ownership-scoped validation Dataset initialization, while Spark 4.1 has not yet received that merged master change. The compatibility job applies each configured commit's first-parent patch, so all three rebased PR microsoft#2662 commits are listed in order rather than only its final GitHub merge OID. Applying the prerequisites first makes the PR patch conflict-free and preserves the exact code that passed Spark 4.1 test compilation.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
## Summary
Terminate the release-compatibility prerequisite configuration with a standard final newline.

## Prompting Intent
Finish PR microsoft#2664 with a clean, review-ready diff after adding the Spark 4.1 compatibility prerequisites.

## Linked Sources
- Pull request: microsoft#2664
- Prerequisite pull request: microsoft#2662

## Rationale
The compatibility parser explicitly tolerates an unterminated final record, but a newline-terminated text file avoids persistent diff annotations and works consistently with standard repository tooling.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
## Summary
Treat validation ingest and serving token mismatches as terminal protocol failures instead of expected client disconnects, and add deterministic classification and server-close regressions.

## Prompting Intent
Resolve the exact-head automated review finding on PR microsoft#2664 without changing normal cancellation, timeout, or socket-disconnect behavior.

## Linked Sources
- Issue: microsoft#2294
- Pull request: microsoft#2664
- Review finding: microsoft#2664 (comment)

## Rationale
EOF, socket closure, timeout, and shutdown interruption can be ordinary cancellation paths, but a token mismatch means the validation descriptor or client authentication is wrong. Recording that SecurityException as the terminal failure preserves the actionable cause for both ingest and serving instead of allowing a later timeout or EOF to mask it.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
## Summary
Run the validation scaling suite on its intended 128 KiB driver-result Spark session and restore a healthy shared TestBase session after the suite completes.

## Prompting Intent
Rebase PR microsoft#2664 and make it engineering-ready by fixing the exact-head Azure UnitTests lightgbm1 failure without weakening the bounded-driver-result regression or destabilizing later split1 suites.

## Linked Sources
- Issue: microsoft#2294
- Pull request: microsoft#2664
- Azure build: https://msdata.visualstudio.com/b9b2accc-2d1c-45b3-9d24-0eb5d78cc47f/_build/results?buildId=232332516

## Rationale
SparkSession.builder().getOrCreate() reused the already-active shared TestBase SparkContext, so stopping the suite provider also stopped the context cached for later suites. Stopping shared state before the specialized suite and explicitly restoring it afterward preserves test isolation in both directions. A live configuration assertion proves the regression actually runs under the intended result-size bound instead of silently reusing a permissive context.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
## Summary
Enumerate release-compatibility prerequisite paths with rename detection disabled and cover replay of a prerequisite rename in a scratch repository.

## Prompting Intent
Make PR microsoft#2664 pass the Spark 4.1 compatibility check by fixing the generic prerequisite replay defect exposed by its exact-head Azure build, while preserving all three required prerequisite commits.

## Linked Sources
- Issue: microsoft#2294
- Pull request: microsoft#2664
- Azure build: https://msdata.visualstudio.com/b9b2accc-2d1c-45b3-9d24-0eb5d78cc47f/_build/results?buildId=232332516

## Rationale
Default name-only diff output collapses a rename to its destination, which causes the later path-filtered patch to omit deletion of the source path. Disabling rename detection only during path enumeration emits both paths and lets the existing literal-path patch machinery preserve the complete change. This is simpler and less error-prone than parsing name-status records or special-casing rename pairs.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
## Summary`nUnwrap accept-loop execution failures before returning them to LightGBM callers, and restore the caller interrupt flag when waiting for ingest is interrupted.

## Prompting Intent`nDrive PR microsoft#2664 to merge readiness, address every current-head review finding, preserve original transport failures, and avoid false-green validation.

## Linked Sources`n- Pull request: https://github.com/microsoft/SynapseML/pull/2664`n- Review comment: https://github.com/microsoft/SynapseML/pull/2664#discussion_r3846851620`n- Tracking issue: microsoft#2294

## Rationale`nCentralized Java Future waiting in the existing socket-support utility so ExecutionException wrappers cannot hide the underlying IOException and interrupted callers retain cancellation state. Deterministic FutureTask regressions cover both paths without introducing network timing into the suite.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
## Summary

Fail fast when the validation spool cannot be listed or does not contain one canonical file for every Spark partition.

## Prompting Intent

Drive PR microsoft#2664 to merge readiness, audit suppressed current-head review feedback, and prevent missing validation partitions from becoming a partial or confusing downstream read.

## Linked Sources

- Pull request: microsoft#2664
- Suppressed review finding on head 0c328fd
- Tracking issue: microsoft#2294

## Rationale

Isolated spool validation in a focused package-private helper to keep ValidationDataServer below the repository file-length limit. Exact file-count and contiguous-index checks reject unreadable, incomplete, malformed, or non-file partition entries before ownership transfers to the serving phase.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@ranadeepsingh

Copy link
Copy Markdown
Collaborator Author

/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.

Review details

  • Files reviewed: 12/12 changed files
  • Comments generated: 1
  • Review effort level: Lite

## Summary
Reuse one fixed 64 KiB buffer per ingest thread instead of allocating a buffer for every validation row, and add a regression that observes buffer identity across row copies.

## Prompting Intent
Keep PR microsoft#2664 scalable after rebasing onto master, resolving the current-head review finding without changing the validation protocol, Fabric authentication path, or public API.

## Linked Sources
- GitHub issue microsoft#2294: microsoft#2294
- Pull request microsoft#2664: microsoft#2664
- Review comment: microsoft#2664 (comment)

## Rationale
A ThreadLocal confines mutable buffers to each bounded ingest executor thread, retaining at most one 64 KiB array per thread while avoiding synchronization and O(rows) allocations. Keeping row framing and the copy loop unchanged minimizes protocol risk; the regression verifies same-thread reuse and byte-for-byte copying.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@ranadeepsingh

Copy link
Copy Markdown
Collaborator Author

/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.

Review details

  • Files reviewed: 12/12 changed files
  • Comments generated: 1
  • Review effort level: Lite

## Summary
Use the existing fixed 64 KiB thread-local transfer buffer when serving spooled validation partitions, avoiding a new array allocation for every client transfer.

## Prompting Intent
Exhaust the current-head PR microsoft#2664 review after the ingest-buffer fix and remove the adjacent serving-side allocation without changing the wire protocol, Fabric authentication path, or public API.

## Linked Sources
- GitHub issue microsoft#2294: microsoft#2294
- Pull request microsoft#2664: microsoft#2664
- Review comment: microsoft#2664 (comment)

## Rationale
The serving executor is bounded and thread-confined, so reusing the same ThreadLocal buffer keeps memory bounded without synchronization. Sharing the already-reviewed buffer strategy between ingest and serving removes both avoidable allocation sites while leaving framing and byte-copy behavior unchanged.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@ranadeepsingh

Copy link
Copy Markdown
Collaborator Author

/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.

Review details

  • Files reviewed: 12/12 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

## Summary
Make the driver-local validation spool resilient to Spark task retries,
speculation, queued readers, stalled socket writes, and asynchronous cleanup
failures. Keep validation rows out of Spark task results, promote only
Spark-committed task attempts, and document Fabric's Python-versus-JVM
LightGBM version distinction.

## Prompting Intent
The engineer asked for PR microsoft#2664 to be rebased onto current master and made
merge-ready through the SynapseML PR loop. The implementation had to preserve
the existing Key Vault-backed Fabric credentials, avoid RDD APIs, retain exact
validation behavior, undergo a file-by-file necessity and security review,
pass Spark 3.5 and Spark 4.1 validation, and run as an actual Fabric E2E path.
They also asked that the branch skill record Fabric Runtime 1.3 Python
lightgbm 4.3.0 and Runtime 2.0 Python lightgbm 4.6.0 without incorrectly
changing the Maven/JNI dependency.

## Linked Sources
- GitHub issue: microsoft#2294
- Pull request: microsoft#2664
- Maven artifact: https://repo1.maven.org/maven2/com/microsoft/ml/lightgbm/lightgbmlib/3.3.510/

## Rationale
Dataset mapPartitions returns one tiny attempt descriptor per validation
partition, preserving DataFrame/Dataset execution while leaving row payloads
in bounded socket and disk streams. Per-attempt files are promoted only after
Spark reports the task attempt successful, because receipt of a complete TCP
payload does not prove task commit. A shared daemon write watchdog closes
stalled sockets without creating one timer thread per transfer, while delayed
read timeouts allow healthy clients to wait behind the bounded eight-transfer
limit. Null-indicator rejection is fused into the existing Catalyst filters to
avoid an extra full scan. The Maven dependency remains lightgbmlib 3.3.510
because managed Fabric's 4.3.0 and 4.6.0 values describe the Python package,
not the JVM/SWIG artifact.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@ranadeepsingh

Copy link
Copy Markdown
Collaborator Author

/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.

Review details

  • Files reviewed: 17/17 changed files
  • Comments generated: 2
  • Review effort level: Lite

## Summary
Cast the null-indicator `raise_error` expression to Boolean explicitly for
stable Spark type coercion and remove a tautological successful-attempt count
check.

## Prompting Intent
The engineer asked for PR microsoft#2664 to be fully reviewed and merge-ready with no
unintended regressions. The final-head automated review identified two concrete
cleanup findings that needed to be fixed and validated on both the Spark 3.5
and Spark 4.1 baselines rather than dismissed.

## Linked Sources
- GitHub issue: microsoft#2294
- Pull request: microsoft#2664
- Review comment: microsoft#2664 (comment)
- Review comment: microsoft#2664 (comment)

## Rationale
Spark 3.5 and Spark 4.1 currently infer the mixed `raise_error`/Boolean
expression correctly, but an explicit Boolean cast matches established Spark
patterns and protects analysis across versions. The successful partition count
is intentionally derived from `attempts.length`, so comparing those same
values added no validation; contiguous, missing, invalid, and duplicate
partition checks remain the authoritative invariants.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

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.

Review details

  • Files reviewed: 17/17 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

@ranadeepsingh

Copy link
Copy Markdown
Collaborator Author

/azp run

@azure-pipelines

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

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.

[BUG] OutOfMemorySparkException only when including a validationIndicatorCol - LightGBMClassifier

4 participants