Skip to content

fix(nx): propagate NaN through clip on BinaryBackend - #1855

Merged
polvalente merged 1 commit into
elixir-nx:mainfrom
blasphemetheus:fix/clip-nan
Oct 7, 2026
Merged

polvalente merged 1 commit into
elixir-nx:mainfrom
blasphemetheus:fix/clip-nan

Conversation

@blasphemetheus

@blasphemetheus blasphemetheus commented Oct 7, 2026 •

Copy link
Copy Markdown
Contributor

Nx.clip on BinaryBackend returns a different thing for NaN depending on which argument carries it:

iex> Nx.clip(Nx.tensor(:nan), 0.0, 2.0)
#Nx.Tensor<f32 0.0>
iex> Nx.clip(Nx.tensor(1.0), Nx.Constants.nan(), 2.0)
#Nx.Tensor<f32 2.0>
iex> Nx.clip(Nx.tensor(1.0), 0.0, Nx.Constants.nan())
#Nx.Tensor<f32 NaN>

The first case is the harmful one: a NaN input comes out as an in-range value, so a clip used as a guard hides the NaN instead of surfacing it. EXLA returns NaN for all three, which matches min(max(x, lo), hi) with Nx's own NaN-propagating min and max.

The cause is the two-comparison chain in BinaryBackend.clip: every comparison against NaN is false, so each position falls through to a different arm. This replaces the chain with the backend's existing element_max and element_min, which already handle NaN and infinities, so both backends compute the same composition.

The new test covers NaN in each argument position and fails on main with the values above. The existing clip tests, including the infinities case, still pass, and so does the full nx suite. (claude) also checked BinaryBackend against the EXLA CUDA client on the three NaN cases plus ordinary float and integer inputs, and they agree on all of them.

🤖 Generated with Claude Code

Nx.clip on BinaryBackend picked its result with two comparisons, and
every comparison against NaN is false, so each argument position fell
through to a different arm: clip(NaN, 0, 2) returned 0.0, clip(1, NaN, 2)
returned 2.0, and only clip(1, 0, NaN) returned NaN. EXLA computes clip
as min(max(x, lo), hi) and returns NaN for all three.

Use the backend's own element_max and element_min, which already
propagate NaN and handle infinities, so clip is the same composition
on both backends.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@blasphemetheus
blasphemetheus marked this pull request as ready for review October 7, 2026 07:12
@polvalente
polvalente merged commit c8f466e into elixir-nx:main Oct 7, 2026
9 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.

2 participants