Skip to content

Fix mixed-dtype cat and stack in to_edge and the edge check - #24450

Merged
JacobSzwejbka merged 1 commit into
release/1.6from
cherry-pick-24381-by-pytorch_bot_bot_
Nov 2, 2026
Merged

JacobSzwejbka merged 1 commit into
release/1.6from
cherry-pick-24381-by-pytorch_bot_bot_

Conversation

@pytorchbot

@pytorchbot pytorchbot commented Nov 2, 2026 •

Copy link
Copy Markdown
Collaborator

What is wrong today

torch.cat accepts tensors of different dtypes and promotes them:

torch.cat([torch.ones(2, dtype=torch.bfloat16), torch.ones(2)]).dtype  # torch.float32

to_edge rejects the same program:

SpecViolationError: These operators are taking Tensor inputs with mismatched dtypes:
... aten.cat.default ... {'tensors': torch.bfloat16, '__ret_0': torch.float32}

torch.stack, torch.concat and torch.hstack fail too, since they become cat.

cat([float32, int32]) is worse: it lowers without an error and gives wrong values. The portable cat kernel fix behind those values is in #24401.

What this change does

  1. to_edge casts the inputs of cat and stack to the dtype eager promoted to. The RemoveMixedTypeOperators pass already did this for binary ops like add. For cat and stack it takes that dtype from the op's output, so an empty input dropped by an earlier pass still counts.
  2. The edge dtype check (EdgeOpArgValidator) now checks every tensor of an input list, not only the first. That gap let cat([float32, int32]) pass.
  3. ConvertDtypePass (used as I64toI32) left int64 topk indices in a cat it made int32, which the check in item 2 now rejects. It now casts the tensor-list inputs of ops it rewrites. Other inputs, like a gather index, get no new cast.

Compatibility:

  • A pass after to_edge that changes only some cat or stack inputs to another dtype now fails the edge check. The Arm int64 passes, such as ConvertInt64OutputOpsToInt32Pass, can do this when passed as transform passes, as the Arm README shows. The Arm tester, scripts and recipes lower with _check_ir_validity=False, so they are not affected, and the README now says to do the same. An Arm test now expects this graph.
  • A dropped empty input with a wider dtype now widens the output, as in eager.
  • Mixed cat programs that already ran, like cat([float32, int64]), now carry a _to_dim_order_copy for each input of another dtype when re-exported. A selective build needs that operator to load them.

What was tested

  • New tests, each failing without its fix: exir/program/test/test_program.py (float, integer, empty and repeated inputs), exir/tests/test_arg_validator.py and backends/transforms/test/test_convert_dtype_pass.py.
  • On the portable runtime, cat with mixed float, integer, bool and empty inputs, stack, concat and hstack match eager.
  • Apart from the new tests and the Arm test, exir and XNNPACK tests and the Arm, Cadence, Qualcomm, Vulkan and backends/transforms pass tests give the same results with and without this change. NXP and some Cadence pass tests could not run here.

cc @digantdesai @freddan80 @per @zingo @oscarandersson8218 @mansnils @Sebastian-Larsson @robell @rascani

### What is wrong today

`torch.cat` accepts tensors of different dtypes and promotes them:

```python
torch.cat([torch.ones(2, dtype=torch.bfloat16), torch.ones(2)]).dtype  # torch.float32
```

`to_edge` rejects the same program:

```
SpecViolationError: These operators are taking Tensor inputs with mismatched dtypes:
... aten.cat.default ... {'tensors': torch.bfloat16, '__ret_0': torch.float32}
```

`torch.stack`, `torch.concat` and `torch.hstack` fail too, since they
become `cat`.

`cat([float32, int32])` is worse: it lowers without an error and gives
wrong values. The portable `cat` kernel fix behind those values is in
#24401.

### What this change does

1. `to_edge` casts the inputs of `cat` and `stack` to the dtype eager
promoted to. The `RemoveMixedTypeOperators` pass already did this for
binary ops like `add`. For `cat` and `stack` it takes that dtype from
the op's output, so an empty input dropped by an earlier pass still
counts.
2. The edge dtype check (`EdgeOpArgValidator`) now checks every tensor
of an input list, not only the first. That gap let `cat([float32,
int32])` pass.
3. `ConvertDtypePass` (used as `I64toI32`) left int64 topk indices in a
`cat` it made int32, which the check in item 2 now rejects. It now casts
the tensor-list inputs of ops it rewrites. Other inputs, like a `gather`
index, get no new cast.

Compatibility:

- A pass after `to_edge` that changes only some `cat` or `stack` inputs
to another dtype now fails the edge check. The Arm int64 passes, such as
`ConvertInt64OutputOpsToInt32Pass`, can do this when passed as transform
passes, as the Arm README shows. The Arm tester, scripts and recipes
lower with `_check_ir_validity=False`, so they are not affected, and the
README now says to do the same. An Arm test now expects this graph.
- A dropped empty input with a wider dtype now widens the output, as in
eager.
- Mixed `cat` programs that already ran, like `cat([float32, int64])`,
now carry a `_to_dim_order_copy` for each input of another dtype when
re-exported. A selective build needs that operator to load them.

### What was tested

- New tests, each failing without its fix:
`exir/program/test/test_program.py` (float, integer, empty and repeated
inputs), `exir/tests/test_arg_validator.py` and
`backends/transforms/test/test_convert_dtype_pass.py`.
- On the portable runtime, `cat` with mixed float, integer, bool and
empty inputs, `stack`, `concat` and `hstack` match eager.
- Apart from the new tests and the Arm test, `exir` and XNNPACK tests
and the Arm, Cadence, Qualcomm, Vulkan and `backends/transforms` pass
tests give the same results with and without this change. NXP and some
Cadence pass tests could not run here.

(cherry picked from commit ecc4197)
@pytorch-bot

pytorch-bot Bot commented Nov 2, 2026 •

Copy link
Copy Markdown

🔗 Helpful Links

🧪 See artifacts and rendered test results at hud.pytorch.org/pr/pytorch/executorch/24450

Note: Links to docs will display an error until the docs builds have been completed.

❗ 1 Active SEVs

There are 1 currently active SEVs. If your PR is affected, please view them below:

⏳ 1 Pending, 1 Unrelated Failure

As of commit 84ce510 with merge base 4db778e (image):

BROKEN TRUNK - The following job failed but were present on the merge base:

👉 Rebase onto the `viable/strict` branch to avoid these failures

This comment was automatically generated by Dr. CI and updates every 15 minutes.

@pytorchbot
pytorchbot requested a review from rascani as a code owner November 2, 2026 17:16
@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. label Nov 2, 2026
@github-actions github-actions Bot added ciflow/trunk module: arm Issues related to arm backend and removed CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. labels Nov 2, 2026
@JacobSzwejbka JacobSzwejbka reopened this Nov 2, 2026
@JacobSzwejbka
JacobSzwejbka merged commit 3668b0c into release/1.6 Nov 2, 2026
836 of 884 checks passed
@JacobSzwejbka
JacobSzwejbka deleted the cherry-pick-24381-by-pytorch_bot_bot_ branch November 2, 2026 19:34

This branch was previously deployed

1 inactive deployment
cadence — 84ce510f Deployed Nov 2, 2026 by JacobSzwejbka via hifi-op-test / hifi4 #35990
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ciflow/trunk module: arm Issues related to arm backend

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants