2020-07-07 04:57:00 +08:00
|
|
|
/* Copyright 2019 The TensorFlow Authors. All Rights Reserved.
|
|
|
|
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
you may not use this file except in compliance with the License.
|
|
|
|
You may obtain a copy of the License at
|
|
|
|
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
See the License for the specific language governing permissions and
|
|
|
|
limitations under the License.
|
|
|
|
==============================================================================*/
|
|
|
|
|
|
|
|
// This file implements logic for lowering LHLO dialect to Affine dialect.
|
|
|
|
|
2020-07-29 07:12:08 +08:00
|
|
|
#include "mlir-hlo/Dialect/mhlo/IR/lhlo_ops.h"
|
|
|
|
#include "mlir-hlo/Dialect/mhlo/transforms/map_lmhlo_to_scalar_op.h"
|
|
|
|
#include "mlir/Dialect/Affine/IR/AffineOps.h"
|
|
|
|
#include "mlir/Dialect/StandardOps/IR/Ops.h"
|
2020-12-15 16:58:42 +08:00
|
|
|
#include "mlir/IR/BuiltinTypes.h"
|
2020-07-29 07:12:08 +08:00
|
|
|
#include "mlir/IR/Location.h"
|
|
|
|
#include "mlir/Pass/Pass.h"
|
2020-10-27 21:55:28 +08:00
|
|
|
#include "mlir/Transforms/GreedyPatternRewriteDriver.h"
|
2020-07-07 04:57:00 +08:00
|
|
|
|
|
|
|
namespace mlir {
|
2020-07-09 01:05:32 +08:00
|
|
|
namespace lmhlo {
|
2020-07-07 04:57:00 +08:00
|
|
|
namespace {
|
|
|
|
|
|
|
|
// Builds an affine loop nest iterating from zeros to "upper_bounds" with unit
|
|
|
|
// steps, and populates the body of the innermost loop using "body_builder".
|
|
|
|
static void BuildBoundedAffineLoopNest(
|
|
|
|
OpBuilder& builder, Location location, ArrayRef<int64_t> upper_bounds,
|
|
|
|
function_ref<void(OpBuilder&, Location, ValueRange)> body_builder) {
|
|
|
|
SmallVector<int64_t, 3> lower_bounds(upper_bounds.size(), /*Value=*/0);
|
|
|
|
SmallVector<int64_t, 3> steps(upper_bounds.size(), /*Value=*/1);
|
|
|
|
buildAffineLoopNest(builder, location, lower_bounds, upper_bounds, steps,
|
|
|
|
body_builder);
|
|
|
|
}
|
|
|
|
|
|
|
|
struct DotOpConverter : public OpRewritePattern<DotOp> {
|
|
|
|
using OpRewritePattern<DotOp>::OpRewritePattern;
|
|
|
|
|
|
|
|
// Supports only rank-2 tensors for LHS and RHS.
|
|
|
|
LogicalResult matchAndRewrite(DotOp op,
|
|
|
|
PatternRewriter& rewriter) const override {
|
|
|
|
Value lhs = op.lhs();
|
|
|
|
Value rhs = op.rhs();
|
|
|
|
MemRefType lhs_type = lhs.getType().cast<MemRefType>();
|
|
|
|
MemRefType rhs_type = rhs.getType().cast<MemRefType>();
|
|
|
|
Type element_type = lhs_type.getElementType();
|
|
|
|
ArrayRef<int64_t> shape_lhs = lhs_type.getShape();
|
|
|
|
ArrayRef<int64_t> shape_rhs = rhs_type.getShape();
|
|
|
|
|
|
|
|
if ((lhs_type.getRank() != 2) || (rhs_type.getRank() != 2)) {
|
|
|
|
return failure();
|
|
|
|
}
|
|
|
|
|
2020-10-16 06:08:30 +08:00
|
|
|
// We don't currently support batching dimensions, or multiple contraction
|
|
|
|
// dimensions.
|
|
|
|
mhlo::DotDimensionNumbers dot_dimension_numbers =
|
|
|
|
op.dot_dimension_numbers();
|
|
|
|
if (dot_dimension_numbers.lhs_batching_dimensions().size() > 0 ||
|
|
|
|
dot_dimension_numbers.rhs_batching_dimensions().size() > 0)
|
|
|
|
return failure();
|
|
|
|
if (dot_dimension_numbers.lhs_contracting_dimensions().size() != 1 ||
|
|
|
|
*dot_dimension_numbers.lhs_contracting_dimensions().begin() != 1 ||
|
|
|
|
dot_dimension_numbers.rhs_contracting_dimensions().size() != 1 ||
|
|
|
|
*dot_dimension_numbers.rhs_contracting_dimensions().begin() != 0) {
|
|
|
|
return failure();
|
|
|
|
}
|
|
|
|
|
2020-07-07 04:57:00 +08:00
|
|
|
LogicalResult map_status = success();
|
|
|
|
auto body_builder = [&](OpBuilder& builder, Location loc, ValueRange ivs) {
|
|
|
|
SmallVector<Value, 2> lhs_indices{ivs[0], ivs[2]},
|
|
|
|
rhs_indices{ivs[2], ivs[1]}, result_indices{ivs[0], ivs[1]};
|
|
|
|
|
|
|
|
auto l = builder.create<AffineLoadOp>(loc, lhs, lhs_indices);
|
|
|
|
auto r = builder.create<AffineLoadOp>(loc, rhs, rhs_indices);
|
|
|
|
auto result =
|
|
|
|
rewriter.create<AffineLoadOp>(loc, op.output(), result_indices);
|
2020-07-09 11:32:16 +08:00
|
|
|
Value op_result = lmhlo::HloOpToStdScalarOp::map<DotOp>(
|
2020-07-07 04:57:00 +08:00
|
|
|
op, element_type, {l, r, result}, &builder);
|
|
|
|
map_status = success(op_result != nullptr);
|
|
|
|
if (failed(map_status)) return;
|
|
|
|
builder.create<AffineStoreOp>(loc, op_result, op.output(),
|
|
|
|
result_indices);
|
|
|
|
};
|
|
|
|
|
|
|
|
BuildBoundedAffineLoopNest(rewriter, op.getLoc(),
|
|
|
|
{shape_lhs[0], shape_rhs[1], shape_rhs[0]},
|
|
|
|
body_builder);
|
|
|
|
if (failed(map_status)) return failure();
|
|
|
|
|
|
|
|
rewriter.eraseOp(op);
|
|
|
|
return success();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
template <typename LhloOpTy>
|
|
|
|
struct BinaryOpConverter : public OpRewritePattern<LhloOpTy> {
|
|
|
|
using OpRewritePattern<LhloOpTy>::OpRewritePattern;
|
|
|
|
|
|
|
|
LogicalResult matchAndRewrite(LhloOpTy op,
|
|
|
|
PatternRewriter& rewriter) const override {
|
|
|
|
const auto& lhs = op.lhs();
|
|
|
|
const auto& rhs = op.rhs();
|
|
|
|
const auto& lhs_type = lhs.getType().template cast<MemRefType>();
|
|
|
|
const auto& rhs_type = rhs.getType().template cast<MemRefType>();
|
|
|
|
const auto& element_type = lhs_type.getElementType();
|
|
|
|
|
|
|
|
if (lhs_type.getShape() != rhs_type.getShape()) {
|
|
|
|
return failure();
|
|
|
|
}
|
|
|
|
|
|
|
|
LogicalResult map_status = success();
|
|
|
|
auto body_builder = [&](OpBuilder& builder, Location loc,
|
|
|
|
ValueRange induction_vars) {
|
|
|
|
auto l = builder.create<AffineLoadOp>(loc, lhs, induction_vars);
|
|
|
|
auto r = builder.create<AffineLoadOp>(loc, rhs, induction_vars);
|
2020-07-09 11:32:16 +08:00
|
|
|
Value op_result = lmhlo::HloOpToStdScalarOp::map<LhloOpTy>(
|
2020-07-07 04:57:00 +08:00
|
|
|
op, element_type, {l, r}, &builder);
|
|
|
|
map_status = success(op_result != nullptr);
|
|
|
|
if (failed(map_status)) return;
|
|
|
|
rewriter.create<AffineStoreOp>(loc, op_result, op.out(), induction_vars);
|
|
|
|
};
|
|
|
|
|
|
|
|
BuildBoundedAffineLoopNest(rewriter, op.getLoc(), lhs_type.getShape(),
|
|
|
|
body_builder);
|
|
|
|
if (failed(map_status)) return failure();
|
|
|
|
rewriter.eraseOp(op);
|
|
|
|
return success();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
void populateLHLOToAffineConversionPattern(MLIRContext* context,
|
|
|
|
OwningRewritePatternList* patterns) {
|
|
|
|
// clang-format off
|
|
|
|
patterns->insert<
|
2020-07-09 01:05:32 +08:00
|
|
|
BinaryOpConverter<lmhlo::AddOp>,
|
|
|
|
BinaryOpConverter<lmhlo::AndOp>,
|
|
|
|
BinaryOpConverter<lmhlo::DivOp>,
|
|
|
|
BinaryOpConverter<lmhlo::MaxOp>,
|
|
|
|
BinaryOpConverter<lmhlo::MinOp>,
|
|
|
|
BinaryOpConverter<lmhlo::MulOp>,
|
|
|
|
BinaryOpConverter<lmhlo::SubOp>,
|
2020-07-07 04:57:00 +08:00
|
|
|
DotOpConverter>(context);
|
|
|
|
// clang-format on
|
|
|
|
}
|
|
|
|
|
2020-07-29 07:12:08 +08:00
|
|
|
struct LhloLegalizeToAffinePass
|
|
|
|
: public PassWrapper<LhloLegalizeToAffinePass, FunctionPass> {
|
2020-08-26 11:30:05 +08:00
|
|
|
void getDependentDialects(DialectRegistry& registry) const override {
|
|
|
|
registry.insert<AffineDialect>();
|
|
|
|
}
|
2020-07-07 04:57:00 +08:00
|
|
|
void runOnFunction() override {
|
|
|
|
auto func = getFunction();
|
2021-03-24 03:18:57 +08:00
|
|
|
OwningRewritePatternList patterns(&getContext());
|
|
|
|
populateLHLOToAffineConversionPattern(&getContext(), &patterns);
|
2021-02-05 23:40:43 +08:00
|
|
|
(void)applyPatternsAndFoldGreedily(func, std::move(patterns));
|
2020-07-07 04:57:00 +08:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
} // namespace
|
|
|
|
|
2020-07-29 07:12:08 +08:00
|
|
|
std::unique_ptr<OperationPass<FuncOp>> createLhloLegalizeToAffinePass() {
|
|
|
|
return std::make_unique<LhloLegalizeToAffinePass>();
|
2020-07-07 04:57:00 +08:00
|
|
|
}
|
|
|
|
|
2020-07-09 01:05:32 +08:00
|
|
|
} // namespace lmhlo
|
2020-07-07 04:57:00 +08:00
|
|
|
} // namespace mlir
|