Skip to content

Commit 3ba18ec

Browse files
[TorchToStablehlo] Add support for lowering torch.aten.sort to stablehlo.sort (#4633)
## Description This PR implements the lowering support for `torch.aten.sort` in the `convert-torch-to-stablehlo` pass, which is a prerequisite for compiling operators like `topk` (since `topk` decomposes into `sort` + `slice`). See semantics in https://openxla.org/stablehlo/spec#sort. Fix #4337. ## Details - Implemented `ConvertAtenSortOp` to lower `torch.aten.sort` to `stablehlo.sort`. - Used `stablehlo.iota` to generate initial indices along the sorting dimension. - Created the comparator region inside `stablehlo.sort` using `stablehlo.compare` (GT for descending, LT for ascending). ## Testing - Added regression test case in `test/Conversion/TorchToStablehlo/reduction.mlir`. - Verified all unit tests pass with `check-torch-mlir`. - the output stablehlo mlir is compiled and executed by iree, the result is expected. Signed-off-by: hsqStephenZhang <stephenzhang666666@gmail.com>
1 parent 0b79041 commit 3ba18ec

2 files changed

Lines changed: 128 additions & 0 deletions

File tree

lib/Conversion/TorchToStablehlo/Basic.cpp

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2204,6 +2204,111 @@ LogicalResult ConvertAtenOp<AtenIsfiniteOp>::matchAndRewrite(
22042204
return success();
22052205
}
22062206

2207+
// AtenSortOp
2208+
template <>
2209+
LogicalResult ConvertAtenOp<AtenSortOp>::matchAndRewrite(
2210+
AtenSortOp op, OpAdaptor adaptor,
2211+
ConversionPatternRewriter &rewriter) const {
2212+
Value self = adaptor.getSelf();
2213+
auto selfType = dyn_cast<RankedTensorType>(self.getType());
2214+
if (!selfType)
2215+
return rewriter.notifyMatchFailure(op, "expected ranked tensor for self");
2216+
2217+
// Values and indices return types
2218+
RankedTensorType valuesType = dyn_cast<RankedTensorType>(
2219+
getTypeConverter()->convertType(op.getResult(0).getType()));
2220+
RankedTensorType indicesType = dyn_cast<RankedTensorType>(
2221+
getTypeConverter()->convertType(op.getResult(1).getType()));
2222+
if (!valuesType || !indicesType)
2223+
return rewriter.notifyMatchFailure(op,
2224+
"expected ranked tensor output types");
2225+
2226+
Location loc = op.getLoc();
2227+
Value dimVal = op.getDim();
2228+
Value descendingVal = op.getDescending();
2229+
2230+
int64_t dim;
2231+
if (auto constantOp = dimVal.getDefiningOp<ConstantIntOp>()) {
2232+
dim = constantOp.getValueAttr().getInt();
2233+
} else {
2234+
return rewriter.notifyMatchFailure(op, "non-constant dim parameter");
2235+
}
2236+
int64_t rank = selfType.getRank();
2237+
if (dim < -rank || dim >= rank) {
2238+
return rewriter.notifyMatchFailure(op, "dimension out of range");
2239+
}
2240+
if (dim < 0) {
2241+
dim += rank;
2242+
}
2243+
2244+
bool descending;
2245+
if (auto constantOp = descendingVal.getDefiningOp<ConstantBoolOp>()) {
2246+
descending = constantOp.getValue();
2247+
} else {
2248+
return rewriter.notifyMatchFailure(op, "non-constant descending parameter");
2249+
}
2250+
2251+
// 1. Generate indices tensor using stablehlo.iota
2252+
Value indices = stablehlo::IotaOp::create(rewriter, loc, indicesType,
2253+
rewriter.getI64IntegerAttr(dim));
2254+
2255+
// 2. Create stablehlo.sort op
2256+
auto sortOp = stablehlo::SortOp::create(
2257+
rewriter, loc, TypeRange{valuesType, indicesType},
2258+
ValueRange{self, indices}, rewriter.getI64IntegerAttr(dim),
2259+
rewriter.getBoolAttr(false));
2260+
2261+
// 3. Build comparator block
2262+
Block &block = sortOp.getComparator().emplaceBlock();
2263+
2264+
auto blockValArgumentType =
2265+
RankedTensorType::get({}, valuesType.getElementType());
2266+
auto blockIdxArgumentType =
2267+
RankedTensorType::get({}, indicesType.getElementType());
2268+
2269+
block.addArgument(blockValArgumentType, loc);
2270+
block.addArgument(blockValArgumentType, loc);
2271+
block.addArgument(blockIdxArgumentType, loc);
2272+
block.addArgument(blockIdxArgumentType, loc);
2273+
2274+
auto *firstValArg = block.args_begin();
2275+
auto *secondValArg = std::next(firstValArg);
2276+
2277+
OpBuilder::InsertionGuard guard(rewriter);
2278+
rewriter.setInsertionPointToStart(&block);
2279+
2280+
auto compareDirectionAttr = stablehlo::ComparisonDirectionAttr::get(
2281+
rewriter.getContext(), descending ? stablehlo::ComparisonDirection::GT
2282+
: stablehlo::ComparisonDirection::LT);
2283+
2284+
stablehlo::ComparisonTypeAttr compareTypeAttr;
2285+
Type elemTy = valuesType.getElementType();
2286+
2287+
if (isa<mlir::FloatType>(valuesType.getElementType())) {
2288+
compareTypeAttr = stablehlo::ComparisonTypeAttr::get(
2289+
rewriter.getContext(), stablehlo::ComparisonType::FLOAT);
2290+
} else if (isa<mlir::IntegerType>(valuesType.getElementType())) {
2291+
if (elemTy.isInteger(1)) {
2292+
compareTypeAttr = stablehlo::ComparisonTypeAttr::get(
2293+
rewriter.getContext(), stablehlo::ComparisonType::UNSIGNED);
2294+
} else {
2295+
compareTypeAttr = stablehlo::ComparisonTypeAttr::get(
2296+
rewriter.getContext(), stablehlo::ComparisonType::SIGNED);
2297+
}
2298+
}
2299+
2300+
auto compareResultType = RankedTensorType::get({}, rewriter.getI1Type());
2301+
2302+
Value compareResult = stablehlo::CompareOp::create(
2303+
rewriter, loc, compareResultType, *firstValArg, *secondValArg,
2304+
compareDirectionAttr, compareTypeAttr);
2305+
2306+
stablehlo::ReturnOp::create(rewriter, loc, compareResult);
2307+
2308+
rewriter.replaceOp(op, sortOp.getResults());
2309+
return success();
2310+
}
2311+
22072312
void mlir::torch::torch_to_stablehlo::populateBasicOpPatternsAndLegality(
22082313
TypeConverter &typeConverter, RewritePatternSet &patterns,
22092314
ConversionTarget &target, const TorchToStablehloOptions &options) {
@@ -2380,6 +2485,8 @@ void mlir::torch::torch_to_stablehlo::populateBasicOpPatternsAndLegality(
23802485

23812486
INSERT_ATENOP_PATTERN(AtenTrilOp);
23822487
INSERT_ATENOP_PATTERN(AtenIsfiniteOp);
2488+
INSERT_ATENOP_PATTERN(AtenSortOp);
2489+
23832490
#undef INSERT_ATENOP_PATTERN
23842491

23852492
#define INSERT_BINARY_BROADCAST_PATTERN(AtenOp, StablehloOp) \

test/Conversion/TorchToStablehlo/basic.mlir

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -339,3 +339,24 @@ func.func @torch.aten.tril(%arg0: !torch.vtensor<[2,3,5],f32>, %arg1: !torch.int
339339
%0 = torch.aten.tril %arg0, %arg1:!torch.vtensor<[2,3,5],f32>, !torch.int -> !torch.vtensor<[2,3,5],f32>
340340
return %0 : !torch.vtensor<[2,3,5],f32>
341341
}
342+
343+
// -----
344+
345+
// CHECK-LABEL: func.func @torch.aten.sort(
346+
// CHECK-SAME: %[[ARG_0:.*]]: !torch.vtensor<[2,3],f32>) -> (!torch.vtensor<[2,3],f32>, !torch.vtensor<[2,3],si64>) {
347+
// CHECK: %[[VAL_0:.*]] = torch_c.to_builtin_tensor %[[ARG0]] : !torch.vtensor<[2,3],f32> -> tensor<2x3xf32>
348+
// CHECK: %[[VAL_1:.*]] = stablehlo.iota dim = 1 : tensor<2x3xi64>
349+
// CHECK: %[[VAL_2:.*]]:2 = "stablehlo.sort"(%[[VAL_0]], %[[VAL_1]]) <{dimension = 1 : i64, is_stable = false}> ({
350+
// CHECK: ^bb0(%[[ARG1:.*]]: tensor<f32>, %[[ARG2:.*]]: tensor<f32>, %[[ARG3:.*]]: tensor<i64>, %[[ARG4:.*]]: tensor<i64>):
351+
// CHECK: %[[VAL_3:.*]] = stablehlo.compare GT, %[[ARG1]], %[[ARG2]], FLOAT : (tensor<f32>, tensor<f32>) -> tensor<i1>
352+
// CHECK: stablehlo.return %[[VAL_3]] : tensor<i1>
353+
// CHECK: }) : (tensor<2x3xf32>, tensor<2x3xi64>) -> (tensor<2x3xf32>, tensor<2x3xi64>)
354+
// CHECK: %[[VAL_4:.*]] = torch_c.from_builtin_tensor %[[VAL_2]]#1 : tensor<2x3xi64> -> !torch.vtensor<[2,3],si64>
355+
// CHECK: %[[VAL_5:.*]] = torch_c.from_builtin_tensor %[[VAL_2]]#0 : tensor<2x3xf32> -> !torch.vtensor<[2,3],f32>
356+
// CHECK: return %[[VAL_5]], %[[VAL_4]] : !torch.vtensor<[2,3],f32>, !torch.vtensor<[2,3],si64>
357+
func.func @torch.aten.sort(%arg0: !torch.vtensor<[2,3],f32>) -> (!torch.vtensor<[2,3],f32>, !torch.vtensor<[2,3],si64>) {
358+
%int-1 = torch.constant.int -1
359+
%true = torch.constant.bool true
360+
%values, %indices = torch.aten.sort %arg0, %int-1, %true : !torch.vtensor<[2,3],f32>, !torch.int, !torch.bool -> !torch.vtensor<[2,3],f32>, !torch.vtensor<[2,3],si64>
361+
return %values, %indices : !torch.vtensor<[2,3],f32>, !torch.vtensor<[2,3],si64>
362+
}

0 commit comments

Comments
 (0)