Skip to content

[Fix][Relax][Frontend][Torch] Emit int64 indices for sort and argsort - #20254

Open
hiyufan wants to merge 1 commit into
apache:mainfrom
hiyufan:fix/relax-torch-sort-index-dtype
Open

[Fix][Relax][Frontend][Torch] Emit int64 indices for sort and argsort#20254
hiyufan wants to merge 1 commit into
apache:mainfrom
hiyufan:fix/relax-torch-sort-index-dtype

Conversation

@hiyufan

@hiyufan hiyufan commented Sep 1, 2026

Copy link
Copy Markdown

Problem

torch.sort and torch.argsort return int64 indices. The Relax PyTorch frontend emits int32 for them.

relax.op.argsort defaults to dtype="int32", and both call sites in base_fx_graph_translator.py take that default:

# _argsort
return self.block_builder.emit(relax.op.argsort(x, dim, descending))

# _sort
indices = self.block_builder.emit(relax.op.argsort(x, dim, descending))

To be precise about the scope: the sorted values and the index values are already correct. Only the index dtype diverges from PyTorch.

Why this looks like an oversight rather than a deliberate choice

relax.op.topk has the same dtype="int32" default, and _topk in this very file explicitly overrides it:

relax.op.topk(x, k=k, axis=dim, largest=largest, ret_type="both", dtype="int64")

So the frontend already establishes that the relax-level default has to be overridden to match PyTorch. Every index-producing torch op the frontend supports comes out as int64 — except sort and argsort:

torch op torch index dtype frontend before this PR
torch.sort int64 int32
torch.argsort int64 int32
torch.topk int64 int64 (explicit dtype=)
torch.argmax / torch.argmin int64 int64
torch.max(dim=) / torch.median(dim=) int64 int64
torch.bucketize int64 int64

A single imported graph that uses both topk and sort therefore carries two different index dtypes for the same kind of value.

Reproduce

import torch
from torch import nn
from tvm.relax.frontend.torch import from_exported_program


def relax_dtypes(fn, x):
    class M(nn.Module):
        def forward(self, t):
            return fn(t)

    ep = torch.export.export(M().eval(), (x,))
    return [str(f.dtype) for f in from_exported_program(ep)["main"].ret_ty.fields]


x = torch.randn(3, 4)
print("sort   ", relax_dtypes(lambda t: torch.sort(t, dim=1), x))
print("argsort", relax_dtypes(lambda t: torch.argsort(t, dim=1), x))
print("topk   ", relax_dtypes(lambda t: torch.topk(t, 2, dim=1), x))

Before / after:

                 before                        after
sort     ['float32', 'int32']          ['float32', 'int64']
argsort  ['int32']                     ['int64']
topk     ['float32', 'int64']          ['float32', 'int64']

Fix

Pass dtype="int64" at both relax.op.argsort call sites, matching what _topk already does.

Verification

Built with LLVM and ran the imported graph through the VM against PyTorch on sort(dim=1), sort(descending=True), sort(dim=0) and argsort(dim=1):

  • before: values equal, indices equal, index dtype int32 vs torch int64
  • after: values equal, indices equal, index dtype int64 — matches torch on every case

Test suites, tests/python/relax/test_frontend_from_fx.py + tests/python/relax/test_frontend_from_exported_program.py:

  • clean main: 24 failed, 412 passed, 3 skipped
  • with this change: 24 failed, 413 passed, 3 skipped

The 24 failures are pre-existing on main in my environment (test_dtypes and friends) and are identical before and after; the one additional pass is the new test_sort.

ruff format --check and ruff check are clean on the touched files.

Tests

  • Updated the expected IR in test_argsort (both frontend test files) and test_sort (test_frontend_from_fx.py) — these had the int32 result written into them.
  • Added test_sort to test_frontend_from_exported_program.py; that path had no coverage for torch.sort.

This change was prepared with AI assistance (Claude). I have reviewed and verified it, and can speak to it in review.

`torch.sort` and `torch.argsort` return int64 indices, but the Relax
frontend emitted int32. `relax.op.argsort` defaults to `dtype="int32"`,
and both call sites in `base_fx_graph_translator.py` took that default.

`_topk` in the same file already overrides the identical int32 default on
`relax.op.topk` to match PyTorch, and every other index-producing op the
frontend supports (argmax, argmin, max.dim, median.dim, bucketize) comes
out as int64. sort and argsort were the only exceptions, so a single
imported graph could carry two different index dtypes for the same kind
of value.

The sorted values and the index values themselves were already correct;
only the index dtype diverged.

Pass `dtype="int64"` at both call sites, update the expectations that had
the int32 result written into them, and add a `test_sort` to the
exported-program tests, which had no coverage for `torch.sort`.

Co-authored-by: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant