Repository navigation
Fix flaky ONNX export test for wav2vec2-style ForPreTraining models (argmax near-tie) - #50575
Merged
Merged
Conversation
The codevector quantizer picks a code with a hard `argmax` in eval mode. On the random tiny models, two code logits can be within a few float32 ulps, so ONNX Runtime's rounding flips the choice and `codevector_perplexity` (a scalar) jumps far over the tolerance. Add `Wav2Vec2ConformerForPreTraining` and `UniSpeechSatForPreTraining` to `onnx.exactness` next to `Wav2Vec2ForPreTraining` and `UniSpeechForPreTraining`, and correct the reason (it is an argmax near-tie, not a Gumbel-softmax draw). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ydshieh
commented
Nov 10, 2026
Comment on lines
+241
to
+242
| "UniSpeechSatForPreTraining": "Same `argmax` near-tie as `Wav2Vec2ForPreTraining`.", | ||
| "Wav2Vec2ConformerForPreTraining": "Same `argmax` near-tie as `Wav2Vec2ForPreTraining`.", |
Collaborator
Author
There was a problem hiding this comment.
These last 2 are added in this PR.
|
The docs for this PR live here. All of your documentation changes will be reflected on that endpoint. The docs are available until 30 days after the last update. |
IlyasMoutawwakil
approved these changes
Nov 10, 2026
IlyasMoutawwakil
left a comment
Member
There was a problem hiding this comment.
yeh sure, i will later only skip the outputs where the near-tie problem arises and compare others like logits/projections.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What does this PR do?
Fixes the flaky
Wav2Vec2ConformerModelTest::test_onnx_export_dynamic(and the same risk for its siblings).In short:
Wav2Vec2ConformerForPreTraininghas a discrete value selection (anargmaxover 320 codes in its quantizer). When two candidates are almost tied, eager PyTorch and ONNX Runtime can pick different ones because of tiny float rounding differences. The flipped choice then changes the tensors computed from it, and the scalarcodevector_perplexityno longer matches. The ONNX export itself is correct.Fix (test-only): add
Wav2Vec2ConformerForPreTrainingandUniSpeechSatForPreTrainingtoEXPORT_SKIPS["onnx.exactness"], next toWav2Vec2ForPreTrainingandUniSpeechForPreTraining, which are already there for the same reason. These classes are still exported and run in ONNX Runtime; only the eager-vs-ONNX value comparison is dropped for them. The reason text is also corrected (see section 6).Failures (
Wav2Vec2ConformerModelTest::test_onnx_export_dynamic, CPU):mainDetails (only for the record)
1. What the test checks
test_onnx_export_{dynamic,static}(fromExportTesterMixinintests/exporters/test_export.py) does, for every class inall_model_classes:OnnxExporterand run the exported graph in ONNX Runtime on the same inputs;atol=rtol=1e-3, allowing up to 5% of the elements of a tensor to mismatch.For
Wav2Vec2ConformerForPreTraining(no labels / negative samples passed) there are 3 outputs:argmax?projected_statesprojected_quantized_statescodevector_perplexity2. The discrete value selection
In eval mode,
Wav2Vec2ConformerGumbelVectorQuantizer.forwarddoes (no Gumbel sampling in eval mode):With the test's tiny config this is 13 (batch) × 14 (frames) × 2 (groups) = 364
argmaxcalls per forward, each over 320 codes.The flow after the
argmax:codevector_perplexityis computed per group from how often each code is picked (the average of the one-hots over all frames):exp(entropy)of that distribution, summed over the 2 groups.3. Why a code flips
Eager PyTorch and ONNX Runtime compute the same logits with different kernels, so the logits differ by a few float32 rounding steps (ulps). Measured on CPU, eager vs ONNX Runtime, max absolute difference per run (median over 300 runs):
extract_features(quantizer input)This is normal float noise, and it is the same in passing and failing runs (section 5). A code flips only when, in one of the 364 rows, the top two logits are closer than this noise. With random weights and random inputs this happens by chance; with logits around 10, 1 ulp is 9.5e-7.
4. What one flip does to the outputs
One flipped row means one frame gets a completely different codevector. Example from a captured failure, each output compared on its own:
projected_statesprojected_quantized_statescodevector_perplexityOver the 15 captured failures, one flip moved the perplexity by 0.09 to 0.92 (0.11% to 1.0% of its value). The allowed difference is
1e-3 + 1e-3 * |expected|, i.e. about 0.08 to 0.15.5. Measurements
Done on CPU (
CUDA_VISIBLE_DEVICES=), torch 2.15.0, A10G runner, with a script that reproduces the test's model / inputs / export forWav2Vec2ConformerForPreTrainingand saves the eager and ONNX Runtime intermediate tensors (logits,argmax, one-hot, perplexity, …) of every run.On CI the test fails more often than on CPU. The daily CI shows new failures with
AssertionError: Scalars are not close!on single GPU (2026-10-23, 10-26, 11-03, 11-08) and multi GPU (2026-10-16, 10-22, 11-05, 11-08), and the test was P / F / P in 3 runs on a GPU runner. The GPU rate was not measured separately.6. Why this fix
onnx.exactnessis the existing mechanism for "the export works but its outputs can't be compared to eager".Wav2Vec2ForPreTrainingandUniSpeechForPreTrainingalready use it for this exact quantizer;Wav2Vec2ConformerForPreTrainingandUniSpeechSatForPreTraininguse the same quantizer and were missing.UniSpeechSatForPreTraininghas not failed on CI yet, but it is inUniSpeechSatModelTest.all_model_classesand has the sameargmax, so it is added too.Model,ForCTC,ForSequenceClassification, …) keep the full value comparison.test_onnx_export_dynamicandtest_onnx_export_static; both have the sameargmax.rtolof about 1e-2 (observed) to 3e-2 (rough worst case), applied to every output of every class of the model.@is_flakywould keep the comparison, but needs a model-file override of theparameterized-generated test, and reruns a ~50 s test on each failure.argmaxnear-tie. The reason is updated, and the 3 other entries point to it.The same
argmaxinstability is already documented in these model test files:test_batching_equivalenceis marked@is_flakyin wav2vec2, wav2vec2_conformer and unispeech, with the reason "Thecodevector_idxcomputed withargmax()… is not stable."7. Verification
CPU (
CUDA_VISIBLE_DEVICES=),RUN_SLOW=1, torch 2.15.0, onnxruntime 1.31.1, onnxscript 0.7.2.Replay of captured failures through the real test. A failure happens about once in 2,700 CPU runs, so a before/after flakefinder run can't show the fix. Instead, a local pytest plugin (not part of this PR) seeds
torchand the testers'global_rngwith a seed captured from a failing run, right before the mixin buildsWav2Vec2ConformerForPreTraining. This recreates the exact failing model and inputs. All 15 failing seeds from section 5:maincodevector_perplexity(e.g. seed 1371433937: expected 92.1406, got 91.2174)Normal runs with this PR (no seeding):
test_onnx_export_{dynamic,static}for wav2vec2, wav2vec2_conformer, unispeech and unispeech_sat (all test classes): 12 passed.Note
The test reports
codevector_perplexity(notprojected_quantized_states) because_check_outputs_closepasses the whole output dict to onetorch.testing.assert_close. That call checks the keys in sorted order and raises at the first mismatch, and the 5% allowance is applied only to that first error. If a tolerated mismatch came first, the outputs after it would not be compared at all. This doesn't change anything for this PR, but it could hide real mismatches in other models; it could be fixed separately by comparing output by output.🤖 Generated with Claude Code