595 lines
24 KiB
C++
595 lines
24 KiB
C++
//===- LowerContractToSVEPatterns.cpp - Contract to I8MM/BF16 ---*- C++ -*-===//
|
|
//
|
|
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
|
// See https://llvm.org/LICENSE.txt for license information.
|
|
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
//
|
|
// This file implements lowering patterns from vector.contract to operations
|
|
// that map to instructions from the SVE FEAT_I8MM and FEAT_BF16 extensions.
|
|
//
|
|
// TODO: There may be opportunities to unify this with a similar pattern
|
|
// for Neon. See:
|
|
// https://github.com/llvm/llvm-project/issues/145559
|
|
// LowerContractToNeonPatterns.cpp
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include "mlir/Dialect/Arith/IR/Arith.h"
|
|
#include "mlir/Dialect/ArmSVE/IR/ArmSVEDialect.h"
|
|
#include "mlir/Dialect/ArmSVE/Transforms/Transforms.h"
|
|
#include "mlir/Dialect/Func/IR/FuncOps.h"
|
|
#include "mlir/Dialect/UB/IR/UBOps.h"
|
|
#include "mlir/Dialect/Vector/IR/VectorOps.h"
|
|
#include "mlir/IR/AffineMap.h"
|
|
#include "mlir/IR/PatternMatch.h"
|
|
|
|
#include <cassert>
|
|
#include <numeric>
|
|
|
|
#define DEBUG_TYPE "lower-contract-to-arm-sve-i8mm"
|
|
|
|
using namespace mlir;
|
|
|
|
namespace {
|
|
// Get the operand of a `vector.contract`. This function is intended to abstract
|
|
// away from the particular way a value is extended before feeding it into the
|
|
// `vector.contract` - via zero-extend or an explicit or implicit sign-extend
|
|
// (for implicit sign-extension see `vector.contract` documentation).
|
|
//
|
|
// The template parameter `Op` indicates the extension operation (explicit or
|
|
// implicit) for which we are checking.
|
|
//
|
|
// Return success only for extensions from `i8` to `i32`.
|
|
template <typename Op>
|
|
std::optional<Value> getExtOperand(Value v) {
|
|
|
|
static_assert(llvm::is_one_of<Op, arith::ExtSIOp, arith::ExtUIOp>::value,
|
|
"Must be instantiated with either sign- or zero- extension op");
|
|
|
|
// If the operand is not defined by an explicit extend operation of the
|
|
// accepted operation type allow for an implicit sign-extension.
|
|
auto extOp = v.getDefiningOp<Op>();
|
|
if (!extOp) {
|
|
if constexpr (std::is_same<Op, arith::ExtSIOp>::value) {
|
|
auto vTy = cast<VectorType>(v.getType());
|
|
if (!vTy.getElementType().isSignlessInteger(8))
|
|
return {};
|
|
return v;
|
|
}
|
|
return {};
|
|
}
|
|
|
|
// If the operand is defined by an explicit extend operation of the accepted
|
|
// operation type, check it's extended from `i8` to `i32`.
|
|
auto inOp = extOp.getIn();
|
|
auto inTy = dyn_cast<VectorType>(inOp.getType());
|
|
if (!inTy || !inTy.getElementType().isSignlessInteger(8))
|
|
return {};
|
|
|
|
auto outTy = dyn_cast<VectorType>(extOp.getType());
|
|
if (!outTy || !outTy.getElementType().isSignlessInteger(32))
|
|
return {};
|
|
|
|
return inOp;
|
|
}
|
|
|
|
/// This class encapsulates the algorithm and parametrisation (in terms of types
|
|
/// and dimensions) of lowering a `vector.contract` to "primitive" matrix
|
|
/// multiplication operations of the SVE dialect (here "primitive" would mean
|
|
/// corresponding to a single target instruction).
|
|
///
|
|
/// Supported are lowering to FEAT_I8MM `smmla`, `ummla`, and `usmmla`, and to
|
|
/// FEAT_BF16 `bfmmla`. All the transformations are very similar to each other
|
|
/// for concreteness the description below is given for `smmla`.
|
|
///
|
|
/// The lowering triggers for a contraction operation that performs a matrix
|
|
/// multiply of two 8-bit integer matrix tiles with logical dimensions
|
|
/// <Mx8> and <8x[N]> for the left-hand side (LHS) and the right-hand side
|
|
/// (RHS), respectively, added to a 32-bit integer accumulator operand (ACC)
|
|
/// with dimensions <Mx[N]>, yielding a <Mx[N]> 32-bit integer result (OUT).
|
|
///
|
|
/// The operands' shapes are such that the operands can be evenly split into
|
|
/// sub-tiles with dimensions as expected by the targeted FEAT_I8MM
|
|
/// instructions. The intent is that M and N are chosen (by higher level
|
|
/// transforms) in such a way as to maximise register usage. The main use case
|
|
/// we envision as of now is MMT4D, thus the RHS operand is expected
|
|
/// pre-transposed.
|
|
///
|
|
/// The matrix multiplication is performed by unrolling the usual tiled matrix
|
|
/// multiplication algorithm using sub-tiles with dimensions <2x8> for the
|
|
/// LHS, <8x[2]> for the RHS, and <2x[2]> for the result and the input
|
|
/// accumulator.
|
|
///
|
|
/// One way to illustrate the operation is as follows:
|
|
///
|
|
/// RHS<8x[N]>: <8x[2]> <8x[2]> ... <8x[2]>
|
|
/// +-----------------------------
|
|
/// LHS<Mx8>: <2x8> | <2x[2]> <2x[2]> ... <2x[2]>
|
|
/// <2x8> | <2x[2]> <2x[2]> ... <2x[2]>
|
|
/// ... | ... ... ... ...
|
|
/// <2x8> | <2x[2]> <2x[2]> ... <2x[2]>
|
|
///
|
|
/// The RHS operand is unpacked into N/2 values, each representing a sequence
|
|
/// of VSCALE number of sub-tiles with dimensions <8x2>.
|
|
/// The LHS operand is initially unpacked into M/2 values, each representing a
|
|
/// sub-tile with dimensions <2x8>, and then each such sub-tile is replicated
|
|
/// VSCALE times. Multiplying thus replicated LHS sub-tile by the corresponding
|
|
/// RHS sub-tile correctly computes an entire result sub-tile.
|
|
/// The 2x2 sub-tiles of the ACC and OUT have rows that are not adjacent
|
|
/// (in memory or when imposing a row-major layout on the 2D vector value).
|
|
/// Reading the ACC is implemented as reading two consecutive rows and
|
|
/// interleaving the by pairs to obtain a vector having length twice the length
|
|
/// of an ACC row. This vector now is a sequence of one-dimensional tiles with
|
|
/// the exact layout needed by the `smmla`/`bfmmla`/etc instructions, which
|
|
/// tiles are extracted one by one. For illustration, if we have an 2x4 ACC tile
|
|
/// a0 a1 b0 b1
|
|
/// a2 a3 b2 b3
|
|
/// we read the two rows as separate values and then interleave by pairs
|
|
/// to obtain
|
|
/// a0 a1 a2 a3 b0 b1 b2 b3
|
|
/// from which we extract `a0 a1 a2 a3` and `b0 b1 b2 b3`.
|
|
///
|
|
/// Writing the OUT tile is done by the reverse of the above procedure,
|
|
/// concatenate two "flattened" sub-tiles into
|
|
/// c0 c1 c2 c3 d0 d1 d2 d3
|
|
/// deinterleave by pairs to obtain as separate values
|
|
/// c0 c1 d0 d1
|
|
/// c2 c3 d2 d3
|
|
/// which are then inserted into the final result.
|
|
///
|
|
/// Multiplication of a signed LHS by an unsigned LHS is performed by
|
|
/// swapping the order of the operands and emitting an `usmmla` (since there
|
|
/// isn't an `summla` instruction). Therefore each ACC sub-tile needs
|
|
/// to be transposed before the addition and the sum, an OUT sub-tile,
|
|
/// needs to be transposed before insertion into the final result.
|
|
/// This is done very elegantly by a modification of the above to
|
|
/// interleave/deinterleave not by pairs, but by individual elements, e.g.
|
|
/// after ordinary interleave we obtain
|
|
/// a0 a2 a1 a3 b0 b2 b1 b3
|
|
/// which is exactly the desired layout of having each individual 2x2 tile
|
|
/// transposed.
|
|
///
|
|
/// All of the above readily applies to FEAT_BF16 `bfmmla` with the
|
|
/// difference that the shapes of the LHS, RHS are <Mx4>, <4x[M]>, and
|
|
/// respectively, that is the "K" dimension is fixed to 4, instead of 8 (like
|
|
/// for the integer case).
|
|
class VectorContractRewriter {
|
|
protected:
|
|
// Designate the operation (resp. instruction) used to do sub-tile matrix
|
|
// multiplications.
|
|
enum class MMLA {
|
|
Nop,
|
|
SignedInt, // smmla
|
|
UnsignedInt, // ummla
|
|
MixedInt, // usmmla
|
|
Bfloat // bfmmla
|
|
};
|
|
|
|
// Lower-level operation to be emitted.
|
|
MMLA mmlaOp = MMLA::Nop;
|
|
|
|
// Indicate if the operands for the ArmSVE dialect operation need to be
|
|
// swapped. Currently this is needed in order to emulate an "summla"
|
|
// operation.
|
|
bool swapOperands = false;
|
|
|
|
// The operand tiles. These are not necessarily the operends of
|
|
// `vector.contract`, for example they could be operands to `arith.extsi`
|
|
// that is in turn fed into `vector.contract`.
|
|
Value lhs;
|
|
Value rhs;
|
|
Value acc;
|
|
|
|
// Conventional names for matrix dimensions.
|
|
int64_t m = 0;
|
|
int64_t n = 0;
|
|
int64_t k = 0;
|
|
|
|
// Create the matrix mulitply and accumulate operation according to
|
|
// `mmlaOp`.
|
|
Value createMMLA(PatternRewriter &rewriter, Location loc, Value acc,
|
|
Value lhs, Value rhs);
|
|
|
|
// Check general preconditions for applying the transformation, common to the
|
|
// integer and the bfloat16 case.
|
|
LogicalResult match(vector::ContractionOp op, PatternRewriter &rewriter);
|
|
|
|
public:
|
|
VectorContractRewriter() = default;
|
|
|
|
// Do the actuall rewrite. This member function is shared by both integer and
|
|
// bfloat16 rewrites.
|
|
Value lower(vector::ContractionOp op, PatternRewriter &rewriter);
|
|
};
|
|
|
|
Value VectorContractRewriter::createMMLA(PatternRewriter &rewriter,
|
|
Location loc, Value acc, Value lhs,
|
|
Value rhs) {
|
|
|
|
Type resTy = acc.getType();
|
|
if (swapOperands)
|
|
std::swap(lhs, rhs);
|
|
|
|
switch (mmlaOp) {
|
|
case MMLA::SignedInt:
|
|
return arm_sve::SmmlaOp::create(rewriter, loc, resTy, acc, lhs, rhs);
|
|
case MMLA::UnsignedInt:
|
|
return arm_sve::UmmlaOp::create(rewriter, loc, resTy, acc, lhs, rhs);
|
|
case MMLA::MixedInt:
|
|
return arm_sve::UsmmlaOp::create(rewriter, loc, resTy, acc, lhs, rhs);
|
|
case MMLA::Bfloat:
|
|
return arm_sve::BfmmlaOp::create(rewriter, loc, resTy, acc, lhs, rhs);
|
|
default:
|
|
llvm_unreachable("Uninitialized operation kind");
|
|
}
|
|
}
|
|
|
|
LogicalResult VectorContractRewriter::match(vector::ContractionOp op,
|
|
PatternRewriter &rewriter) {
|
|
// Check iterator types for matrix multiplication.
|
|
auto itTypes = op.getIteratorTypesArray();
|
|
if (itTypes.size() != 3 || itTypes[0] != vector::IteratorType::parallel ||
|
|
itTypes[1] != vector::IteratorType::parallel ||
|
|
itTypes[2] != vector::IteratorType::reduction)
|
|
return rewriter.notifyMatchFailure(
|
|
op, "iterator types do not correspond to matrix multiplication");
|
|
|
|
// Check permutation maps. For now only accept
|
|
// lhs: (d0, d1, d2) -> (d0, d2)
|
|
// rhs: (d0, d1, d2) -> (d1, d2)
|
|
// acc: (d0, d1, d2) -> (d0, d1)
|
|
// This corresponds to matrix multiplication with transposed RHS.
|
|
if (op.getIndexingMapsArray()[0] !=
|
|
AffineMap::getMultiDimMapWithTargets(3, ArrayRef{0u, 2u},
|
|
op.getContext()) ||
|
|
op.getIndexingMapsArray()[1] !=
|
|
AffineMap::getMultiDimMapWithTargets(3, ArrayRef{1u, 2u},
|
|
op.getContext()) ||
|
|
op.getIndexingMapsArray()[2] != AffineMap::getMultiDimMapWithTargets(
|
|
3, ArrayRef{0u, 1u}, op.getContext()))
|
|
return rewriter.notifyMatchFailure(op, "non-matching permutation maps");
|
|
|
|
// Check the combining kind is addition.
|
|
if (op.getKind() != vector::CombiningKind::ADD)
|
|
return rewriter.notifyMatchFailure(op, "combining kind is not an addition");
|
|
|
|
return success();
|
|
}
|
|
|
|
Value VectorContractRewriter::lower(vector::ContractionOp op,
|
|
PatternRewriter &rewriter) {
|
|
|
|
// Initialize some helper types.
|
|
Type operandEltType = cast<VectorType>(lhs.getType()).getElementType();
|
|
Type resultEltType = cast<VectorType>(op.getResultType()).getElementType();
|
|
|
|
const int64_t numOperandSubTileElts =
|
|
128 / operandEltType.getIntOrFloatBitWidth();
|
|
|
|
assert(resultEltType.getIntOrFloatBitWidth() == 32 &&
|
|
"Only implemented for i32 or f32 output");
|
|
const int64_t numResultSubTileElts = 4;
|
|
|
|
// Single-dimensional vector types for the operands of the ArmSVE dialect
|
|
// op.
|
|
auto flatLhsType =
|
|
VectorType::get(/*shape=*/numOperandSubTileElts, operandEltType,
|
|
/*scalableDims=*/{true});
|
|
auto flatRhsType =
|
|
VectorType::get(/*shape=*/numOperandSubTileElts, operandEltType,
|
|
/*scalableDims=*/{true});
|
|
auto flatAccType =
|
|
VectorType::get(/*shape=*/numResultSubTileElts, resultEltType,
|
|
/*scalableDims=*/{true});
|
|
|
|
// Single-dimension vector type for the entire RHS tile.
|
|
|
|
auto flatRhsTileType = VectorType::get(/*shape=*/k * n, operandEltType,
|
|
/*scalableDims=*/{true});
|
|
|
|
// Vector type having the same number of elements as a row in the
|
|
// accumulator/output tile and the same element type.
|
|
auto accRowTy = VectorType::get(/*shape=*/n, resultEltType,
|
|
/*scalableDims=*/{true});
|
|
|
|
// Vector type having twice the number of elements as a row in the
|
|
// accumulator/output tile the same element type.
|
|
auto accRowX2Ty = VectorType::get(/*shape=*/2 * n, resultEltType,
|
|
/*scalableDims=*/{true});
|
|
// Vector type having half the number of elements as a row in the
|
|
// accumulator/output tile and an integer element type with twice the bit
|
|
// width.
|
|
auto accRow64Ty = VectorType::get(/*shape=*/n / 2, rewriter.getI64Type(),
|
|
/*scalableDims=*/{true});
|
|
// Vector type having the same the number of elements as a row in the
|
|
// accumulator/output tile and an integer element type with twice the bit
|
|
// width.
|
|
auto accRowX264Ty = VectorType::get(/*shape=*/n, rewriter.getI64Type(),
|
|
/*scalableDims=*/{true});
|
|
|
|
Location loc = op.getLoc();
|
|
|
|
// Extract LHS sub-tiles with logical shape <2xK>.
|
|
SmallVector<Value> lhsTile;
|
|
for (int64_t i = 0; i < m; i += 2) {
|
|
// Extract two consecutive rows of the LHS tile.
|
|
auto r0 =
|
|
vector::ExtractOp::create(rewriter, loc, lhs, ArrayRef<int64_t>{i});
|
|
auto r1 =
|
|
vector::ExtractOp::create(rewriter, loc, lhs, ArrayRef<int64_t>{i + 1});
|
|
// Concatenate to obtain a 2 x K x <input-type> flattened sub-tile.
|
|
SmallVector<int64_t> shuffleIdx(2 * k);
|
|
std::iota(shuffleIdx.begin(), shuffleIdx.end(), 0);
|
|
auto t = vector::ShuffleOp::create(rewriter, loc, r0, r1, shuffleIdx);
|
|
// Turn it into a scalable vector.
|
|
auto s = vector::ScalableInsertOp::create(
|
|
rewriter, loc, t, ub::PoisonOp::create(rewriter, loc, flatLhsType), 0);
|
|
// Replicate the sub-tile VSCALE times to fill the entire vector.
|
|
auto r = arm_sve::DupQLaneOp::create(rewriter, loc, s, 0);
|
|
lhsTile.push_back(r);
|
|
}
|
|
|
|
// "Flatten" the RHS tile from <[N]xK> to <[N*K]>.
|
|
auto rhs = vector::ShapeCastOp::create(rewriter, this->rhs.getLoc(),
|
|
flatRhsTileType, this->rhs);
|
|
|
|
// Extract the RHS sub-tiles with logical shape <Kx[2]>.
|
|
SmallVector<Value> rhsTile;
|
|
for (int64_t j = 0; j < n; j += 2)
|
|
rhsTile.push_back(vector::ScalableExtractOp::create(
|
|
rewriter, loc, flatRhsType, rhs, j * k));
|
|
|
|
// Extract and pack the ACC sub-tiles.
|
|
SmallVector<Value> accTile;
|
|
for (int64_t i = 0; i < m; i += 2) {
|
|
// Extract two consecutive rows of the accumulator tile.
|
|
auto r0 = vector::ExtractOp::create(rewriter, loc, op.getAcc(),
|
|
ArrayRef<int64_t>{i});
|
|
auto r1 = vector::ExtractOp::create(rewriter, loc, op.getAcc(),
|
|
ArrayRef<int64_t>{i + 1});
|
|
Value accTileVec;
|
|
if (swapOperands) {
|
|
// We are performing the operation with swapped LHS and RHS we need to
|
|
// transpose each individual 2x2 tile of the accumulator and (later) the
|
|
// final result.
|
|
accTileVec = vector::InterleaveOp::create(rewriter, loc, r0, r1);
|
|
} else {
|
|
// Bitcast accumulator rows to double-width integer elements, so
|
|
// subsequent interleave/deinterleave work on pairs of elements.
|
|
auto r0I64 = vector::BitCastOp::create(rewriter, loc, accRow64Ty, r0);
|
|
auto r1I64 = vector::BitCastOp::create(rewriter, loc, accRow64Ty, r1);
|
|
|
|
// Interleave the rows, effectively flattening each 2x2 tile into 4
|
|
// consecutive elements.
|
|
auto intrI64 = vector::InterleaveOp::create(rewriter, loc, r0I64, r1I64);
|
|
|
|
// Bitcast back to original element type.
|
|
accTileVec =
|
|
vector::BitCastOp::create(rewriter, loc, accRowX2Ty, intrI64);
|
|
}
|
|
// Extract ACC sub-tiles.
|
|
for (int64_t j = 0; j < n; j += 2)
|
|
accTile.push_back(vector::ScalableExtractOp::create(
|
|
rewriter, loc, flatAccType, accTileVec, j * 2));
|
|
}
|
|
|
|
// Emit sub-tile matrix multiplications.
|
|
SmallVector<Value> outTile;
|
|
for (int64_t i = 0; i < m / 2; ++i)
|
|
for (int64_t j = 0; j < n / 2; ++j) {
|
|
Value mmla = createMMLA(rewriter, loc, accTile[i * n / 2 + j], lhsTile[i],
|
|
rhsTile[j]);
|
|
outTile.push_back(mmla);
|
|
}
|
|
|
|
// Unpack the OUT sub-tiles and insert into the result.
|
|
Value result = ub::PoisonOp::create(rewriter, loc, op.getResultType());
|
|
for (int64_t i = 0; i < m / 2; ++i) {
|
|
// Collect a number of sub-tiles in a row.
|
|
Value row = ub::PoisonOp::create(rewriter, loc, accRowX2Ty);
|
|
for (int64_t j = 0; j < n / 2; ++j)
|
|
row = vector::ScalableInsertOp::create(
|
|
rewriter, loc, outTile[i * n / 2 + j], row, j * 4);
|
|
|
|
// Unpack the row to obtain two rows of the output. If we have the out
|
|
// sub-tiles transposed we obtain two consecutive output rows by
|
|
// separating even and odd elements, i.e. a simple deinterleave.
|
|
// Otherwise, the interleave is by pairs.
|
|
Value out0, out1;
|
|
if (swapOperands) {
|
|
auto tmp = vector::DeinterleaveOp::create(rewriter, loc, row);
|
|
out0 = tmp.getRes1();
|
|
out1 = tmp.getRes2();
|
|
} else {
|
|
// Deinterleave by pairs.
|
|
auto row64 = vector::BitCastOp::create(rewriter, loc, accRowX264Ty, row);
|
|
auto deintr64 = vector::DeinterleaveOp::create(rewriter, loc, row64);
|
|
|
|
// Bitcast back into original element type and insert into the result.
|
|
out0 = vector::BitCastOp::create(rewriter, loc, accRowTy,
|
|
deintr64.getRes1());
|
|
out1 = vector::BitCastOp::create(rewriter, loc, accRowTy,
|
|
deintr64.getRes2());
|
|
}
|
|
result = vector::InsertOp::create(rewriter, loc, out0, result, i * 2);
|
|
result = vector::InsertOp::create(rewriter, loc, out1, result, i * 2 + 1);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
class VectorContractRewriterI8MM : public VectorContractRewriter {
|
|
public:
|
|
// Check the specific preconditions for the integer case. Initialise
|
|
// parametrisation types and dimensions.
|
|
LogicalResult matchAndInit(vector::ContractionOp op,
|
|
PatternRewriter &rewriter) {
|
|
if (failed(match(op, rewriter)))
|
|
return failure();
|
|
|
|
VectorType lhsType = op.getLhsType();
|
|
VectorType rhsType = op.getRhsType();
|
|
|
|
m = lhsType.getDimSize(0);
|
|
n = rhsType.getDimSize(0);
|
|
k = rhsType.getDimSize(1);
|
|
|
|
// Check the operands have the expected shape:
|
|
// * for LHS: fixed vector MxK
|
|
// * for RHS: scalable vector [N]xK
|
|
// * K == 8
|
|
// * M and N even and at least 2
|
|
if (lhsType.isScalable() || !rhsType.getScalableDims()[0] ||
|
|
rhsType.getScalableDims()[1] || lhsType.getDimSize(1) != k || k != 8 ||
|
|
m < 2 || m % 2 != 0 || n < 2 || n % 2 != 0 ||
|
|
!rhsType.getScalableDims()[0])
|
|
return rewriter.notifyMatchFailure(op, "non-matching operand shape");
|
|
|
|
// Check the output is a vector of i32 elements.
|
|
auto outTy = dyn_cast<VectorType>(op.getResultType());
|
|
if (!outTy || outTy.getElementType() != rewriter.getI32Type())
|
|
return rewriter.notifyMatchFailure(op,
|
|
"output type is not a vector of i32");
|
|
|
|
// Check inputs are sign-/zero- extensions from i8 to i32. Get the values
|
|
// before the extension. All four signed/unsigned combinations for input
|
|
// operands are supported, but they are lowered to different operations.
|
|
// Determine which is the appropriate operation to lower to.
|
|
mmlaOp = MMLA::SignedInt;
|
|
swapOperands = false;
|
|
auto maybeLhs = getExtOperand<arith::ExtSIOp>(op.getLhs());
|
|
if (!maybeLhs) {
|
|
mmlaOp = MMLA::UnsignedInt;
|
|
maybeLhs = getExtOperand<arith::ExtUIOp>(op.getLhs());
|
|
}
|
|
if (!maybeLhs)
|
|
return rewriter.notifyMatchFailure(
|
|
op, "LHS is not a sign- or zero- extended i8");
|
|
|
|
auto maybeRhs = getExtOperand<arith::ExtSIOp>(op.getRhs());
|
|
if (maybeRhs) {
|
|
if (mmlaOp == MMLA::UnsignedInt)
|
|
mmlaOp = MMLA::MixedInt;
|
|
} else {
|
|
if (mmlaOp == MMLA::SignedInt) {
|
|
mmlaOp = MMLA::MixedInt;
|
|
swapOperands = true;
|
|
}
|
|
maybeRhs = getExtOperand<arith::ExtUIOp>(op.getRhs());
|
|
}
|
|
if (!maybeRhs)
|
|
return rewriter.notifyMatchFailure(
|
|
op, "RHS is not a sign- or zero- extended i8");
|
|
|
|
// Initialise algorithm parameters.
|
|
lhs = *maybeLhs;
|
|
rhs = *maybeRhs;
|
|
acc = op.getAcc();
|
|
|
|
return success();
|
|
}
|
|
};
|
|
|
|
class VectorContractRewriterBfloat : public VectorContractRewriter {
|
|
public:
|
|
// Check the specific preconditions for the bfloat16 case. Initialise
|
|
// parametrisation types and dimensions.
|
|
LogicalResult matchAndInit(vector::ContractionOp op,
|
|
PatternRewriter &rewriter) {
|
|
if (failed(match(op, rewriter)))
|
|
return failure();
|
|
|
|
VectorType lhsType = op.getLhsType();
|
|
VectorType rhsType = op.getRhsType();
|
|
|
|
m = lhsType.getDimSize(0);
|
|
n = rhsType.getDimSize(0);
|
|
k = rhsType.getDimSize(1);
|
|
|
|
// Check the operands have the expected shape:
|
|
// * for LHS: fixed vector MxK
|
|
// * for RHS: scalable vector [N]xK
|
|
// * K == 4
|
|
// * M and N even and at least 2
|
|
if (lhsType.isScalable() || !rhsType.getScalableDims()[0] ||
|
|
rhsType.getScalableDims()[1] || lhsType.getDimSize(1) != k || k != 4 ||
|
|
m < 2 || m % 2 != 0 || n < 2 || n % 2 != 0 ||
|
|
!rhsType.getScalableDims()[0])
|
|
return rewriter.notifyMatchFailure(op, "non-matching operand shape");
|
|
|
|
// Check the output is a vector of Float32 elements.
|
|
auto outTy = dyn_cast<VectorType>(op.getResultType());
|
|
if (!outTy || outTy.getElementType() != rewriter.getF32Type())
|
|
return rewriter.notifyMatchFailure(op,
|
|
"output type is not a vector of f32");
|
|
|
|
// Check the inputs are vectors of BFloat16 elements.
|
|
if (lhsType.getElementType() != rewriter.getBF16Type())
|
|
return rewriter.notifyMatchFailure(op,
|
|
"input type is not a vector of bf16");
|
|
|
|
// Initialise algorithm parameters.
|
|
mmlaOp = MMLA::Bfloat;
|
|
swapOperands = false;
|
|
lhs = op.getLhs();
|
|
rhs = op.getRhs();
|
|
acc = op.getAcc();
|
|
|
|
return success();
|
|
}
|
|
};
|
|
|
|
class LowerContractionToSVEI8MMPattern
|
|
: public OpRewritePattern<vector::ContractionOp> {
|
|
public:
|
|
using OpRewritePattern::OpRewritePattern;
|
|
LogicalResult matchAndRewrite(vector::ContractionOp op,
|
|
PatternRewriter &rewriter) const override {
|
|
|
|
// Match i8xi8 -> i32 matrix multiply and accumulate.
|
|
VectorContractRewriterI8MM vcr;
|
|
if (failed(vcr.matchAndInit(op, rewriter)))
|
|
return failure();
|
|
|
|
Value result = vcr.lower(op, rewriter);
|
|
rewriter.replaceOp(op, result);
|
|
|
|
return success();
|
|
}
|
|
};
|
|
|
|
class LowerContractionToSVEBFMMLAPattern
|
|
: public OpRewritePattern<vector::ContractionOp> {
|
|
public:
|
|
using OpRewritePattern::OpRewritePattern;
|
|
LogicalResult matchAndRewrite(vector::ContractionOp op,
|
|
PatternRewriter &rewriter) const override {
|
|
|
|
// Match bf16xbf16 -> f32 matrix multiply and accumulate.
|
|
VectorContractRewriterBfloat vcr;
|
|
if (failed(vcr.matchAndInit(op, rewriter)))
|
|
return failure();
|
|
|
|
Value result = vcr.lower(op, rewriter);
|
|
rewriter.replaceOp(op, result);
|
|
|
|
return success();
|
|
}
|
|
};
|
|
|
|
} // namespace
|
|
|
|
void mlir::populateLowerContractionToSVEI8MMPatterns(
|
|
RewritePatternSet &patterns) {
|
|
MLIRContext *context = patterns.getContext();
|
|
patterns.add<LowerContractionToSVEI8MMPattern>(context, /*benefit=*/2);
|
|
}
|
|
|
|
void mlir::populateLowerContractionToSVEBFMMLAPatterns(
|
|
RewritePatternSet &patterns) {
|
|
MLIRContext *context = patterns.getContext();
|
|
patterns.add<LowerContractionToSVEBFMMLAPattern>(context, /*benefit=*/2);
|
|
}
|