Skip to content

Fix numerical stability of TorchLib erfcx - #3038

Open
Yusef Syed (YusefSyed) wants to merge 1 commit into
microsoft:mainfrom
YusefSyed:codex/stable-erfcx
Open

Fix numerical stability of TorchLib erfcx#3038
Yusef Syed (YusefSyed) wants to merge 1 commit into
microsoft:mainfrom
YusefSyed:codex/stable-erfcx

Conversation

@YusefSyed

Copy link
Copy Markdown

Fixes #1223.

The current exp(x*x) * (1 - erf(x)) decomposition loses the small erf complement for positive inputs and can combine overflow with underflow. For example, the existing OpInfo inputs around 6.8 return zero instead of approximately 0.08. This change uses piecewise Cephes rational approximations, a reciprocal positive tail, and the negative-input reflection formula. It promotes half inputs for the calculation, preserves float64 constants, and masks inactive approximation inputs because ONNX Where evaluates both branches. Exported graphs need neither Erf nor Loop.

The existing erfcx xfail is removed without relaxing its tolerance. New tests cover interval boundaries, tails, special values, dtypes, shapes and empty inputs. The coefficient provenance and BSD notice are included in both wheel and source distributions.

Validation:

  • Focused regression and existing OpInfo tests: 20 passed, 4 expected skips; rerun immediately before submission.
  • 291,148 actual ONNX Runtime output comparisons, with graph optimizations disabled and enabled, including every float16 bit pattern and dense float32/float64 inputs.
  • Independent SciPy checks and 180 high-precision mpmath reference values.
  • Actual torch.onnx.export and ONNX Runtime execution passed for float32 and float64; lint, formatting, wheel and source-distribution checks passed.
.venv/bin/python -m pytest tests/function_libs/torch_lib/special_test.py tests/function_libs/torch_lib/ops_test.py -k erfcx -q

The runtime validation was on macOS arm64. BFloat16 dispatch is included but was not executed in these runtime checks; no GPU or throughput claim is made.

AI disclosure: OpenAI Codex assisted with the implementation, regression tests, validation tooling and this PR description. The numerical coefficients are adapted from the attributed SciPy/XSF source, not presented as newly derived approximations.


_MATH_PI = math.pi

# Coefficients adapted from SciPy XSF's Cephes ``ndtr.h`` (pinned source revision

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thanks. Is there a version that comes from pytorch?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Codex-assisted response: yes. PyTorch has Steven G. Johnson's erfcx_y100 / calc_erfcx implementation. It uses a 100-region polynomial table for the central positive range and a continued-fraction tail. The CPU kernel calls it; CUDA uses either the corresponding Jiterator implementation or the same helper.

Those are different coefficients from the Cephes rational approximation used here. A PyTorch-sourced ONNX translation is feasible using coefficient lookup and Horner evaluation, but it would replace this approximation with the 700-coefficient table and its tail rules. I chose the current 42-coefficient rational form to avoid that lookup table in the export graph. Its SciPy provenance is explicit in the source/notice, and the focused erfcx tests still pass against PyTorch (20 passed, four expected skips). No performance comparison between the two ONNX representations has been established.

@codecov

codecov Bot commented Sep 9, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 72.73%. Comparing base (d1c005d) to head (c679e0c).
⚠️ Report is 1 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #3038      +/-   ##
==========================================
+ Coverage   72.70%   72.73%   +0.03%     
==========================================
  Files         265      265              
  Lines       32298    32345      +47     
  Branches     3059     3063       +4     
==========================================
+ Hits        23481    23525      +44     
- Misses       7779     7782       +3     
  Partials     1038     1038              

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

@YusefSyed

Copy link
Copy Markdown
Author

Codex-assisted CI follow-up for the unchanged head c679e0c:

  • The torch-nightly addbmm/baddbmm/logit/grouped-mm failures also occur in the corresponding base CI run.
  • Optional Lint fails during Docker apt-get update on expired Debian bullseye-security metadata, before running lint; the base Optional Lint run also fails.
  • The Ubuntu py310 job passed its first pytest phase, then received a runner shutdown signal during the second phase. Its log does not show a test assertion causing that cancellation.
  • The Windows onnx-ir-git conv2d failures did not reproduce in this controlled Windows recheck: parent d1c005d and head c679e0c ran on the same machine, dependency environment and seed. Both passed the float16 conv2d checks. The head also passed its erfcx OpInfo checks and all 19 dedicated erfcx regressions.

The comparison artifacts include source/import identity, dependency freeze, JUnit and logs. It pins ONNX IR to 641d90447cac737fabd43d94ea8c1796bde3e3b4; the original job reported version 1.1.0 without exposing its installed Git SHA, so this is not a claim to reproduce that entire environment exactly. No conv code or test tolerance was changed. The original Windows job still needs an upstream rerun to establish its current status; I do not have permission to rerun that upstream job.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Development

Successfully merging this pull request may close these issues.

[torchlib] Fix special.erfcx

2 participants