pytorch / pytorch/pytorch

__torch_function__ objects are rejected in the leading position of a var-args int-list argument

Open
#191,275 1 comment 0 reactions 0 assignees View on GitHub
bot-triaged module: __torch_function__ triaged
Dominant language
Python
Stars
103k
Forks
29.6k
PR merge metrics
PR metrics pending

Description

### 🐛 Describe the bug

Encountered the following bug; Claude did the following detective work/presentation.

------------------------------------

# `__torch_function__` objects are rejected in the leading position of a var-args int-list argument (`t.expand(B, 4)` fails, `t.expand((B, 4))` works)

## 🐛 Describe the bug

When a `__torch_function__` object — `torch.fx.Proxy` being the common case — appears as the
**first** element of a var-args int-list argument, the argument parser raises `TypeError`
instead of dispatching to `__torch_function__`. The packed form of the same call, and the same
object in any non-leading position, both dispatch correctly.

This makes `fx.symbolic_trace` fail on the very common pattern of broadcasting a captured
concrete tensor up to the traced batch size:

```python
import torch
import torch.fx as fx

CONST = torch.zeros(1, 4)

def f(x):
return CONST.expand(x.shape[0], *CONST.shape[1:]) # symbolic dim leads

fx.symbolic_trace(f)
```

```
TypeError: expand() takes 1 positional argument but 2 were given
```

Wrapping the sizes in a tuple — `CONST.expand((x.shape[0], *CONST.shape[1:]))` — traces fine and
produces the correct graph, so the two documented spellings of `expand` are not interchangeable
under tracing.

### It isn't fx-specific

Any object implementing the protocol reproduces it, so this is in the argument parser rather than
in `torch.fx`. Note that a *Tensor subclass* is unaffected — only non-Tensor implementers:

```python
class TF:
@classmethod
def __torch_function__(cls, func, types, args=(), kwargs=None):
return "dispatched"

t, B = torch.zeros(1, 4), TF()

t.expand(B, 4) # TypeError: expand() takes 1 positional argument but 2 were given
t.expand(4, B) # 'dispatched' -- same object, non-leading
t.expand(B) # 'dispatched'
t.expand((B, 4)) # 'dispatched' -- packed
```

### Affected surface

Everything with keyword-only parameters following the int-list is affected; everything with a
bare int-list signature is fine. Tested with the object in leading position:

| fails | dispatches |
| --- | --- |
| `Tensor.expand` | `Tensor.view` |
| `Tensor.new_zeros`, `new_empty`, `new_ones` | `Tensor.reshape` |
| `torch.zeros`, `torch.ones`, `torch.empty` | `Tensor.permute` |
| `torch.rand`, `torch.randn` | `Tensor.repeat` |
| | `Tensor.broadcast_to` |

`torch.zeros(B, 4)` and `torch.rand(B, 4)` failing is arguably the more painful half of this —
allocating a tensor whose leading dim is symbolic is hard to avoid when tracing.

`rand`/`randn` report `received an invalid combination of arguments` rather than the
`takes 1 positional argument` message, but the trigger and the packed-form workaround are identical.

### Likely mechanism

Not verified against a debug build, but this is the only reading consistent with the split above,
in `FunctionSignature::parse` (`torch/csrc/utils/python_arg_parser.cpp`):

`is_int_or_symint_list` is explicitly `__torch_function__`-aware — it appends protocol
implementers to `overloaded_args` and keeps going rather than rejecting them — so the var-args
gate (`allow_varargs_intlist`) is satisfied. But at iteration 0 of the parameter loop,
`FunctionParameter::check` *also* accepts the object for the `SYM_INT_LIST` parameter and consumes
a single positional slot, so the var-args collapse branch below it is never reached and `arg_pos`
advances by 1 instead of to `nargs`. The loop then reaches the first keyword-only parameter
(`implicit` for `expand`, `dtype`/`device`/`generator` for the factories) with `arg_pos < nargs`
and hits:

```cpp
// extra positional args given after single positional IntArrayRef arg
if (param.keyword_only) {
if (raise_exception) {
extra_args(*this, nargs);
}
return false;
}
```

which is exactly the observed message. Signatures with no trailing keyword-only parameter never
reach that branch: the loop simply ends, the leftover positional argument is ignored, and
`has_torch_function()` is true, so they dispatch — which is why `view`/`reshape`/`permute` work.

If that reading is right, the fix would be to attempt the var-args int-list collapse before
raising in that branch, or to stop `check` from greedily consuming the leading slot when the
parameter is var-args-eligible and `nargs > max_pos_args`.

### Expected behaviour

`t.expand(B, 4)` dispatches to `__torch_function__` exactly as `t.expand((B, 4))` does.

### Workaround

Pass sizes packed at every var-args int-list call site that a traced value can reach:

```python
t.expand((B, *t.shape[1:]))
```

### Versions

Claude's sandbox:
```
PyTorch version: 2.13.0+cu130
OS: Ubuntu 24.04.4 LTS (x86_64)
Libc version: glibc-2.39
Python version: 3.12.3 (main, Mar 3 2026, 12:15:18) [GCC 13.3.0] (64-bit runtime)
Is CUDA available: False
```

The relevant parser code is unchanged between v2.12.0, v2.13.0 and `main`.

My own setup, which was where we first observed the above bug:

```
PyTorch version: 2.13.0+cu130
Is debug build: False
CUDA used to build PyTorch: 13.0
ROCM used to build PyTorch: N/A

OS: Ubuntu 24.04.1 LTS (x86_64)
GCC version: (Ubuntu 13.3.0-6ubuntu2~24.04.1) 13.3.0
Clang version: Could not collect
CMake version: Could not collect
Libc version: glibc-2.39

Python version: 3.13.14 | packaged by Anaconda, Inc. | (main, Jun 17 2026, 20:12:46) [GCC 14.3.0] (64-bit runtime)
Python platform: Linux-6.18.33.2-microsoft-standard-WSL2-x86_64-with-glibc2.39
Is CUDA available: True
CUDA runtime version: Could not collect
CUDA_MODULE_LOADING set to:
GPU models and configuration: GPU 0: NVIDIA GeForce RTX 3080
Nvidia driver version: 591.86
cuDNN version: Could not collect
Is XPU available: False
HIP runtime version: N/A
MIOpen runtime version: N/A
Is XNNPACK available: False
Caching allocator config: N/A

CPU:
Architecture: x86_64
CPU op-mode(s): 32-bit, 64-bit
Address sizes: 39 bits physical, 48 bits virtual
Byte Order: Little Endian
CPU(s): 16
On-line CPU(s) list: 0-15
Vendor ID: GenuineIntel
Model name: 13th Gen Intel(R) Core(TM) i7-1360P
CPU family: 6
Model: 186
Thread(s) per core: 2
Core(s) per socket: 8
Socket(s): 1
Stepping: 2
BogoMIPS: 5222.42
Flags: fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ss ht syscall nx pdpe1gb rdtscp lm constant_tsc rep_good nopl xtopology tsc_reliable nonstop_tsc cpuid tsc_known_freq pni pclmulqdq vmx ssse3 fma cx16 pcid sse4_1 sse4_2 x2apic movbe popcnt tsc_deadline_timer aes xsave avx f16c rdrand hypervisor lahf_lm abm 3dnowprefetch ssbd ibrs ibpb stibp ibrs_enhanced tpr_shadow ept vpid ept_ad fsgsbase tsc_adjust bmi1 avx2 smep bmi2 erms invpcid rdseed adx smap clflushopt clwb sha_ni xsaveopt xsavec xgetbv1 xsaves avx_vnni vnmi umip waitpkg gfni vaes vpclmulqdq rdpid movdiri movdir64b fsrm md_clear serialize ibt flush_l1d arch_capabilities
Virtualization: VT-x
Hypervisor vendor: Microsoft
Virtualization type: full
L1d cache: 384 KiB (8 instances)
L1i cache: 256 KiB (8 instances)
L2 cache: 10 MiB (8 instances)
L3 cache: 18 MiB (1 instance)
NUMA node(s): 1
NUMA node0 CPU(s): 0-15
Vulnerability Gather data sampling: Not affected
Vulnerability Ghostwrite: Not affected
Vulnerability Indirect target selection: Not affected
Vulnerability Itlb multihit: Not affected
Vulnerability L1tf: Not affected
Vulnerability Mds: Not affected
Vulnerability Meltdown: Not affected
Vulnerability Mmio stale data: Not affected
Vulnerability Old microcode: Not affected
Vulnerability Reg file data sampling: Mitigation; Clear Register File
Vulnerability Retbleed: Mitigation; Enhanced IBRS
Vulnerability Spec rstack overflow: Not affected
Vulnerability Spec store bypass: Mitigation; Speculative Store Bypass disabled via prctl
Vulnerability Spectre v1: Mitigation; usercopy/swapgs barriers and __user pointer sanitization
Vulnerability Spectre v2: Mitigation; Enhanced / Automatic IBRS; IBPB conditional; PBRSB-eIBRS SW sequence; BHI BHI_DIS_S
Vulnerability Srbds: Not affected
Vulnerability Tsa: Not affected
Vulnerability Tsx async abort: Not affected
Vulnerability Vmscape: Not affected

Versions of relevant libraries:
[pip3] Could not collect
[conda] nvtx 0.2.14 pypi_0 pypi
```

cc @hameerabbasi @rgommers @ezyang

Contributor guide

Open the contributing guide

Research direction

Reproduce the leading-position and packed-form cases described in the issue, then inspect FunctionSignature::parse and FunctionParameter::check in torch/csrc/utils/python_arg_parser.cpp. Trace how var-args int-list handling interacts with keyword-only parameters and __torch_function__ dispatch. Done means t.expand(B, 4) and the listed factory calls dispatch like their packed forms without regressing existing int-list signatures.

Written by the indexing model from the issue text.

Assessment

Tech stack
cpp, python
Domain
backend, machine-learning
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
48/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.