Skip to content

Keep infinities in clamp with one bound - #3089

Open
Muhammad Usman Mateen (usmanmateen) wants to merge 1 commit into
microsoft:mainfrom
usmanmateen:fix/clamp-one-sided-inf
Open

Muhammad Usman Mateen (usmanmateen) wants to merge 1 commit into
microsoft:mainfrom
usmanmateen:fix/clamp-one-sided-inf

Conversation

@usmanmateen

Copy link
Copy Markdown

aten_clamp (with only min or only max), aten_clamp_min, aten_clamp_max and the 0-d tensor branches of aten_clamp_min_tensor / aten_clamp_max_tensor leave the other Clip bound empty. The Clip spec says the bounds default to numeric_limits::lowest() and numeric_limits::max(), and ONNX Runtime does exactly that. So for floating-point inputs an infinity is clipped to the largest finite value:

import math

import torch


class Model(torch.nn.Module):
    def forward(self, x):
        return x.clamp(min=1e-6), x.clamp(max=0.0)


x = torch.tensor([math.inf, -math.inf, 2.0, -3.0])
onnx_program = torch.onnx.export(Model(), (x,), dynamo=True)
print(Model()(x))
# (tensor([   inf, 1.0000e-06, 2.0000e+00, 1.0000e-06]), tensor([ 0., -inf,  0., -3.]))
print(onnx_program(x))
# (tensor([3.4028e+38, 1.0000e-06, 2.0000e+00, 1.0000e-06]), tensor([ 0.0000e+00, -3.4028e+38,  0.0000e+00, -3.0000e+00]))

After that, torch.isinf no longer sees the value, and something like 1 / x gives 2.9e-39 instead of 0.

This adds a small _clip helper that passes -inf / inf for the missing bound when the input is floating point, and uses it in those five places. Integer inputs keep the omitted bound, since there the type's limits are the right default. NaN still propagates, as before.

Tests

test_clamp_with_one_bound_keeps_infinities in tests/function_libs/torch_lib/e2e_ops_tests.py exports clamp with only min and only max, clamp_min and clamp_max with a scalar, and clamp_min and clamp_max with a 0-d tensor bound. The input contains inf, -inf and nan, and the test checks the outputs against eager. It fails on main (inf vs 3.4028e+38 in the first output) and passes with this change.

  • python -m pytest tests/function_libs/torch_lib/e2e_ops_tests.py -k "clamp or clip or hardtanh or relu6": 2 passed
  • python -m pytest tests/function_libs/torch_lib/ops_test.py -k "clamp or hardtanh or relu6 or clip": 21 passed, 14 skipped (124 subtests passed)
  • ruff check and ruff format --check (0.15.8) on the changed files

Tested with torch 2.14.1+cpu and onnxruntime 1.31.0.

clamp, clamp_min and clamp_max with only one bound left the other Clip
input empty. ONNX Runtime uses the lowest or largest finite value of the
type for an omitted bound, as the Clip spec describes, so x.clamp(min=0.5)
turned inf into 3.4e38 and x.clamp(max=0.5) turned -inf into -3.4e38.
Pass -inf or inf for the missing bound of floating-point inputs instead.

This branch has not been deployed

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

1 participant