Skip to content

Commit 35010fc

Browse files
[Torch][FX] Rewrite as_strided before Torch IR import (#4585)
Move `aten.as_strided.default` handling out of backend/Torch IR decomposition and into FX import. The new `torch_mlir.extras.fx_as_strided` pass runs before Torch IR construction, while FakeTensor metadata still carries storage offset, physical strides, and layout information. Supported static `as_strided` reads are rewritten to `aten.index.Tensor`, raw `aten.as_strided.default` is rejected if it reaches generic ATen import. Delete the existing Torch `DecomposeComplexOps` lowering and the direct TorchToTOSA lowering for `aten.as_strided`. Those lowerings flattened the immediate tensor value and could not recover storage/layout metadata after import, which is unsafe for channels-last tensors and some view/copy boundaries. Add FX importer tests and e2e coverage for normal view chains, stepped slices, `view -> contiguous -> as_strided`, channels-last inputs and parameters, and `to(memory_format=channels_last) -> as_strided`. Update xfail/crashing sets for ONNX cases where export has already lost storage semantics or aborts before the runner can report an xfail. Also retag StableHLO integer resource literals to signless result types. The FX rewrite emits literal index tensors, and StableHLO constants must use signless integer element types even when the resource blob came from a signed Torch integer tensor.
1 parent dd41cf6 commit 35010fc

11 files changed

Lines changed: 1125 additions & 363 deletions

File tree

lib/Conversion/TorchToStablehlo/Basic.cpp

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
#include "mlir/Dialect/Arith/IR/Arith.h"
1616
#include "mlir/Dialect/Shape/IR/Shape.h"
1717
#include "mlir/Dialect/Tensor/IR/Tensor.h"
18+
#include "mlir/IR/DialectResourceBlobManager.h"
1819
#include "stablehlo/dialect/ChloOps.h"
1920
#include "stablehlo/dialect/StablehloOps.h"
2021
#include "torch-mlir/Conversion/TorchToStablehlo/StablehloLegalizeUtils.h"
@@ -894,8 +895,16 @@ LogicalResult ConvertAtenOp<ValueTensorLiteralOp>::matchAndRewrite(
894895
return success();
895896
}
896897

897-
rewriter.replaceOpWithNewOp<stablehlo::ConstantOp>(op, resultType,
898-
adaptor.getValue());
898+
ElementsAttr attr = cast<ElementsAttr>(adaptor.getValue());
899+
if (auto res = dyn_cast<DenseResourceElementsAttr>(attr)) {
900+
// Resource-backed integer literals keep the Torch signedness in the
901+
// attribute type. StableHLO integer tensors are signless, so retag the blob
902+
// to the converted result type before constructing the constant.
903+
auto shapedAttrTy = cast<ShapedType>(res.getType());
904+
if (isa<IntegerType>(shapedAttrTy.getElementType()))
905+
attr = DenseResourceElementsAttr::get(resultType, res.getRawHandle());
906+
}
907+
rewriter.replaceOpWithNewOp<stablehlo::ConstantOp>(op, resultType, attr);
899908
return success();
900909
}
901910

lib/Conversion/TorchToTosa/TorchToTosa.cpp

Lines changed: 0 additions & 103 deletions
Original file line numberDiff line numberDiff line change
@@ -9082,108 +9082,6 @@ LogicalResult ConvertAtenOp<AtenThresholdBackwardOp>::matchAndRewriteImpl(
90829082
return success();
90839083
}
90849084

9085-
// Legalization for aten.as_strided
9086-
template <>
9087-
LogicalResult ConvertAtenOp<AtenAsStridedOp>::matchAndRewriteImpl(
9088-
AtenAsStridedOp op, OpAdaptor adaptor,
9089-
ConversionPatternRewriter &rewriter) const {
9090-
// To lower aten.as_strided to TOSA, we will first reshape the input tensor to
9091-
// an 1-D tensor, then calculate the indices of result elements based on the
9092-
// output size, stride and storage offset. With the reshaped 1-D tensor and
9093-
// the indices, we can apply Gather to extract the required elements into a
9094-
// new tensor and then reshape it back to the desired output shape.
9095-
auto self = adaptor.getSelf();
9096-
9097-
// Not a tensor type
9098-
auto selfType = dyn_cast<TensorType>(self.getType());
9099-
if (!selfType)
9100-
return rewriter.notifyMatchFailure(op, "Only tensor types are supported");
9101-
auto selfElemTy = selfType.getElementType();
9102-
auto selfShape = selfType.getShape();
9103-
9104-
auto resultType =
9105-
dyn_cast<TensorType>(typeConverter->convertType(op.getType()));
9106-
auto resultElemTy = resultType.getElementType();
9107-
9108-
// Get output size
9109-
SmallVector<int64_t> outputSize;
9110-
if (!matchPattern(op.getSize(), m_TorchListOfConstantInts(outputSize)))
9111-
return rewriter.notifyMatchFailure(
9112-
op, "Only a constant list form of output size is supported");
9113-
9114-
// Get stride
9115-
SmallVector<int64_t> stride;
9116-
if (!matchPattern(op.getStride(), m_TorchListOfConstantInts(stride)))
9117-
return rewriter.notifyMatchFailure(
9118-
op, "Only a constant list form of stride is supported");
9119-
9120-
// Get storage offset
9121-
int64_t offset;
9122-
if (!matchPattern(op.getStorageOffset(), m_TorchConstantInt(&offset)))
9123-
offset = 0;
9124-
9125-
// Reshape input tensor into an 1-D tensor
9126-
int64_t selfNumElems = std::accumulate(selfShape.begin(), selfShape.end(), 1,
9127-
std::multiplies<int64_t>());
9128-
9129-
auto self1D = tosa::ReshapeOp::create(
9130-
rewriter, op->getLoc(), RankedTensorType::get({selfNumElems}, selfElemTy),
9131-
self, tosa::getTosaConstShape(rewriter, op->getLoc(), {selfNumElems}));
9132-
9133-
// Calculate the target elements indices
9134-
SmallVector<int32_t> targetIndicesVec;
9135-
int64_t outputRank = outputSize.size();
9136-
int64_t outputNumElems = std::accumulate(outputSize.begin(), outputSize.end(),
9137-
1, std::multiplies<int64_t>());
9138-
9139-
for (int64_t i = 0; i < outputNumElems; i++) {
9140-
// Index formula:
9141-
// index[i] = coord_i_0 * stride[0] + coord_i_1 * stride[1] + ... +
9142-
// coord_i_n * stride[n]
9143-
int32_t index = offset;
9144-
int64_t coordFinder = i;
9145-
for (int64_t dim = 0; dim < outputRank; dim++) {
9146-
int64_t indexCoord = coordFinder % outputSize[outputRank - dim - 1];
9147-
index += indexCoord * stride[outputRank - dim - 1];
9148-
coordFinder /= outputSize[outputRank - dim - 1];
9149-
}
9150-
targetIndicesVec.push_back(index);
9151-
}
9152-
9153-
auto targetIndices =
9154-
tosa::getConstTensor<int32_t>(rewriter, op, targetIndicesVec,
9155-
makeShapeTorchCompatible({outputNumElems}))
9156-
.value();
9157-
9158-
// Convert PyTorch-style indices and dim into TensorFlow-style indices
9159-
auto targetIndicesTf = tosa::convertTorchIndexToTfIndices(
9160-
rewriter, op, self1D.getResult(), targetIndices, 0);
9161-
if (!targetIndicesTf)
9162-
return rewriter.notifyMatchFailure(op,
9163-
"Convert PyTorch-style indices and dim "
9164-
"to TensorFlow-style indices failed");
9165-
9166-
// Gather the target elements from 1-D input tensor
9167-
// Apply TensorFlow GatherNdOp with TensorFlow-style indices to retrieve the
9168-
// target elements
9169-
auto gatherOp = tosa::convertGatherNdOp(
9170-
rewriter, op,
9171-
RankedTensorType::get(makeShapeTorchCompatible({outputNumElems}),
9172-
resultElemTy),
9173-
self1D.getResult(), targetIndicesTf.value());
9174-
9175-
if (!gatherOp)
9176-
return rewriter.notifyMatchFailure(op, "Convert GatherNdOp failed");
9177-
9178-
auto result = tosa::ReshapeOp::create(
9179-
rewriter, op->getLoc(), resultType, gatherOp.value(),
9180-
tosa::getTosaConstShape(rewriter, op->getLoc(), outputSize));
9181-
9182-
rewriter.replaceOp(op, {result.getResult()});
9183-
9184-
return success();
9185-
}
9186-
91879085
// Legalization for torch.prims.collapse
91889086
template <>
91899087
LogicalResult ConvertAtenOp<PrimsCollapseOp>::matchAndRewriteImpl(
@@ -11472,7 +11370,6 @@ std::set<StringRef> populateTorchToTosaConversionPatternsAndIllegalOps(
1147211370
INSERT_ATENOP_PATTERN(AtenDiagEmbedOp);
1147311371
INSERT_ATENOP_PATTERN(AtenUniformOp);
1147411372
INSERT_ATENOP_PATTERN(AtenThresholdBackwardOp);
11475-
INSERT_ATENOP_PATTERN(AtenAsStridedOp);
1147611373
INSERT_ATENOP_PATTERN(AtenClampTensorOp);
1147711374
INSERT_ATENOP_PATTERN(PrimsCollapseOp);
1147811375
INSERT_ATENOP_PATTERN(AtenReflectionPad1dOp);

lib/Dialect/Torch/Transforms/DecomposeComplexOps.cpp

Lines changed: 0 additions & 200 deletions
Original file line numberDiff line numberDiff line change
@@ -13176,205 +13176,6 @@ class DecomposeAtenBroadcastTensorsOp
1317613176
};
1317713177
} // namespace
1317813178

13179-
namespace {
13180-
class DecomposeAtenAsStridedOp : public OpRewritePattern<AtenAsStridedOp> {
13181-
public:
13182-
using OpRewritePattern<AtenAsStridedOp>::OpRewritePattern;
13183-
LogicalResult matchAndRewrite(AtenAsStridedOp op,
13184-
PatternRewriter &rewriter) const override {
13185-
13186-
// The `aten.as_strided` operation is decomposed into a series of
13187-
// operations that compute the indices based on the provided sizes and
13188-
// strides, and then index into the flattened input tensor as follows:
13189-
13190-
// input_flat = input.view(-1)
13191-
//
13192-
// for dim, s in enumerate(self.size):
13193-
// arange = torch.arange(s)
13194-
// view_shape = []
13195-
// for i in range(len(self.size)):
13196-
// if i == dim:
13197-
// view_shape.append(-1)
13198-
// else:
13199-
// view_shape.append(1)
13200-
// arange = arange.view(view_shape)
13201-
// if dim != 0:
13202-
// idx = idx + arange * self.stride[dim]
13203-
//
13204-
// # Flatten indices and add offset
13205-
// final_indices = idx.reshape(-1) + self.storage_offset
13206-
//
13207-
// # Index the flattened input tensor
13208-
// output = input_flat[final_indices]
13209-
//
13210-
// # Reshape to desired output size
13211-
// return output.view(self.size)
13212-
13213-
Location loc = op.getLoc();
13214-
MLIRContext *context = op->getContext();
13215-
Value input = op.getSelf();
13216-
auto inputType = dyn_cast<BaseTensorType>(input.getType());
13217-
13218-
if (!inputType || !inputType.hasSizes())
13219-
return rewriter.notifyMatchFailure(op, "input must have sizes");
13220-
13221-
SmallVector<int64_t> sizesInts;
13222-
if (!matchPattern(op.getSize(), m_TorchListOfConstantInts(sizesInts)))
13223-
return rewriter.notifyMatchFailure(
13224-
op, "sizes must be a list of constant ints");
13225-
13226-
SmallVector<int64_t> stridesInts;
13227-
if (!matchPattern(op.getStride(), m_TorchListOfConstantInts(stridesInts)))
13228-
return rewriter.notifyMatchFailure(
13229-
op, "strides must be a list of constant ints");
13230-
13231-
int64_t storageOffset = 0;
13232-
if (!isa<Torch::NoneType>(op.getStorageOffset().getType())) {
13233-
if (!matchPattern(op.getStorageOffset(),
13234-
m_TorchConstantInt(&storageOffset)))
13235-
return rewriter.notifyMatchFailure(
13236-
op, "storage_offset must be a constant integer");
13237-
}
13238-
13239-
ArrayRef<int64_t> inputSizes = inputType.getSizes();
13240-
int64_t inputRank = inputSizes.size();
13241-
int64_t resultRank = sizesInts.size();
13242-
13243-
Value cstZero =
13244-
ConstantIntOp::create(rewriter, loc, rewriter.getI64IntegerAttr(0));
13245-
if (inputRank > 1) {
13246-
// If the input is not a 1-d tensor, we need to flatten it
13247-
// to a 1D tensor before applying the strided indexing.
13248-
int64_t flattenedInputSize = 1;
13249-
for (int64_t size : inputSizes) {
13250-
if (size == kUnknownSize) {
13251-
flattenedInputSize = kUnknownSize;
13252-
break;
13253-
}
13254-
flattenedInputSize *= size;
13255-
}
13256-
13257-
auto flattenedInputTy =
13258-
cast<BaseTensorType>(inputType.getWithSizesAndDtype(
13259-
{flattenedInputSize}, inputType.getOptionalDtype()));
13260-
13261-
Value end = ConstantIntOp::create(
13262-
rewriter, loc, rewriter.getI64IntegerAttr(inputRank - 1));
13263-
input = AtenFlattenUsingIntsOp::create(rewriter, loc, flattenedInputTy,
13264-
input, cstZero, end);
13265-
}
13266-
13267-
Value cstOne =
13268-
ConstantIntOp::create(rewriter, loc, rewriter.getI64IntegerAttr(1));
13269-
Value cstMinusOne =
13270-
ConstantIntOp::create(rewriter, loc, rewriter.getI64IntegerAttr(-1));
13271-
13272-
SmallVector<int64_t> viewShapeInts(resultRank, 1);
13273-
SmallVector<Value> viewShapeListElems(resultRank, cstOne);
13274-
13275-
auto si64Type = IntegerType::get(context, 64, IntegerType::Signed);
13276-
Value finalIndices;
13277-
for (unsigned dim = 0; dim < sizesInts.size(); dim++) {
13278-
int64_t size = sizesInts[dim];
13279-
Value cstNone = ConstantNoneOp::create(rewriter, loc);
13280-
Value end = ConstantIntOp::create(rewriter, loc,
13281-
rewriter.getI64IntegerAttr(size));
13282-
13283-
auto arangeType =
13284-
ValueTensorType::get(context, llvm::ArrayRef(size), si64Type);
13285-
Value index = Torch::AtenArangeOp::create(
13286-
rewriter, loc, arangeType, end, cstNone, cstNone, cstNone, cstNone);
13287-
13288-
// Set the current dimension to -1 for broadcasting
13289-
viewShapeInts[dim] = -1;
13290-
viewShapeListElems[dim] = cstMinusOne;
13291-
13292-
Value viewShapeList = Torch::PrimListConstructOp::create(
13293-
rewriter, loc, Torch::ListType::get(Torch::IntType::get(context)),
13294-
viewShapeListElems);
13295-
13296-
auto viewType = ValueTensorType::get(
13297-
context, llvm::ArrayRef(viewShapeInts), si64Type);
13298-
index = AtenViewOp::create(rewriter, loc, viewType, index, viewShapeList);
13299-
13300-
// Multiply the index with the stride for the current dimension
13301-
Value cstStride = ConstantIntOp::create(
13302-
rewriter, loc, rewriter.getI64IntegerAttr(stridesInts[dim]));
13303-
index =
13304-
AtenMulScalarOp::create(rewriter, loc, viewType, index, cstStride);
13305-
13306-
// Reset the current dimension to 1 for the next iteration
13307-
viewShapeInts[dim] = 1;
13308-
viewShapeListElems[dim] = cstOne;
13309-
13310-
if (dim == 0) {
13311-
finalIndices = index;
13312-
continue;
13313-
}
13314-
13315-
// calculate common shape for broadcast
13316-
SmallVector<int64_t> broadcastShape;
13317-
SmallVector<Value> broadcastShapeValue;
13318-
computeBroadcastShape(rewriter, loc, {finalIndices, index},
13319-
broadcastShape, broadcastShapeValue);
13320-
Type broadcastType = ValueTensorType::get(
13321-
context, llvm::ArrayRef(broadcastShape), si64Type);
13322-
13323-
finalIndices = AtenAddTensorOp::create(rewriter, loc, broadcastType,
13324-
finalIndices, index, cstOne);
13325-
}
13326-
13327-
int64_t flattenedResultSize = 1;
13328-
for (int64_t size : sizesInts)
13329-
flattenedResultSize *= size;
13330-
13331-
// Flattening the indices and adding the storage offset
13332-
finalIndices = AtenFlattenUsingIntsOp::create(
13333-
rewriter, loc,
13334-
ValueTensorType::get(context, llvm::ArrayRef(flattenedResultSize),
13335-
si64Type),
13336-
finalIndices, cstZero, cstMinusOne); // -1 means flatten all
13337-
13338-
if (storageOffset != 0) {
13339-
Value cstStorageOffset = ConstantIntOp::create(
13340-
rewriter, loc, rewriter.getI64IntegerAttr(storageOffset));
13341-
finalIndices =
13342-
AtenAddScalarOp::create(rewriter, loc, finalIndices.getType(),
13343-
finalIndices, cstStorageOffset, cstOne);
13344-
}
13345-
13346-
// Index the flattened input tensor
13347-
Type listElemType =
13348-
inputType.getWithSizesAndDtype(/*optionalSizes=*/std::nullopt,
13349-
/*optionalDtype=*/nullptr);
13350-
Value indicesList = Torch::PrimListConstructOp::create(
13351-
rewriter, loc, Torch::ListType::get(listElemType),
13352-
SmallVector<Value>{finalIndices});
13353-
13354-
auto flattenedResultTy =
13355-
ValueTensorType::get(context, llvm::ArrayRef(flattenedResultSize),
13356-
inputType.getOptionalDtype());
13357-
Value result = AtenIndexTensorOp::create(rewriter, loc, flattenedResultTy,
13358-
input, indicesList);
13359-
13360-
// Reshape the result to the desired output size
13361-
SmallVector<Value> sizesIntsValues;
13362-
for (int64_t size : sizesInts) {
13363-
sizesIntsValues.push_back(ConstantIntOp::create(
13364-
rewriter, loc, rewriter.getI64IntegerAttr(size)));
13365-
}
13366-
Value resultSizeList = Torch::PrimListConstructOp::create(
13367-
rewriter, loc, Torch::ListType::get(Torch::IntType::get(context)),
13368-
sizesIntsValues);
13369-
result =
13370-
AtenViewOp::create(rewriter, loc, op.getType(), result, resultSizeList);
13371-
13372-
rewriter.replaceOp(op, result);
13373-
return success();
13374-
}
13375-
};
13376-
} // namespace
13377-
1337813179
namespace {
1337913180
class DecomposeComplexOpsPass
1338013181
: public impl::DecomposeComplexOpsBase<DecomposeComplexOpsPass> {
@@ -13709,7 +13510,6 @@ class DecomposeComplexOpsPass
1370913510
patterns);
1371013511
addPatternIfTargetOpIsIllegal<DecomposeAten_AssertScalarOp>(patterns);
1371113512
addPatternIfTargetOpIsIllegal<DecomposeAtenRoundDecimalsOp>(patterns);
13712-
addPatternIfTargetOpIsIllegal<DecomposeAtenAsStridedOp>(patterns);
1371313513

1371413514
GreedyRewriteConfig config;
1371513515
config.setUseTopDownTraversal(true);

0 commit comments

Comments
 (0)