Skip to content

fix: preserve VW Generic input schemas - #2821

Merged
Rana Singh (ranadeepsingh) merged 2 commits into
microsoft:masterfrom
leninworld:fix-vw-generic-schema-issue-2817
Nov 9, 2026
Merged

Rana Singh (ranadeepsingh) merged 2 commits into
microsoft:masterfrom
leninworld:fix-vw-generic-schema-issue-2817

Conversation

@leninworld

@leninworld Lenin Mookiah (leninworld) commented Nov 9, 2026 •

Copy link
Copy Markdown
Contributor

VW Generic predictions could fail or misalign output fields when input data included IDs, labels, or other columns alongside the VW example. This change preserves the complete input schema and values, including nested metadata needed by downstream Spark ML stages, and appends the existing flat prediction fields.

Related Issues/PRs

Fixes #2817. The source-inspection finding is now backed by public Spark regressions.

What changes are proposed in this pull request?

The contributor's fix keeps every input column in its original order, with its datatype, nullability and metadata. It rejects non-string example columns and names that collide with prediction fields, using the dataset session's Spark case-sensitivity setting.

The additive maintainer follow-up restores nested field metadata that Spark's row encoder drops. A cast to the original datatype, followed by the existing metadata alias, preserves structs, arrays and maps without rebuilding their values. New regressions cover nested nullability and values, plus categorical metadata used by IndexToString.

Single-column prediction values and flat prediction-column names are unchanged. Inputs named like prediction fields now receive an actionable error instead of ambiguous duplicate columns. Input nullability follows the original schema. Public JVM signatures, serialized parameters and dependencies are unchanged.

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 follow-up validation uses JDK 11, Spark 3.5.0, Scala 2.12.17 and VW JNI 9.3.0 in local WSL Spark:

  • Before the cast repair, sbt vw/Test/scalastyle "vw/testOnly com.microsoft.azure.synapse.ml.vw.VerifyVowpalWabbitGeneric -- -z nested": style passed; both new regressions failed as intended. The nested schema lost child metadata, and downstream IndexToString threw ClassCastException.
  • sbt scalastyle Test/scalastyle compile Test/compile getDatasets vw/test core/codegen vw/codegen: passed. All 118 VW tests passed across 15 suites, with zero failures, cancellations or skips. Local XML reports confirm both nested regressions and the DSJSON/coordinated-learning cases executed.
  • black --check --extend-exclude 'docs/' . with Black 22.3.0: passed, 216 files unchanged.
  • python3 "$validationScript" "$worktree" using the session-local pr2821-python-validation.py harness and generated core/VW wrappers: passed all six scalar/array x original/copy/reload cases. Checks cover schema and value preservation, nested categorical decoding, prediction parity and verified JVM class provenance from the fixed worktree. The harness is local evidence, not an installed-release or hosted-CI result.
  • Azure build 245670108: succeeded, triggered by /azp run with reason=pullRequest. Its merge commit dbf9b83148d3ce43763398d2dcecfb9b70345f68 contains follow-up head d24b27872c2b8bf8be0c8d3715b4ec59894e0954 and target 6c2699defcfb24d5bff5ba55f2252ba2d8cbea24. All 118 Scala VW tests passed with no skips; Generic suite results explicitly include both new nested-metadata regressions. All 23 Python VW tests passed with no skips, and the VW R job passed.
  • All 65 Azure jobs completed: 64 succeeded, and AI Foundry succeeded with cache-only warnings while publishing eight passing tests. Databricks CPU/GPU E2E and the other matrix jobs finished successfully. The first UnitTests form attempt failed during Azure CLI authentication, before tests, because the certificate returned for msdata.visualstudio.com had only *.azureedge.net as its alternate name. Retrying only that failed job on the same source revision succeeded: 53 tests passed, with two existing explicit ignore cases in ListCustomModelsSuite reported as not executed. The successful jobs were not rerun, and no TLS checks or CI policies were weakened.
  • Current-head GitHub Dependency Review, PR Validation, dead-link, semantic-title and CLA checks passed. Both CodeQL language-analysis jobs passed, but the aggregate CodeQL check is neutral: it cannot compare alerts because the target branch has an unmatched API upload / <default> scan configuration. This is a scanning-coverage limitation, not a clean aggregate scan. The current-head automated review recommends approval with no open or suppressed findings; maintainer approval and contributor sign-off remain outstanding.

Contributor validation, before the nested-metadata follow-up: hosted Docker run passed root style/compilation, both original regressions, all 116 VW tests without skips, PipelineTestCoverageSuite, six-module codegen, core/VW Python package installation and publishM2. Installed-Python checks covered scalar/array predictions, custom input, copy/save/load and collision handling. Its validation-branch SHA differs from the original PR head, but the tested candidate patch was independently verified to match that PR diff. It does not validate the maintainer follow-up.

No Fabric or native Windows runtime validation is claimed.

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

Keep all input fields and append the native prediction schema, restoring
top-level metadata after row encoding. Reject prediction-name collisions
using the Spark session case-sensitivity setting. Cover schema ordering,
scalar and array predictions, custom inputs and collision behavior.
Copilot AI balanced review requested due to automatic review settings November 9, 2026 10:51
@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

github-actions Bot commented Nov 9, 2026

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

Rebuilding nested structs drops child-field metadata, including within arrays and maps, causing output schemas to differ from transformSchema and requiring a metadata-preserving fix with full-schema regression coverage.

1 open issue
What changed in this PR

Addresses #2817 by aligning VowpalWabbitGenericModel’s declared output schema with its emitted rows, preserving input columns before appending flat prediction fields.

Changes:

  • Preserve input-column order, types and nullability, and restore top-level metadata after encoding.
  • Reject non-string example columns and prediction-name collisions using Spark’s case-sensitivity setting.
  • Add regressions for scalar and array predictions, reordered and custom inputs, metadata, and collision handling.
File Description
vw/​src/​test/​scala/​com/​microsoft/​azure/​synapse/​ml/​vw/​VerifyVowpalWabbitGeneric.scala Adds schema-preservation and collision regressions, plus multi-column fuzzing coverage.
vw/​src/​main/​scala/​com/​microsoft/​azure/​synapse/​ml/​vw/​VowpalWabbitGeneric.scala Preserves input schemas, restores top-level metadata, and validates input types and prediction-name conflicts.

Review effort: Balanced

## Summary
Restore each output column's declared datatype after Spark row encoding so
nested field metadata survives VowpalWabbitGenericModel.transform. Add public
Spark regressions for nested schemas and categorical label decoding.

## Prompting Intent
Apply the independently reproduced nested-metadata correction to the existing
external contribution, preserve the contributor's implementation and history,
and validate the improvement through the PR and external-contributor loops.

## Linked Sources
- Original report: microsoft#2817
- Contributor PR: microsoft#2821
- Review finding: microsoft#2821 (comment)
- Spark row encoder: https://github.com/apache/spark/blob/v3.5.0/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/encoders/ExpressionEncoder.scala
- Spark serializer: https://github.com/apache/spark/blob/v3.5.0/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/SerializerBuildHelper.scala
- Spark casts: https://github.com/apache/spark/blob/v3.5.0/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/expressions/Cast.scala
- Label decoder: https://github.com/apache/spark/blob/v3.5.0/mllib/src/main/scala/org/apache/spark/ml/feature/StringIndexer.scala

## Rationale
Top-level aliases cannot recover metadata on nested struct fields erased by
ExpressionEncoder. Casting to the original datatype restores that schema while
retaining the existing top-level metadata alias. Spark treats structurally
identical casts as representation-preserving, avoiding recursive reconstruction,
new native calls, or shuffles. Public tests check structs, arrays, map keys and
values, nullability, collected values, and downstream IndexToString behavior.
No public signatures, parameter formats, dependency pins, or CI files change.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: ed340ce9-385a-41e0-86c4-fe0f09097478
Copilot AI balanced review requested due to automatic review settings November 9, 2026 12:43
@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.

🟢 Approval recommended

No active review issues are reported for the change preserving input schemas and values while retaining existing prediction fields.

No open issues 🎉

1 resolved since last review

Review effort: Balanced

@ranadeepsingh

Copy link
Copy Markdown
Collaborator

Thanks Lenin Mookiah (@leninworld) for fixing VW Generic's handling of inputs with multiple columns!

I kept your schema-preservation approach and pushed the additive follow-up d24b27872c. It restores nested metadata that Spark's row encoder drops and adds public regressions for nested schema/value preservation and categorical decoding through IndexToString. Both tests failed for the intended reasons before the correction and now pass. The PR description includes the implementation and validation details.

Azure build 245670108, triggered by /azp run, succeeded for this head merged with the current master. All 118 Scala VW tests and 23 Python VW tests passed without skips. The published Generic suite confirms both new regressions passed. Only an unrelated TLS startup failure needed a targeted job retry; that retry passed without code or CI-policy changes.

The current-head automated review has no open or suppressed findings. GitHub workflow jobs passed; CodeQL's aggregate comparison remains neutral because of an unmatched target-branch scan configuration, as documented in the description.

Please review the follow-up and sign off if it fits your intent. If it does not fit your vision for the PR, feel free to revert my commit, or let me know and I will revert it. CLA validation has passed; your sign-off and maintainer approval remain outstanding.

@leninworld

Copy link
Copy Markdown
Contributor Author

Thanks Lenin Mookiah (@leninworld) for fixing VW Generic's handling of inputs with multiple columns!

I kept your schema-preservation approach and pushed the additive follow-up d24b27872c. It restores nested metadata that Spark's row encoder drops and adds public regressions for nested schema/value preservation and categorical decoding through IndexToString. Both tests failed for the intended reasons before the correction and now pass. The PR description includes the implementation and validation details.

Azure build 245670108, triggered by /azp run, succeeded for this head merged with the current master. All 118 Scala VW tests and 23 Python VW tests passed without skips. The published Generic suite confirms both new regressions passed. Only an unrelated TLS startup failure needed a targeted job retry; that retry passed without code or CI-policy changes.

The current-head automated review has no open or suppressed findings. GitHub workflow jobs passed; CodeQL's aggregate comparison remains neutral because of an unmatched target-branch scan configuration, as documented in the description.

Please review the follow-up and sign off if it fits your intent. If it does not fit your vision for the PR, feel free to revert my commit, or let me know and I will revert it. CLA validation has passed; your sign-off and maintainer approval remain outstanding.

LGTM

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG] Potential multi-column schema mismatch in VowpalWabbitGenericModel

4 participants