Repository navigation
Fix mixed-dtype cat and stack in to_edge and the edge check - #24450
Merged
Merged
Conversation
### 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)
pytorchbot
requested review from
JacobSzwejbka and
larryliu0820
as code owners
November 2, 2026 17:16
🔗 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 SEVsThere are 1 currently active SEVs. If your PR is affected, please view them below: ⏳ 1 Pending, 1 Unrelated FailureAs of commit 84ce510 with merge base 4db778e ( 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. |
This was referenced Nov 2, 2026
JacobSzwejbka
approved these changes
Nov 2, 2026
This branch was previously deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What is wrong today
torch.cataccepts tensors of different dtypes and promotes them:to_edgerejects the same program:torch.stack,torch.concatandtorch.hstackfail too, since they becomecat.cat([float32, int32])is worse: it lowers without an error and gives wrong values. The portablecatkernel fix behind those values is in #24401.What this change does
to_edgecasts the inputs ofcatandstackto the dtype eager promoted to. TheRemoveMixedTypeOperatorspass already did this for binary ops likeadd. Forcatandstackit takes that dtype from the op's output, so an empty input dropped by an earlier pass still counts.EdgeOpArgValidator) now checks every tensor of an input list, not only the first. That gap letcat([float32, int32])pass.ConvertDtypePass(used asI64toI32) left int64 topk indices in acatit made int32, which the check in item 2 now rejects. It now casts the tensor-list inputs of ops it rewrites. Other inputs, like agatherindex, get no new cast.Compatibility:
to_edgethat changes only somecatorstackinputs to another dtype now fails the edge check. The Arm int64 passes, such asConvertInt64OutputOpsToInt32Pass, 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.catprograms that already ran, likecat([float32, int64]), now carry a_to_dim_order_copyfor each input of another dtype when re-exported. A selective build needs that operator to load them.What was tested
exir/program/test/test_program.py(float, integer, empty and repeated inputs),exir/tests/test_arg_validator.pyandbackends/transforms/test/test_convert_dtype_pass.py.catwith mixed float, integer, bool and empty inputs,stack,concatandhstackmatch eager.exirand XNNPACK tests and the Arm, Cadence, Qualcomm, Vulkan andbackends/transformspass 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