pytorch - ✅(Solved) Fix Custom operator python API error when marking an optional argument as mutated [1 pull requests, 1 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
pytorch/pytorch#180618Fetched 2026-04-17 08:26:00
View on GitHub
Comments
0
Participants
1
Timeline
20
Reactions
0
Author
Participants
Timeline (top)
labeled ×6mentioned ×5subscribed ×5renamed ×3

Error Message

With out: OK Traceback (most recent call last): File "/data/users/xmfan/gb200_moe_sol/tests/repro_custom_op_optional_mutate.py", line 25, in <module> result = foo(x) ^^^^^^ File "/home/xmfan/miniconda3/envs/gb200_moe_sol/lib/python3.12/site-packages/torch/_library/custom_ops.py", line 726, in call return self._opoverload(*args, **kwargs) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/home/xmfan/miniconda3/envs/gb200_moe_sol/lib/python3.12/site-packages/torch/_ops.py", line 871, in call return self._op(*args, **kwargs) ^^^^^^^^^^^^^^^^^^^^^^^^^ File "/home/xmfan/miniconda3/envs/gb200_moe_sol/lib/python3.12/site-packages/torch/_library/autograd.py", line 112, in autograd_impl result = forward_no_grad(*args, Metadata(keyset, keyword_only_args)) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/home/xmfan/miniconda3/envs/gb200_moe_sol/lib/python3.12/site-packages/torch/_library/autograd.py", line 41, in forward_no_grad result = op.redispatch(keyset & _C._after_autograd_keyset, *args, **kwargs) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/home/xmfan/miniconda3/envs/gb200_moe_sol/lib/python3.12/site-packages/torch/_ops.py", line 878, in redispatch return self._handle.redispatch_boxed(keyset, *args, **kwargs) # type: ignore[return-value] ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/home/xmfan/miniconda3/envs/gb200_moe_sol/lib/python3.12/site-packages/torch/_library/custom_ops.py", line 686, in adinplaceorview_impl increment_version(args[idx]) ~~~~^^^^^ IndexError: tuple index out of range

Fix Action

Fix / Workaround

Works if you register via Library.define as Tensor(a!)?, python API error message:

With out: OK
Traceback (most recent call last):
  File "/data/users/xmfan/gb200_moe_sol/tests/repro_custom_op_optional_mutate.py", line 25, in <module>
    result = foo(x)
             ^^^^^^
  File "/home/xmfan/miniconda3/envs/gb200_moe_sol/lib/python3.12/site-packages/torch/_library/custom_ops.py", line 726, in __call__
    return self._opoverload(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/xmfan/miniconda3/envs/gb200_moe_sol/lib/python3.12/site-packages/torch/_ops.py", line 871, in __call__
    return self._op(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/xmfan/miniconda3/envs/gb200_moe_sol/lib/python3.12/site-packages/torch/_library/autograd.py", line 112, in autograd_impl
    result = forward_no_grad(*args, Metadata(keyset, keyword_only_args))
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/xmfan/miniconda3/envs/gb200_moe_sol/lib/python3.12/site-packages/torch/_library/autograd.py", line 41, in forward_no_grad
    result = op.redispatch(keyset & _C._after_autograd_keyset, *args, **kwargs)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/xmfan/miniconda3/envs/gb200_moe_sol/lib/python3.12/site-packages/torch/_ops.py", line 878, in redispatch
    return self._handle.redispatch_boxed(keyset, *args, **kwargs)  # type: ignore[return-value]
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/xmfan/miniconda3/envs/gb200_moe_sol/lib/python3.12/site-packages/torch/_library/custom_ops.py", line 686, in adinplaceorview_impl
    increment_version(args[idx])
                      ~~~~^^^^^
IndexError: tuple index out of range

PR fix notes

PR #180621: custom_op: handle omitted optional mutated defaults

Description (problem / solution / changelog)

Summary

Fix a crash when torch.library.custom_op marks an optional argument as mutated and the caller omits that argument so it falls back to its default.

Root cause problem

CustomOpDef's ADInplaceOrView wrapper computes mutated positional and keyword argument locations from the schema, then indexes directly into the runtime args and kwargs. The dispatcher strips default-valued arguments before this Python wrapper runs, so an omitted out: Optional[Tensor] = None is not present in args, which causes IndexError: tuple index out of range during version bump bookkeeping.

Proposed fix

Call utils.fill_defaults(schema, args, kwargs) inside the ADInplaceOrView wrapper before incrementing versions for mutated arguments. This materializes omitted default values only for the bookkeeping step, while leaving the original args and kwargs untouched for the actual kernel dispatch.

Also add a regression test covering a custom op with mutates_args={"out"} and out: Optional[Tensor] = None, verifying that the omitted-argument call succeeds and that the explicit out path still bumps the version counter.

Why this is the right long term fix

fill_defaults is already the helper PyTorch uses when Python-side custom op wrappers need schema-aligned inputs. Reusing it keeps the ADInplaceOrView bookkeeping consistent with dispatcher semantics for default arguments and fixes both positional and keyword-only mutated defaults without changing dispatch behavior.

Testing

  • Reproduced the crash against torch-2.13.0.dev20260416+cpu nightly: IndexError: tuple index out of range
  • Ran TestCustomOpAPI.test_mutated_optional_arg_default_none from test/test_custom_ops.py against the patched torch._library.custom_ops on the same nightly CPU wheel

Fix #180618

Drafted via Codex, published after manual review by @bobrenjc93

Changed files

  • test/test_custom_ops.py (modified, +21/-0)
  • torch/_library/custom_ops.py (modified, +3/-2)

Code Example

With out: OK
Traceback (most recent call last):
  File "/data/users/xmfan/gb200_moe_sol/tests/repro_custom_op_optional_mutate.py", line 25, in <module>
    result = foo(x)
             ^^^^^^
  File "/home/xmfan/miniconda3/envs/gb200_moe_sol/lib/python3.12/site-packages/torch/_library/custom_ops.py", line 726, in __call__
    return self._opoverload(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/xmfan/miniconda3/envs/gb200_moe_sol/lib/python3.12/site-packages/torch/_ops.py", line 871, in __call__
    return self._op(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/xmfan/miniconda3/envs/gb200_moe_sol/lib/python3.12/site-packages/torch/_library/autograd.py", line 112, in autograd_impl
    result = forward_no_grad(*args, Metadata(keyset, keyword_only_args))
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/xmfan/miniconda3/envs/gb200_moe_sol/lib/python3.12/site-packages/torch/_library/autograd.py", line 41, in forward_no_grad
    result = op.redispatch(keyset & _C._after_autograd_keyset, *args, **kwargs)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/xmfan/miniconda3/envs/gb200_moe_sol/lib/python3.12/site-packages/torch/_ops.py", line 878, in redispatch
    return self._handle.redispatch_boxed(keyset, *args, **kwargs)  # type: ignore[return-value]
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/xmfan/miniconda3/envs/gb200_moe_sol/lib/python3.12/site-packages/torch/_library/custom_ops.py", line 686, in adinplaceorview_impl
    increment_version(args[idx])
                      ~~~~^^^^^
IndexError: tuple index out of range

---

"""Repro: @custom_op crashes when a mutated arg is optional and not passed.

IndexError in adinplaceorview_impl when calling an op with
mutates_args=("out",) where out is Tensor | None and defaults to None.
"""
import torch


@torch.library.custom_op("mylib::foo", mutates_args=("out",))
def foo(x: torch.Tensor, out: torch.Tensor | None = None) -> torch.Tensor:
    if out is not None:
        out.copy_(x)
        return x.new_empty(0)  # don't alias out
    return x.clone()


x = torch.randn(4, device="cuda")

# Works: out is provided
out = torch.empty(4, device="cuda")
result = foo(x, out=out)
print("With out: OK")

# Crashes: out defaults to None
result = foo(x)
print("Without out: OK")
RAW_BUFFERClick to expand / collapse

🐛 Describe the bug

Works if you register via Library.define as Tensor(a!)?, python API error message:

With out: OK
Traceback (most recent call last):
  File "/data/users/xmfan/gb200_moe_sol/tests/repro_custom_op_optional_mutate.py", line 25, in <module>
    result = foo(x)
             ^^^^^^
  File "/home/xmfan/miniconda3/envs/gb200_moe_sol/lib/python3.12/site-packages/torch/_library/custom_ops.py", line 726, in __call__
    return self._opoverload(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/xmfan/miniconda3/envs/gb200_moe_sol/lib/python3.12/site-packages/torch/_ops.py", line 871, in __call__
    return self._op(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/xmfan/miniconda3/envs/gb200_moe_sol/lib/python3.12/site-packages/torch/_library/autograd.py", line 112, in autograd_impl
    result = forward_no_grad(*args, Metadata(keyset, keyword_only_args))
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/xmfan/miniconda3/envs/gb200_moe_sol/lib/python3.12/site-packages/torch/_library/autograd.py", line 41, in forward_no_grad
    result = op.redispatch(keyset & _C._after_autograd_keyset, *args, **kwargs)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/xmfan/miniconda3/envs/gb200_moe_sol/lib/python3.12/site-packages/torch/_ops.py", line 878, in redispatch
    return self._handle.redispatch_boxed(keyset, *args, **kwargs)  # type: ignore[return-value]
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/xmfan/miniconda3/envs/gb200_moe_sol/lib/python3.12/site-packages/torch/_library/custom_ops.py", line 686, in adinplaceorview_impl
    increment_version(args[idx])
                      ~~~~^^^^^
IndexError: tuple index out of range
"""Repro: @custom_op crashes when a mutated arg is optional and not passed.

IndexError in adinplaceorview_impl when calling an op with
mutates_args=("out",) where out is Tensor | None and defaults to None.
"""
import torch


@torch.library.custom_op("mylib::foo", mutates_args=("out",))
def foo(x: torch.Tensor, out: torch.Tensor | None = None) -> torch.Tensor:
    if out is not None:
        out.copy_(x)
        return x.new_empty(0)  # don't alias out
    return x.clone()


x = torch.randn(4, device="cuda")

# Works: out is provided
out = torch.empty(4, device="cuda")
result = foo(x, out=out)
print("With out: OK")

# Crashes: out defaults to None
result = foo(x)
print("Without out: OK")

Error logs

No response

Versions

main

cc @chauhang @penguinwu @bdhirsh @bobrenjc93 @aorenste

extent analysis

TL;DR

The most likely fix is to modify the foo function to handle the case when out is None without attempting to access it as if it were a tuple element.

Guidance

  • Verify that the error occurs when the out parameter is not provided (i.e., defaults to None).
  • Check the mutates_args parameter of the @torch.library.custom_op decorator to ensure it correctly handles optional arguments.
  • Consider modifying the foo function to explicitly handle the case when out is None, potentially by adding a conditional statement to avoid attempting to access out as if it were a tuple element.
  • Review the PyTorch documentation for custom ops to ensure that the implementation aligns with the expected behavior for optional arguments.

Example

@torch.library.custom_op("mylib::foo", mutates_args=("out",))
def foo(x: torch.Tensor, out: torch.Tensor | None = None) -> torch.Tensor:
    if out is not None:
        out.copy_(x)
        return x.new_empty(0)  # don't alias out
    else:
        # Handle the case when out is None without attempting to access it as a tuple element
        return x.clone()

Notes

The provided code snippet and error message suggest that the issue is related to the handling of optional arguments in custom PyTorch ops. However, without further information about the intended behavior of the foo function, it is difficult to provide a more specific solution.

Recommendation

Apply a workaround by modifying the foo function to explicitly handle the case when out is None, as shown in the example above. This should prevent the IndexError from occurring when the out parameter is not provided.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING

pytorch - ✅(Solved) Fix Custom operator python API error when marking an optional argument as mutated [1 pull requests, 1 participants]