[Mlir-commits] [mlir] [mlir][linalg] Scaled contraction op (PR #203958)
Adam Siemieniuk
llvmlistbot at llvm.org
Wed Aug 12 06:27:01 PDT 2026
https://github.com/adam-smnk updated https://github.com/llvm/llvm-project/pull/203958
>From a4743274aa2b3612d7a0a32a4d02b6e831f66717 Mon Sep 17 00:00:00 2001
From: Adam Siemieniuk <adam.siemieniuk at intel.com>
Date: Thu, 7 May 2026 14:44:03 +0200
Subject: [PATCH] [mlir][linalg] Scaled contraction op
A new contraction op with extended semantics to represent input
scaling using additional scale value as described by their
corresponding indexing maps.
RFC: https://discourse.llvm.org/t/rfc-linalg-scaled-contraction/91075
Assisted-by: Claude
Co-authored-by: Md Asghar Ahmad Shahid <md.asghar.ahmad.shahid at intel.com>
---
.../Dialect/Linalg/IR/LinalgStructuredOps.td | 144 ++++++
mlir/lib/Dialect/Linalg/IR/LinalgOps.cpp | 430 +++++++++++++++---
.../Dialect/Linalg/generalize-named-ops.mlir | 139 ++++++
mlir/test/Dialect/Linalg/invalid.mlir | 187 ++++++++
mlir/test/Dialect/Linalg/loops.mlir | 58 +++
mlir/test/Dialect/Linalg/named-ops.mlir | 162 +++++++
mlir/test/Dialect/Linalg/roundtrip.mlir | 28 ++
.../Dialect/Linalg/transform-op-fuse.mlir | 175 +++++++
.../Dialect/Linalg/transform-op-tile.mlir | 196 ++++++++
9 files changed, 1462 insertions(+), 57 deletions(-)
diff --git a/mlir/include/mlir/Dialect/Linalg/IR/LinalgStructuredOps.td b/mlir/include/mlir/Dialect/Linalg/IR/LinalgStructuredOps.td
index fa8125f280db2..e322efe749b9d 100644
--- a/mlir/include/mlir/Dialect/Linalg/IR/LinalgStructuredOps.td
+++ b/mlir/include/mlir/Dialect/Linalg/IR/LinalgStructuredOps.td
@@ -1218,6 +1218,150 @@ def BatchReduceMatmulOp : LinalgStructuredBase_Op<"batch_reduce_matmul", [
}];
}
+//===----------------------------------------------------------------------===//
+// Scaled Contract op.
+//===----------------------------------------------------------------------===//
+
+def ScaledContractOp : LinalgStructuredBase_Op<"scaled_contract", [
+ AttrSizedOperandSegments]> {
+ let summary = [{
+ Perform a scaled contraction on two inputs where their scaling is described
+ by affine maps of the two corresponding scales, accumulating into the output.
+ }];
+ let description = [{
+ Extends the semantics of `linalg.contract` by including extra scaling factors
+ for the inputs `A` and `B` described by the scales' affine maps.
+ The data types of the inputs are chosen independently from the scales.
+
+ The semantics of contracting inputs `A` and `B` with scales `scale_A` and
+ `scale_B` on top of `C` to produce output `D` is given by:
+
+ `D[H] = (SUM_{(I ∪ J) \ H} (A[I] * scale_A[I]) * (B[J] * scale_B[J])) + C[H]`
+
+ The iteration type of each dim is inferred.
+
+ The input operands `A`, `B`, and `C` follow the standard contraction semantics
+ together with broadcasting and transposition rules.
+ See `linalg.contract` for further details.
+
+ **Affine maps for scales**
+
+ Each scale indexing map describes the scaling scheme for the corresponding
+ input `A` and `B`. The following scaling schemes are supported:
+
+ - Tensor scaling - no dimensions: a scalar value used to scale the whole input.
+ - Dimension scaling - dimension `d`: one scale value per element along `d`.
+ - Block scaling - binary expression `d floordiv B`: one scale value for every
+ `B` elements along `d` where `B` corresponds to the block size.
+
+ Scale maps may only use dimensions present in the corresponding input's map.
+ A dimension absent from the scale map indicates that a single scale value is
+ reused across the whole input dimension.
+
+ **Dim sizes for scales**
+
+ - Tensor scaling: a unit-size scale.
+ - Dimension scaling (`d`): the scale dim size equals the input dim size.
+ - Block scaling (`d floordiv B`): the scale dim size equals
+ `ceil(input_dim_size / B)`.
+
+ For dynamic shapes, all sizes are assumed to be correct at runtime.
+
+ Example scaled matmul with transposed B:
+ ```mlir
+ %D = linalg.scaled_contract
+ indexing_maps = [
+ affine_map<(m, n, k) -> (m, k)>,
+ affine_map<(m, n, k) -> (m floordiv 32, k floordiv 128)>, // A - 32x128 block scale
+ affine_map<(m, n, k) -> (n, k)>,
+ affine_map<(m, n, k) -> (n)>, // B - row-wise scale
+ affine_map<(m, n, k) -> (m, n)>]
+ ins(%A, %scale_A, %B, %scale_B
+ : tensor<200x512xf8E5M2>, tensor<7x4xf8E8M0FNU>,
+ tensor<128x512xf8E5M2>, tensor<128xf8E8M0FNU>)
+ outs(%C: tensor<200x128xf32>) -> tensor<200x128xf32>
+ ```
+
+ Numeric casting is performed on the inputs and scales, promoting/truncating
+ them to the same data type as the accumulator/output.
+
+ TODO: Allow control over the combining/accumulating op and possibly the
+ multiplication op.
+ }];
+
+ let arguments = (ins
+ Variadic<AnyType>:$inputs,
+ Variadic<AnyShaped>:$outputs,
+ AffineMapArrayAttr:$indexing_maps,
+ DefaultValuedOptionalAttr<TypeFnAttr, "TypeFn::cast_signed">:$cast
+ );
+ let results = (outs Variadic<AnyShaped>:$result_tensors);
+ let regions = (region SizedRegion<1>:$combiner);
+
+ let skipDefaultBuilders = 1;
+ let builders = [
+ OpBuilder<
+ (ins "ValueRange":$inputs, "ValueRange":$outputs,
+ CArg<"ArrayRef<NamedAttribute>", "{}">:$attributes),
+ [{
+ buildStructuredOp($_builder, $_state, std::nullopt, inputs, outputs,
+ attributes, regionBuilder);
+ }]>,
+ OpBuilder<(ins "TypeRange":$resultTensorTypes, "ValueRange":$inputs,
+ "ValueRange":$outputs, "ArrayAttr":$indexingMaps,
+ CArg<"ArrayRef<NamedAttribute>", "{}">:$attributes),
+ [{
+ $_state.addAttribute("indexing_maps", indexingMaps);
+ buildStructuredOp($_builder, $_state, resultTensorTypes, inputs,
+ outputs, attributes, regionBuilder);
+ }]>,
+ OpBuilder<(ins "ValueRange":$inputs, "ValueRange":$outputs,
+ "ArrayAttr":$indexingMaps,
+ CArg<"ArrayRef<NamedAttribute>", "{}">:$attributes),
+ [{
+ $_state.addAttribute("indexing_maps", indexingMaps);
+ buildStructuredOp($_builder, $_state, std::nullopt, inputs, outputs,
+ attributes, regionBuilder);
+ }]>
+ ];
+ let hasCustomAssemblyFormat = 1;
+ let hasFolder = 1;
+ let hasVerifier = 1;
+
+ let extraClassDeclaration = structuredOpsBaseDecls # [{
+ // Declare/implement functions necessary for LinalgStructuredInterface.
+
+ /// Infer iterator types for each dim in the domain of IndexingMaps.
+ SmallVector<utils::IteratorType> getIteratorTypesArray();
+
+ /// IndexingMaps always depends on attr associated to current Op instance.
+ bool hasDynamicIndexingMaps() { return true; };
+ bool hasUserDefinedMaps() { return true; };
+
+ static unsigned getNumRegionArgs();
+
+ static void regionBuilder(ImplicitLocOpBuilder &b,
+ Block &block, ArrayRef<NamedAttribute> attrs,
+ function_ref<InFlightDiagnostic()> emitError);
+
+ static std::function<void(ImplicitLocOpBuilder &,
+ Block &, ArrayRef<NamedAttribute>,
+ function_ref<InFlightDiagnostic()>)>
+ getRegionBuilder() {
+ return regionBuilder;
+ }
+
+ std::string getLibraryCallName() {
+ return "op_has_no_registered_library_name";
+ }
+
+ // Implement function necessary for DestinationStyleOpInterface.
+ ::mlir::MutableOperandRange getDpsInitsMutable() {
+ return getOutputsMutable();
+ }
+ }];
+}
+
//===----------------------------------------------------------------------===//
// Named Linalg ops, implemented as a declarative configurations of generic ops.
//===----------------------------------------------------------------------===//
diff --git a/mlir/lib/Dialect/Linalg/IR/LinalgOps.cpp b/mlir/lib/Dialect/Linalg/IR/LinalgOps.cpp
index 170e1edf8a55d..10a9c478a6c7b 100644
--- a/mlir/lib/Dialect/Linalg/IR/LinalgOps.cpp
+++ b/mlir/lib/Dialect/Linalg/IR/LinalgOps.cpp
@@ -4584,65 +4584,67 @@ void ContractOp::print(OpAsmPrinter &p) {
/*elidedAttrs=*/{"indexing_maps", "operandSegmentSizes"});
}
-LogicalResult ContractOp::verify() {
- int iterationSpaceDims = -1;
- // Map iter space dims to #occurrences in inputs' and output's affine_maps:
- // e.g., inOccurrences[0] will hold #times that dim (with index) 0 is used to
- // access an input operand (so occurrence count can be at most 2) and
- // outOccurrences[1] will indicate whether dim 1 occurred in the output, etc.
- SmallVector<size_t> inOccurrences;
- SmallVector<size_t> outOccurrences;
-
- // A helper so that for each operand's affine_map and type we check that ...
- auto checkAffineMapAndType = [&](AffineMap affineMap, Type operandType,
- bool isInput) -> LogicalResult {
- // ... the affine_map is a projected permutation;
- if (!affineMap.isProjectedPermutation())
- return emitError("provided affine_map is not a projected permutation");
-
- // ... the rank of the affine_map's results and corresponding type match;
- if (auto shapedType = dyn_cast<ShapedType>(operandType)) {
- if (affineMap.getNumResults() != shapedType.getRank())
- return emitError("ranks of shaped operand and results of corresponding "
- "affine_map differ");
- } else if (affineMap.getNumResults() != 0) {
- return emitError("affine_map specifies shaped access while operand has "
- "non-shaped type");
- }
-
- // ... the rank of the affine_map's domain is the same as those seen prior;
- if (iterationSpaceDims == -1) {
- iterationSpaceDims = affineMap.getNumDims();
- inOccurrences = SmallVector<size_t>(iterationSpaceDims, 0);
- outOccurrences = SmallVector<size_t>(iterationSpaceDims, 0);
- } else if (iterationSpaceDims != (int)affineMap.getNumDims()) {
- return emitError("iteration spaces of provided affine_maps differ");
- }
-
- // ... update counts of dims used to access either an input or the output.
- for (AffineExpr affineExpr : affineMap.getResults()) {
- auto affineDimExpr = dyn_cast<AffineDimExpr>(affineExpr);
- if (!affineDimExpr)
- llvm_unreachable("affine_map is a projected permutation");
-
- if (isInput)
- inOccurrences[affineDimExpr.getPosition()] += 1;
- else
- outOccurrences[affineDimExpr.getPosition()] += 1;
- }
-
- return success();
- };
-
- for (auto &&[affineMap, operandType, isInput] :
- llvm::zip(getIndexingMapsArray(), getOperandTypes(),
- SmallVector<bool>{true, true, false})) {
- if (failed(checkAffineMapAndType(affineMap, operandType, isInput)))
- return failure(); // NB: checkAffineMapAndType will emit relevant error.
+/// Validate contraction operands indexing maps and shapes.
+/// For a given affine_map and type, checks that:
+/// - the affine_map is a projected permutation;
+/// - the rank of the affine_map's results and the corresponding type match;
+/// - the rank of the affine_map's domain is consistent with prior maps.
+/// Also updates the per-dim input/output occurrence counts.
+static LogicalResult
+checkContractionAffineMapAndType(AffineMap affineMap, Type operandType,
+ bool isInput, int &iterationSpaceDims,
+ SmallVector<size_t> &inOccurrences,
+ SmallVector<size_t> &outOccurrences,
+ function_ref<InFlightDiagnostic()> emitError) {
+ if (!affineMap.isProjectedPermutation())
+ return emitError() << "provided affine_map is not a projected permutation";
+
+ if (auto shapedType = dyn_cast<ShapedType>(operandType)) {
+ if (affineMap.getNumResults() != shapedType.getRank())
+ return emitError()
+ << "ranks of shaped operand and results of corresponding "
+ "affine_map differ";
+ } else if (affineMap.getNumResults() != 0) {
+ return emitError()
+ << "affine_map specifies shaped access while operand has "
+ "non-shaped type";
+ }
+
+ if (iterationSpaceDims == -1) {
+ iterationSpaceDims = affineMap.getNumDims();
+ inOccurrences = SmallVector<size_t>(iterationSpaceDims, 0);
+ outOccurrences = SmallVector<size_t>(iterationSpaceDims, 0);
+ } else if (iterationSpaceDims != (int)affineMap.getNumDims()) {
+ return emitError() << "iteration spaces of provided affine_maps differ";
+ }
+
+ // Update counts of dims used to access either an input or the output.
+ for (AffineExpr affineExpr : affineMap.getResults()) {
+ auto affineDimExpr = dyn_cast<AffineDimExpr>(affineExpr);
+ if (!affineDimExpr)
+ llvm_unreachable("affine_map is a projected permutation");
+
+ if (isInput)
+ inOccurrences[affineDimExpr.getPosition()] += 1;
+ else
+ outOccurrences[affineDimExpr.getPosition()] += 1;
}
+ return success();
+}
+
+/// Validates the contracting dimension constraints given the per-dim
+/// occurrence counts. Checks that:
+/// - every iteration-space dimension is used by at least one operand;
+/// - every dimension is either contracting (appears in both inputs, not in
+/// output) or parallel (appears in exactly one input and in the output);
+/// - at least one contracting dimension exists.
+static LogicalResult
+verifyContractionDims(size_t iterationSpaceDims, ArrayRef<size_t> inOccurrences,
+ ArrayRef<size_t> outOccurrences,
+ function_ref<InFlightDiagnostic()> emitError) {
bool hasContractingDim = false;
- for (size_t dimIndex = 0; dimIndex < (size_t)iterationSpaceDims; dimIndex++) {
+ for (size_t dimIndex = 0; dimIndex < iterationSpaceDims; dimIndex++) {
size_t inOccCount = inOccurrences[dimIndex];
size_t outOccCount = outOccurrences[dimIndex];
@@ -4669,11 +4671,35 @@ LogicalResult ContractOp::verify() {
}
if (!hasContractingDim)
- return emitError("'indexing_maps' do not specify a contracting dimension");
+ return emitError()
+ << "'indexing_maps' do not specify a contracting dimension";
return success();
}
+LogicalResult ContractOp::verify() {
+ int iterationSpaceDims = -1;
+ // Map iter space dims to #occurrences in inputs' and output's affine_maps:
+ // e.g., inOccurrences[0] will hold #times that dim (with index) 0 is used to
+ // access an input operand (so occurrence count can be at most 2) and
+ // outOccurrences[1] will indicate whether dim 1 occurred in the output, etc.
+ SmallVector<size_t> inOccurrences;
+ SmallVector<size_t> outOccurrences;
+
+ for (auto &&[affineMap, operandType, isInput] :
+ llvm::zip(getIndexingMapsArray(), getOperandTypes(),
+ SmallVector<bool>{true, true, false})) {
+ if (failed(checkContractionAffineMapAndType(
+ affineMap, operandType, isInput, iterationSpaceDims, inOccurrences,
+ outOccurrences, [&]() { return emitError(); })))
+ return failure(); // NB: Validation helper emits relevant error.
+ }
+
+ return verifyContractionDims(static_cast<size_t>(iterationSpaceDims),
+ inOccurrences, outOccurrences,
+ [&]() { return emitError(); });
+}
+
LogicalResult ContractOp::fold(FoldAdaptor, SmallVectorImpl<OpFoldResult> &) {
return memref::foldMemRefCast(*this);
}
@@ -6842,6 +6868,296 @@ Speculation::Speculatability BatchReduceMatmulOp::getSpeculatability() {
return getGenericSpeculatabilityImpl(cast<LinalgOp>(getOperation()));
}
+//===----------------------------------------------------------------------===//
+// ScaledContractOp
+//===----------------------------------------------------------------------===//
+
+SmallVector<utils::IteratorType> ScaledContractOp::getIteratorTypesArray() {
+ AffineMap outAffineMap = getIndexingMapsArray().pop_back_val();
+ // Infer iterator types based on the output.
+ SmallVector<bool> dimsInOutput(outAffineMap.getNumDims(), false);
+ for (auto result : outAffineMap.getResults()) {
+ auto dimExpr = dyn_cast<AffineDimExpr>(result);
+ assert(dimExpr && "affine_map is a projected permutation");
+ dimsInOutput[dimExpr.getPosition()] = true;
+ }
+
+ SmallVector<utils::IteratorType> iteratorTypes;
+ for (auto dimOccursInOutput : dimsInOutput)
+ iteratorTypes.push_back(dimOccursInOutput ? utils::IteratorType::parallel
+ : utils::IteratorType::reduction);
+
+ return iteratorTypes;
+}
+
+unsigned ScaledContractOp::getNumRegionArgs() { return 5; }
+
+/// Implement block region builder, which is called by 'fillStructuredOpRegion'.
+void ScaledContractOp::regionBuilder(
+ ImplicitLocOpBuilder &b, Block &block, ArrayRef<NamedAttribute> attrs,
+ function_ref<InFlightDiagnostic()> emitError) {
+ if (emitError && block.getNumArguments() != 5) {
+ emitError() << "ScaledContractOp regionBuilder expects 5 args, got "
+ << block.getNumArguments();
+ return;
+ }
+ assert(block.getNumArguments() == 5 &&
+ "ScaledContractOp regionBuilder expects 5 args");
+ RegionBuilderHelper helper(b, block);
+
+ TypeFn castSignedness = TypeFn::cast_signed;
+ auto castIter = llvm::find_if(attrs, [&](const NamedAttribute &attr) {
+ return attr.getName() == "cast";
+ });
+ if (castIter != attrs.end()) {
+ if (auto attr = llvm::dyn_cast<TypeFnAttr>(castIter->getValue()))
+ castSignedness = attr.getValue();
+ }
+
+ // TODO: Support fields with operators besides mult & add.
+ Type outType = block.getArgument(4).getType();
+
+ // Build input data value scaling.
+ // Uses specialized arith ops when possible.
+ // Otherwise, constructs computation manually.
+ auto buildScaledValue = [&](Value data, Value scale) -> Value {
+ auto dataFloatTy = dyn_cast<FloatType>(data.getType());
+ auto outFloatTy = dyn_cast<FloatType>(outType);
+ if (dataFloatTy && dyn_cast<FloatType>(scale.getType()) && outFloatTy) {
+ unsigned dataWidth = dataFloatTy.getWidth();
+ unsigned outWidth = outFloatTy.getWidth();
+ if (dataWidth < outWidth)
+ return arith::ScalingExtFOp::create(b, outType, data, scale,
+ /*fastmath=*/nullptr);
+ if (dataWidth > outWidth)
+ return arith::ScalingTruncFOp::create(b, outType, data, scale,
+ /*roundingmode=*/nullptr,
+ /*fastmath=*/nullptr);
+ }
+ Value dataAtOutType = helper.buildTypeFn(castSignedness, outType, data);
+ Value scaleAtOutType = helper.buildTypeFn(castSignedness, outType, scale);
+ return helper.buildBinaryFn(BinaryFn::mul, dataAtOutType, scaleAtOutType,
+ emitError);
+ };
+
+ Value scaledLhs =
+ buildScaledValue(block.getArgument(0), block.getArgument(1));
+ if (!scaledLhs)
+ return;
+ Value scaledRhs =
+ buildScaledValue(block.getArgument(2), block.getArgument(3));
+ if (!scaledRhs)
+ return;
+ Value productAtOutType =
+ helper.buildBinaryFn(BinaryFn::mul, scaledLhs, scaledRhs, emitError);
+ if (!productAtOutType)
+ return;
+ Value result = helper.buildBinaryFn(BinaryFn::add, block.getArgument(4),
+ productAtOutType, emitError);
+ if (!result)
+ return;
+ helper.yieldOutputs({result});
+}
+
+ParseResult ScaledContractOp::parse(OpAsmParser &parser,
+ OperationState &result) {
+ FailureOr<ArrayAttr> indexingMapsAttr = parseIndexingMapsAttr(parser);
+ if (failed(indexingMapsAttr) || *indexingMapsAttr == nullptr)
+ return parser.emitError(parser.getCurrentLocation(),
+ "expected 'indexing_maps' attribute");
+ result.addAttribute("indexing_maps", *indexingMapsAttr);
+
+ return parseNamedStructuredOp(parser, result, getNumRegionArgs(),
+ regionBuilder);
+}
+
+void ScaledContractOp::print(OpAsmPrinter &p) {
+ p << " indexing_maps = " << llvm::interleaved_array(getIndexingMaps());
+ printNamedStructuredOp(
+ p, getOperation(), getInputs(), getOutputs(),
+ /*elidedAttrs=*/{"indexing_maps", "operandSegmentSizes"});
+}
+
+LogicalResult ScaledContractOp::verify() {
+ int iterationSpaceDims = -1;
+ // Map iter space dims to #occurrences in inputs' and output's affine_maps:
+ // e.g., inOccurrences[0] will hold #times that dim (with index) 0 is used to
+ // access an input operand (so occurrence count can be at most 2) and
+ // outOccurrences[1] will indicate whether dim 1 occurred in the output, etc.
+ SmallVector<size_t> inOccurrences;
+ SmallVector<size_t> outOccurrences;
+
+ // Validate inputs and contraction semantics.
+ SmallVector<AffineMap, 5> maps = getIndexingMapsArray();
+ SmallVector<Type, 5> types = llvm::to_vector(getOperandTypes());
+ for (auto &&[affineMap, operandType, isInput] :
+ llvm::zip(SmallVector<AffineMap>{maps[0], maps[2], maps[4]},
+ SmallVector<Type>{types[0], types[2], types[4]},
+ SmallVector<bool>{true, true, false})) {
+ if (failed(checkContractionAffineMapAndType(
+ affineMap, operandType, isInput, iterationSpaceDims, inOccurrences,
+ outOccurrences, [&]() { return emitError(); })))
+ return failure(); // NB: Validation helper emits relevant error.
+ }
+
+ if (failed(verifyContractionDims(static_cast<size_t>(iterationSpaceDims),
+ inOccurrences, outOccurrences,
+ [&]() { return emitError(); })))
+ return failure(); // NB: Validation helper emits relevant error.
+
+ // Validate scales and scaling semantics.
+ auto checkScaleAffineMapAndType = [&](AffineMap affineMap, Type operandType,
+ bool isInput) -> LogicalResult {
+ // If scale's map is not a projected permutation, then it must follow
+ // specific scaling scheme semantics.
+ if (!affineMap.isProjectedPermutation()) {
+ if (affineMap.getNumSymbols() > 0)
+ return emitError("scale affine_map must not contain symbols");
+ if (affineMap.getNumResults() > affineMap.getNumInputs())
+ return emitError(
+ "scale affine_map must not have more results than inputs");
+
+ SmallVector<bool, 8> seen(affineMap.getNumInputs(), false);
+ // Allow, at most, only one instance of each input dimension in the result
+ // expressions.
+ for (auto expr : affineMap.getResults()) {
+ AffineDimExpr dim = nullptr;
+ if (isa<AffineDimExpr>(expr)) {
+ // Scaling over whole dimesion.
+ dim = dyn_cast<AffineDimExpr>(expr);
+ } else if (auto binExpr = dyn_cast<AffineBinaryOpExpr>(expr)) {
+ // Scaling over a part of the dimension.
+ // Note: Currently support limited to block scaling i.e.,
+ // one scale per a fixed number of contiguous scalar elements
+ // in a given dimension.
+ if (binExpr.getKind() != AffineExprKind::FloorDiv)
+ return emitError(
+ "only block scale with floordiv is supported for now");
+ auto scaleDim = dyn_cast<AffineDimExpr>(binExpr.getLHS());
+ if (!scaleDim)
+ return emitError("block scale LHS must be dim");
+ auto scaleFactor = dyn_cast<AffineConstantExpr>(binExpr.getRHS());
+ if (!scaleFactor)
+ return emitError("block scale RHS must be constant");
+ if (scaleFactor.getValue() <= 0)
+ return emitError("block scale factor must be positive");
+ dim = scaleDim;
+ } else {
+ return emitError("unsupported scaling variant");
+ }
+
+ if (!dim)
+ return emitError("invalid scale affine_map result expression");
+ if (seen[dim.getPosition()])
+ return emitError(
+ "scale affine_map must not have duplicate result dimensions");
+ seen[dim.getPosition()] = true;
+ }
+ }
+
+ if (auto shapedType = dyn_cast<ShapedType>(operandType)) {
+ if (affineMap.getNumResults() != shapedType.getRank())
+ return emitError(
+ "scale ranks of shaped operand and results of corresponding "
+ "affine_map differ");
+ } else if (affineMap.getNumResults() != 0) {
+ return emitError(
+ "scale affine_map specifies shaped access while operand has "
+ "non-shaped type");
+ }
+
+ return success();
+ };
+
+ // Validate scales' maps.
+ for (auto &&[affineMap, operandType] :
+ llvm::zip(SmallVector<AffineMap>{maps[1], maps[3]},
+ SmallVector<Type>{types[1], types[3]})) {
+ if (failed(checkScaleAffineMapAndType(affineMap, operandType,
+ /*isInput=*/true)))
+ return failure(); // NB: Validation helper emits relevant error.
+ }
+
+ // Cross-validate maps of operand and their scale.
+ for (auto &&[inputMap, inputType, scaleMap, scaleType] :
+ llvm::zip(SmallVector<AffineMap>{maps[0], maps[2]},
+ SmallVector<Type>{types[0], types[2]},
+ SmallVector<AffineMap>{maps[1], maps[3]},
+ SmallVector<Type>{types[1], types[3]})) {
+ if (inputMap.getNumResults() < scaleMap.getNumResults())
+ return emitError("scale must have at most the same rank as input");
+ if (scaleMap.getNumResults() == 0)
+ continue;
+
+ auto inputShape = dyn_cast<ShapedType>(inputType).getShape();
+ auto scaleShape = dyn_cast<ShapedType>(scaleType).getShape();
+
+ // Each scale dim must reference a dim present in the input map:
+ // - a missing scale dim indicates scaling over the whole input dimension
+ // - a scale dim with floordiv indicates reusing the scaling factor over
+ // parts of the input dimension; the factor must match the ratio of
+ // input dim and scale dim sizes.
+ for (auto [scaleIdx, scaleExpr] : llvm::enumerate(scaleMap.getResults())) {
+ AffineDimExpr scaleDimExpr = nullptr;
+ std::optional<int64_t> scaleFactor;
+ if (auto dimExpr = dyn_cast<AffineDimExpr>(scaleExpr)) {
+ // Scaling over the whole dimension.
+ scaleDimExpr = dimExpr;
+ } else if (auto scaleBinExpr = dyn_cast<AffineBinaryOpExpr>(scaleExpr)) {
+ // Block scaling over a part of the dimension.
+ assert(scaleBinExpr.getKind() == AffineExprKind::FloorDiv &&
+ "only floordiv is supported for now");
+ auto scaleDim = dyn_cast<AffineDimExpr>(scaleBinExpr.getLHS());
+ assert(scaleDim && "block scale LHS is a dim expression");
+ scaleDimExpr = scaleDim;
+ scaleFactor =
+ dyn_cast<AffineConstantExpr>(scaleBinExpr.getRHS()).getValue();
+ } else {
+ llvm_unreachable("unknown scale expression");
+ }
+ assert(scaleDimExpr && "failed to find scale dim expression");
+
+ std::optional<unsigned> inputIdx =
+ inputMap.getResultPosition(scaleDimExpr);
+ if (!inputIdx)
+ return emitError(
+ "scale map must contain corresponding input dimensions only");
+
+ // Validate block scaling factor for static shapes.
+ // For dynamic shapes, it is assumed that all sizes are correct.
+ if (scaleFactor && inputShape[*inputIdx] != ShapedType::kDynamic &&
+ scaleShape[scaleIdx] != ShapedType::kDynamic &&
+ llvm::divideCeilSigned(inputShape[*inputIdx], *scaleFactor) !=
+ static_cast<int64_t>(scaleShape[scaleIdx])) {
+ return emitError() << "Invalid scale shape at dim " << *inputIdx
+ << ", expected "
+ << llvm::divideCeilSigned(inputShape[*inputIdx],
+ *scaleFactor)
+ << " but got " << scaleShape[scaleIdx];
+ }
+ }
+ }
+
+ return success();
+}
+
+LogicalResult ScaledContractOp::fold(FoldAdaptor,
+ SmallVectorImpl<OpFoldResult> &) {
+ return memref::foldMemRefCast(*this);
+}
+
+void ScaledContractOp::getEffects(
+ SmallVectorImpl<SideEffects::EffectInstance<MemoryEffects::Effect>>
+ &effects) {
+ if (hasPureTensorSemantics())
+ return;
+ getGenericEffectsImpl(effects, cast<LinalgOp>(getOperation()));
+}
+
+Speculation::Speculatability ScaledContractOp::getSpeculatability() {
+ return getGenericSpeculatabilityImpl(cast<LinalgOp>(getOperation()));
+}
+
} // namespace linalg
} // namespace mlir
diff --git a/mlir/test/Dialect/Linalg/generalize-named-ops.mlir b/mlir/test/Dialect/Linalg/generalize-named-ops.mlir
index e346bee901f1d..9ed057bbcec5c 100644
--- a/mlir/test/Dialect/Linalg/generalize-named-ops.mlir
+++ b/mlir/test/Dialect/Linalg/generalize-named-ops.mlir
@@ -1279,3 +1279,142 @@ func.func @preserve_discardable_attrs(%A : tensor<16x8xf32>,
// CHECK-LABEL: func @preserve_discardable_attrs
// CHECK: linalg.generic
// CHECK-SAME: attrs = {another_attr = 42 : i64, my_custom_attr = "preserved"}
+
+// -----
+
+func.func @generalize_scaled_contract_upcast_data(
+ %A: tensor<8x16xf8E5M2>, %sA: tensor<8xf8E8M0FNU>,
+ %B: tensor<4x16xf8E5M2>, %sB: tensor<4xf8E8M0FNU>,
+ %C: tensor<8x4xf32>) -> tensor<8x4xf32> {
+ %D = linalg.scaled_contract
+ indexing_maps = [affine_map<(m, n, k) -> (m, k)>,
+ affine_map<(m, n, k) -> (m)>,
+ affine_map<(m, n, k) -> (n, k)>,
+ affine_map<(m, n, k) -> (n)>,
+ affine_map<(m, n, k) -> (m, n)>]
+ ins(%A, %sA, %B, %sB
+ : tensor<8x16xf8E5M2>, tensor<8xf8E8M0FNU>,
+ tensor<4x16xf8E5M2>, tensor<4xf8E8M0FNU>)
+ outs(%C : tensor<8x4xf32>) -> tensor<8x4xf32>
+ return %D : tensor<8x4xf32>
+}
+
+// CHECK-DAG: #[[$A_MAP:.+]] = affine_map<(d0, d1, d2) -> (d0, d2)>
+// CHECK-DAG: #[[$SA_MAP:.+]] = affine_map<(d0, d1, d2) -> (d0)>
+// CHECK-DAG: #[[$B_MAP:.+]] = affine_map<(d0, d1, d2) -> (d1, d2)>
+// CHECK-DAG: #[[$SB_MAP:.+]] = affine_map<(d0, d1, d2) -> (d1)>
+// CHECK-DAG: #[[$C_MAP:.+]] = affine_map<(d0, d1, d2) -> (d0, d1)>
+
+// CHECK-LABEL: func @generalize_scaled_contract_upcast_data
+// CHECK-SAME: %[[A:.+]]: tensor<8x16xf8E5M2>
+// CHECK-SAME: %[[SA:.+]]: tensor<8xf8E8M0FNU>
+// CHECK-SAME: %[[B:.+]]: tensor<4x16xf8E5M2>
+// CHECK-SAME: %[[SB:.+]]: tensor<4xf8E8M0FNU>
+// CHECK-SAME: %[[C:.+]]: tensor<8x4xf32>
+
+// CHECK: linalg.generic
+// CHECK-SAME: indexing_maps = [#[[$A_MAP]], #[[$SA_MAP]], #[[$B_MAP]], #[[$SB_MAP]], #[[$C_MAP]]]
+// CHECK-SAME: iterator_types = ["parallel", "parallel", "reduction"]
+// CHECK-SAME: ins(%[[A]], %[[SA]], %[[B]], %[[SB]]
+// CHECK-SAME: : tensor<8x16xf8E5M2>, tensor<8xf8E8M0FNU>, tensor<4x16xf8E5M2>, tensor<4xf8E8M0FNU>)
+// CHECK-SAME: outs(%[[C]] : tensor<8x4xf32>)
+// CHECK: ^{{.*}}(%[[A_ARG:.+]]: f8E5M2, %[[SA_ARG:.+]]: f8E8M0FNU, %[[B_ARG:.+]]: f8E5M2, %[[SB_ARG:.+]]: f8E8M0FNU, %[[C_ARG:.+]]: f32)
+// CHECK: %[[SLHS:.+]] = arith.scaling_extf %[[A_ARG]], %[[SA_ARG]] : f8E5M2, f8E8M0FNU to f32
+// CHECK: %[[SRHS:.+]] = arith.scaling_extf %[[B_ARG]], %[[SB_ARG]] : f8E5M2, f8E8M0FNU to f32
+// CHECK: %[[PROD:.+]] = arith.mulf %[[SLHS]], %[[SRHS]] : f32
+// CHECK: %[[ACC:.+]] = arith.addf %[[C_ARG]], %[[PROD]] : f32
+// CHECK: linalg.yield %[[ACC]] : f32
+
+// -----
+
+func.func @generalize_scaled_contract_downcast_data(
+ %A: tensor<8x16xf32>, %sA: tensor<8xf8E8M0FNU>,
+ %B: tensor<4x16xf32>, %sB: tensor<4xf8E8M0FNU>,
+ %C: tensor<8x4xf16>) -> tensor<8x4xf16> {
+ %D = linalg.scaled_contract
+ indexing_maps = [affine_map<(m, n, k) -> (m, k)>,
+ affine_map<(m, n, k) -> (m)>,
+ affine_map<(m, n, k) -> (n, k)>,
+ affine_map<(m, n, k) -> (n)>,
+ affine_map<(m, n, k) -> (m, n)>]
+ ins(%A, %sA, %B, %sB
+ : tensor<8x16xf32>, tensor<8xf8E8M0FNU>,
+ tensor<4x16xf32>, tensor<4xf8E8M0FNU>)
+ outs(%C : tensor<8x4xf16>) -> tensor<8x4xf16>
+ return %D : tensor<8x4xf16>
+}
+
+// CHECK-LABEL: func @generalize_scaled_contract_downcast_data
+// CHECK: linalg.generic
+// CHECK: ^{{.*}}(%[[A_ARG:.+]]: f32, %[[SA_ARG:.+]]: f8E8M0FNU, %[[B_ARG:.+]]: f32, %[[SB_ARG:.+]]: f8E8M0FNU, %[[C_ARG:.+]]: f16)
+// CHECK: %[[SLHS:.+]] = arith.scaling_truncf %[[A_ARG]], %[[SA_ARG]] : f32, f8E8M0FNU to f16
+// CHECK: %[[SRHS:.+]] = arith.scaling_truncf %[[B_ARG]], %[[SB_ARG]] : f32, f8E8M0FNU to f16
+// CHECK: %[[PROD:.+]] = arith.mulf %[[SLHS]], %[[SRHS]] : f16
+// CHECK: %[[ACC:.+]] = arith.addf %[[C_ARG]], %[[PROD]] : f16
+// CHECK: linalg.yield %[[ACC]] : f16
+
+// -----
+
+func.func @generalize_scaled_contract_same_data_acc_width(
+ %A: tensor<8x16xf32>, %sA: tensor<8xf8E8M0FNU>,
+ %B: tensor<4x16xf32>, %sB: tensor<4xf8E8M0FNU>,
+ %C: tensor<8x4xf32>) -> tensor<8x4xf32> {
+ %D = linalg.scaled_contract
+ indexing_maps = [affine_map<(m, n, k) -> (m, k)>,
+ affine_map<(m, n, k) -> (m)>,
+ affine_map<(m, n, k) -> (n, k)>,
+ affine_map<(m, n, k) -> (n)>,
+ affine_map<(m, n, k) -> (m, n)>]
+ ins(%A, %sA, %B, %sB
+ : tensor<8x16xf32>, tensor<8xf8E8M0FNU>,
+ tensor<4x16xf32>, tensor<4xf8E8M0FNU>)
+ outs(%C : tensor<8x4xf32>) -> tensor<8x4xf32>
+ return %D : tensor<8x4xf32>
+}
+
+// CHECK-LABEL: func @generalize_scaled_contract_same_data_acc_width
+// CHECK: linalg.generic
+// CHECK: ^{{.*}}(%[[A_ARG:.+]]: f32, %[[SA_ARG:.+]]: f8E8M0FNU, %[[B_ARG:.+]]: f32, %[[SB_ARG:.+]]: f8E8M0FNU, %[[C_ARG:.+]]: f32)
+// CHECK-NOT: arith.scaling_extf
+// CHECK-NOT: arith.scaling_truncf
+// CHECK: %[[SA_EXT:.+]] = arith.extf %[[SA_ARG]] : f8E8M0FNU to f32
+// CHECK: %[[SLHS:.+]] = arith.mulf %[[A_ARG]], %[[SA_EXT]] : f32
+// CHECK: %[[SB_EXT:.+]] = arith.extf %[[SB_ARG]] : f8E8M0FNU to f32
+// CHECK: %[[SRHS:.+]] = arith.mulf %[[B_ARG]], %[[SB_EXT]] : f32
+// CHECK: %[[PROD:.+]] = arith.mulf %[[SLHS]], %[[SRHS]] : f32
+// CHECK: %[[ACC:.+]] = arith.addf %[[C_ARG]], %[[PROD]] : f32
+// CHECK: linalg.yield %[[ACC]] : f32
+
+// -----
+
+func.func @generalize_scaled_contract_integer_data(
+ %A: tensor<8x16xi8>, %sA: tensor<8xf8E8M0FNU>,
+ %B: tensor<4x16xi8>, %sB: tensor<4xf8E8M0FNU>,
+ %C: tensor<8x4xf32>) -> tensor<8x4xf32> {
+ %D = linalg.scaled_contract
+ indexing_maps = [affine_map<(m, n, k) -> (m, k)>,
+ affine_map<(m, n, k) -> (m)>,
+ affine_map<(m, n, k) -> (n, k)>,
+ affine_map<(m, n, k) -> (n)>,
+ affine_map<(m, n, k) -> (m, n)>]
+ ins(%A, %sA, %B, %sB
+ : tensor<8x16xi8>, tensor<8xf8E8M0FNU>,
+ tensor<4x16xi8>, tensor<4xf8E8M0FNU>)
+ outs(%C : tensor<8x4xf32>) -> tensor<8x4xf32>
+ return %D : tensor<8x4xf32>
+}
+
+// CHECK-LABEL: func @generalize_scaled_contract_integer_data
+// CHECK: linalg.generic
+// CHECK: ^{{.*}}(%[[A_ARG:.+]]: i8, %[[SA_ARG:.+]]: f8E8M0FNU, %[[B_ARG:.+]]: i8, %[[SB_ARG:.+]]: f8E8M0FNU, %[[C_ARG:.+]]: f32)
+// CHECK-NOT: arith.scaling_extf
+// CHECK-NOT: arith.scaling_truncf
+// CHECK: %[[A_CAST:.+]] = arith.sitofp %[[A_ARG]] : i8 to f32
+// CHECK: %[[SA_EXT:.+]] = arith.extf %[[SA_ARG]] : f8E8M0FNU to f32
+// CHECK: %[[SLHS:.+]] = arith.mulf %[[A_CAST]], %[[SA_EXT]] : f32
+// CHECK: %[[B_CAST:.+]] = arith.sitofp %[[B_ARG]] : i8 to f32
+// CHECK: %[[SB_EXT:.+]] = arith.extf %[[SB_ARG]] : f8E8M0FNU to f32
+// CHECK: %[[SRHS:.+]] = arith.mulf %[[B_CAST]], %[[SB_EXT]] : f32
+// CHECK: %[[PROD:.+]] = arith.mulf %[[SLHS]], %[[SRHS]] : f32
+// CHECK: %[[ACC:.+]] = arith.addf %[[C_ARG]], %[[PROD]] : f32
+// CHECK: linalg.yield %[[ACC]] : f32
diff --git a/mlir/test/Dialect/Linalg/invalid.mlir b/mlir/test/Dialect/Linalg/invalid.mlir
index a5ec1fcc58880..56270637429c2 100644
--- a/mlir/test/Dialect/Linalg/invalid.mlir
+++ b/mlir/test/Dialect/Linalg/invalid.mlir
@@ -757,6 +757,193 @@ func.func @unused_iteration_space_dim_contraction(
// -----
+func.func @scaled_contract_scale_with_symbols(
+ %A: tensor<256x512xi8>, %sA: tensor<8x4xf8E8M0FNU>,
+ %B: tensor<128x512xi8>, %sB: tensor<128xf8E8M0FNU>, %C: tensor<256x128xf32>) -> tensor<256x128xf32> {
+ // expected-error @+1 {{scale affine_map must not contain symbols}}
+ %D = linalg.scaled_contract
+ indexing_maps = [affine_map<(m, n, k) -> (m, k)>,
+ affine_map<(m, n, k)[s0] -> (m floordiv 32, s0)>,
+ affine_map<(m, n, k) -> (n, k)>,
+ affine_map<(m, n, k) -> (n)>,
+ affine_map<(m, n, k) -> (m, n)>]
+ ins(%A, %sA, %B, %sB : tensor<256x512xi8>, tensor<8x4xf8E8M0FNU>, tensor<128x512xi8>, tensor<128xf8E8M0FNU>)
+ outs(%C : tensor<256x128xf32>) -> tensor<256x128xf32>
+ return %D : tensor<256x128xf32>
+}
+
+// -----
+
+func.func @scaled_contract_scale_too_many_results(
+ %A: tensor<256x512xi8>, %sA: tensor<8x4xf8E8M0FNU>,
+ %B: tensor<128x512xi8>, %sB: tensor<128xf8E8M0FNU>, %C: tensor<256x128xf32>) -> tensor<256x128xf32> {
+ // expected-error @+1 {{scale affine_map must not have more results than inputs}}
+ %D = linalg.scaled_contract
+ indexing_maps = [affine_map<(m, n, k) -> (m, k)>,
+ affine_map<(m, n, k) -> (m floordiv 32, k floordiv 128, n, m)>,
+ affine_map<(m, n, k) -> (n, k)>,
+ affine_map<(m, n, k) -> (n)>,
+ affine_map<(m, n, k) -> (m, n)>]
+ ins(%A, %sA, %B, %sB : tensor<256x512xi8>, tensor<8x4xf8E8M0FNU>, tensor<128x512xi8>, tensor<128xf8E8M0FNU>)
+ outs(%C : tensor<256x128xf32>) -> tensor<256x128xf32>
+ return %D : tensor<256x128xf32>
+}
+
+// -----
+
+func.func @scaled_contract_scale_non_floordiv(
+ %A: tensor<256x512xi8>, %sA: tensor<8x4xf8E8M0FNU>,
+ %B: tensor<128x512xi8>, %sB: tensor<128xf8E8M0FNU>, %C: tensor<256x128xf32>) -> tensor<256x128xf32> {
+ // expected-error @+1 {{only block scale with floordiv is supported for now}}
+ %D = linalg.scaled_contract
+ indexing_maps = [affine_map<(m, n, k) -> (m, k)>,
+ affine_map<(m, n, k) -> (m floordiv 32, k mod 128)>,
+ affine_map<(m, n, k) -> (n, k)>,
+ affine_map<(m, n, k) -> (n)>,
+ affine_map<(m, n, k) -> (m, n)>]
+ ins(%A, %sA, %B, %sB : tensor<256x512xi8>, tensor<8x4xf8E8M0FNU>, tensor<128x512xi8>, tensor<128xf8E8M0FNU>)
+ outs(%C : tensor<256x128xf32>) -> tensor<256x128xf32>
+ return %D : tensor<256x128xf32>
+}
+
+// -----
+
+func.func @scaled_contract_block_scale_lhs_not_dim(
+ %A: tensor<256x512xi8>, %sA: tensor<8x4xf8E8M0FNU>,
+ %B: tensor<128x512xi8>, %sB: tensor<128xf8E8M0FNU>, %C: tensor<256x128xf32>) -> tensor<256x128xf32> {
+ // expected-error @+1 {{block scale LHS must be dim}}
+ %D = linalg.scaled_contract
+ indexing_maps = [affine_map<(m, n, k) -> (m, k)>,
+ affine_map<(m, n, k) -> ((m + n) floordiv 32, k floordiv 128)>,
+ affine_map<(m, n, k) -> (n, k)>,
+ affine_map<(m, n, k) -> (n)>,
+ affine_map<(m, n, k) -> (m, n)>]
+ ins(%A, %sA, %B, %sB : tensor<256x512xi8>, tensor<8x4xf8E8M0FNU>, tensor<128x512xi8>, tensor<128xf8E8M0FNU>)
+ outs(%C : tensor<256x128xf32>) -> tensor<256x128xf32>
+ return %D : tensor<256x128xf32>
+}
+
+// -----
+
+func.func @scaled_contract_block_scale_nonpositive_factor(
+ %A: tensor<256x512xi8>, %sA: tensor<8x4xf8E8M0FNU>,
+ %B: tensor<128x512xi8>, %sB: tensor<128xf8E8M0FNU>, %C: tensor<256x128xf32>) -> tensor<256x128xf32> {
+ // expected-error @+1 {{block scale factor must be positive}}
+ %D = linalg.scaled_contract
+ indexing_maps = [affine_map<(m, n, k) -> (m, k)>,
+ affine_map<(m, n, k) -> (m floordiv 32, k floordiv -32)>,
+ affine_map<(m, n, k) -> (n, k)>,
+ affine_map<(m, n, k) -> (n)>,
+ affine_map<(m, n, k) -> (m, n)>]
+ ins(%A, %sA, %B, %sB : tensor<256x512xi8>, tensor<8x4xf8E8M0FNU>, tensor<128x512xi8>, tensor<128xf8E8M0FNU>)
+ outs(%C : tensor<256x128xf32>) -> tensor<256x128xf32>
+ return %D : tensor<256x128xf32>
+}
+
+// -----
+
+func.func @scaled_contract_scale_duplicate_dims(
+ %A: tensor<256x512xi8>, %sA: tensor<8x4xf8E8M0FNU>,
+ %B: tensor<128x512xi8>, %sB: tensor<128xf8E8M0FNU>, %C: tensor<256x128xf32>) -> tensor<256x128xf32> {
+ // expected-error @+1 {{scale affine_map must not have duplicate result dimensions}}
+ %D = linalg.scaled_contract
+ indexing_maps = [affine_map<(m, n, k) -> (m, k)>,
+ affine_map<(m, n, k) -> (m floordiv 32, m floordiv 16)>,
+ affine_map<(m, n, k) -> (n, k)>,
+ affine_map<(m, n, k) -> (n)>,
+ affine_map<(m, n, k) -> (m, n)>]
+ ins(%A, %sA, %B, %sB : tensor<256x512xi8>, tensor<8x4xf8E8M0FNU>, tensor<128x512xi8>, tensor<128xf8E8M0FNU>)
+ outs(%C : tensor<256x128xf32>) -> tensor<256x128xf32>
+ return %D : tensor<256x128xf32>
+}
+
+// -----
+
+func.func @scaled_contract_scale_rank_mismatch(
+ %A: tensor<256x512xi8>, %sA: tensor<8x4xf8E8M0FNU>,
+ %B: tensor<128x512xi8>, %sB: tensor<128xf8E8M0FNU>, %C: tensor<256x128xf32>) -> tensor<256x128xf32> {
+ // expected-error @+1 {{scale ranks of shaped operand and results of corresponding affine_map differ}}
+ %D = linalg.scaled_contract
+ indexing_maps = [affine_map<(m, n, k) -> (m, k)>,
+ affine_map<(m, n, k) -> (m)>,
+ affine_map<(m, n, k) -> (n, k)>,
+ affine_map<(m, n, k) -> (n)>,
+ affine_map<(m, n, k) -> (m, n)>]
+ ins(%A, %sA, %B, %sB : tensor<256x512xi8>, tensor<8x4xf8E8M0FNU>, tensor<128x512xi8>, tensor<128xf8E8M0FNU>)
+ outs(%C : tensor<256x128xf32>) -> tensor<256x128xf32>
+ return %D : tensor<256x128xf32>
+}
+
+// -----
+
+func.func @scaled_contract_scale_non_shaped_operand(
+ %A: tensor<256x512xi8>, %sA: f8E8M0FNU,
+ %B: tensor<128x512xi8>, %sB: tensor<128xf8E8M0FNU>, %C: tensor<256x128xf32>) -> tensor<256x128xf32> {
+ // expected-error @+1 {{scale affine_map specifies shaped access while operand has non-shaped type}}
+ %D = linalg.scaled_contract
+ indexing_maps = [affine_map<(m, n, k) -> (m, k)>,
+ affine_map<(m, n, k) -> (m)>,
+ affine_map<(m, n, k) -> (n, k)>,
+ affine_map<(m, n, k) -> (n)>,
+ affine_map<(m, n, k) -> (m, n)>]
+ ins(%A, %sA, %B, %sB : tensor<256x512xi8>, f8E8M0FNU, tensor<128x512xi8>, tensor<128xf8E8M0FNU>)
+ outs(%C : tensor<256x128xf32>) -> tensor<256x128xf32>
+ return %D : tensor<256x128xf32>
+}
+
+// -----
+
+func.func @scaled_contract_scale_rank_exceeds_input(
+ %A: tensor<256x512xi8>, %sA: tensor<8x4x2xf8E8M0FNU>,
+ %B: tensor<128x512xi8>, %sB: tensor<128xf8E8M0FNU>, %C: tensor<256x128xf32>) -> tensor<256x128xf32> {
+ // expected-error @+1 {{scale must have at most the same rank as input}}
+ %D = linalg.scaled_contract
+ indexing_maps = [affine_map<(m, n, k) -> (m, k)>,
+ affine_map<(m, n, k) -> (m, k, n)>,
+ affine_map<(m, n, k) -> (n, k)>,
+ affine_map<(m, n, k) -> (n)>,
+ affine_map<(m, n, k) -> (m, n)>]
+ ins(%A, %sA, %B, %sB : tensor<256x512xi8>, tensor<8x4x2xf8E8M0FNU>, tensor<128x512xi8>, tensor<128xf8E8M0FNU>)
+ outs(%C : tensor<256x128xf32>) -> tensor<256x128xf32>
+ return %D : tensor<256x128xf32>
+}
+
+// -----
+
+func.func @scaled_contract_invalid_block_scale_shape(
+ %A: tensor<256x512xi8>, %sA: tensor<8x8xf8E8M0FNU>,
+ %B: tensor<128x512xi8>, %sB: tensor<128xf8E8M0FNU>, %C: tensor<256x128xf32>) -> tensor<256x128xf32> {
+ // expected-error @+1 {{Invalid scale shape at dim 1, expected 4 but got 8}}
+ %D = linalg.scaled_contract
+ indexing_maps = [affine_map<(m, n, k) -> (m, k)>,
+ affine_map<(m, n, k) -> (m floordiv 32, k floordiv 128)>,
+ affine_map<(m, n, k) -> (n, k)>,
+ affine_map<(m, n, k) -> (n)>,
+ affine_map<(m, n, k) -> (m, n)>]
+ ins(%A, %sA, %B, %sB : tensor<256x512xi8>, tensor<8x8xf8E8M0FNU>, tensor<128x512xi8>, tensor<128xf8E8M0FNU>)
+ outs(%C : tensor<256x128xf32>) -> tensor<256x128xf32>
+ return %D : tensor<256x128xf32>
+}
+
+// -----
+
+func.func @scaled_contract_scale_dim_not_in_input(
+ %A: tensor<256x512xi8>, %sA: tensor<128xf8E8M0FNU>,
+ %B: tensor<128x512xi8>, %sB: tensor<128xf8E8M0FNU>, %C: tensor<256x128xf32>) -> tensor<256x128xf32> {
+ // expected-error @+1 {{scale map must contain corresponding input dimensions only}}
+ %D = linalg.scaled_contract
+ indexing_maps = [affine_map<(m, n, k) -> (m, k)>,
+ affine_map<(m, n, k) -> (n)>,
+ affine_map<(m, n, k) -> (n, k)>,
+ affine_map<(m, n, k) -> (n)>,
+ affine_map<(m, n, k) -> (m, n)>]
+ ins(%A, %sA, %B, %sB : tensor<256x512xi8>, tensor<128xf8E8M0FNU>, tensor<128x512xi8>, tensor<128xf8E8M0FNU>)
+ outs(%C : tensor<256x128xf32>) -> tensor<256x128xf32>
+ return %D : tensor<256x128xf32>
+}
+
+// -----
+
func.func @invalid_static_2d_conv(%input : memref<1x3x4x2xf32>, %filter: memref<3x2x2x1xf32>, %output: memref<1x2x3x1xf32>) {
// expected-error @+1 {{inferred input/output operand #0 has shape's dimension #1 to be greater than or equal to 4, but found 3}}
linalg.conv_2d_nhwc_hwcf
diff --git a/mlir/test/Dialect/Linalg/loops.mlir b/mlir/test/Dialect/Linalg/loops.mlir
index efe8010cffc91..c570bc890519d 100644
--- a/mlir/test/Dialect/Linalg/loops.mlir
+++ b/mlir/test/Dialect/Linalg/loops.mlir
@@ -674,6 +674,64 @@ func.func @batch_reduce_matmul_as_contract(
// CHECKPARALLEL: %[[res:.*]] = arith.addf %[[vc]], %[[inc]] : f32
// CHECKPARALLEL: store %[[res]], %[[mC]][%[[m]], %[[n]]] : memref<?x?xf32>
+func.func @scaled_contract_as_loops(
+ %A: memref<?x?xf8E5M2>, %sA: memref<?xf8E8M0FNU>,
+ %B: memref<?x?xf8E5M2>, %sB: memref<?xf8E8M0FNU>, %C: memref<?x?xf32>) {
+ linalg.scaled_contract
+ indexing_maps = [affine_map<(m, n, k) -> (m, k)>,
+ affine_map<(m, n, k) -> (m)>,
+ affine_map<(m, n, k) -> (n, k)>,
+ affine_map<(m, n, k) -> (n)>,
+ affine_map<(m, n, k) -> (m, n)>]
+ ins(%A, %sA, %B, %sB : memref<?x?xf8E5M2>, memref<?xf8E8M0FNU>, memref<?x?xf8E5M2>, memref<?xf8E8M0FNU>)
+ outs(%C : memref<?x?xf32>)
+ return
+}
+// CHECK-LABEL: @scaled_contract_as_loops
+// CHECK-SAME: %[[mA:[a-zA-Z0-9]+]]: memref<?x?xf8E5M2>
+// CHECK-SAME: %[[msA:[a-zA-Z0-9]+]]: memref<?xf8E8M0FNU>
+// CHECK-SAME: %[[mB:[a-zA-Z0-9]+]]: memref<?x?xf8E5M2>
+// CHECK-SAME: %[[msB:[a-zA-Z0-9]+]]: memref<?xf8E8M0FNU>
+// CHECK-SAME: %[[mC:[a-zA-Z0-9]+]]: memref<?x?xf32>
+// CHECK: %[[M:.*]] = memref.dim %[[mA]], %c0 : memref<?x?xf8E5M2>
+// CHECK: %[[K:.*]] = memref.dim %[[mA]], %c1 : memref<?x?xf8E5M2>
+// CHECK: %[[N:.*]] = memref.dim %[[mB]], %c0 : memref<?x?xf8E5M2>
+// CHECK: scf.for %[[m:.*]] = %{{.*}} to %[[M]]
+// CHECK: scf.for %[[n:.*]] = %{{.*}} to %[[N]]
+// CHECK: scf.for %[[k:.*]] = %{{.*}} to %[[K]]
+// CHECK: %[[va:.*]] = memref.load %[[mA]][%[[m]], %[[k]]] : memref<?x?xf8E5M2>
+// CHECK: %[[vsa:.*]] = memref.load %[[msA]][%[[m]]] : memref<?xf8E8M0FNU>
+// CHECK: %[[vb:.*]] = memref.load %[[mB]][%[[n]], %[[k]]] : memref<?x?xf8E5M2>
+// CHECK: %[[vsb:.*]] = memref.load %[[msB]][%[[n]]] : memref<?xf8E8M0FNU>
+// CHECK: %[[vc:.*]] = memref.load %[[mC]][%[[m]], %[[n]]] : memref<?x?xf32>
+// CHECK: %[[sa:.*]] = arith.scaling_extf %[[va]], %[[vsa]] : f8E5M2, f8E8M0FNU to f32
+// CHECK: %[[sb:.*]] = arith.scaling_extf %[[vb]], %[[vsb]] : f8E5M2, f8E8M0FNU to f32
+// CHECK: %[[prod:.*]] = arith.mulf %[[sa]], %[[sb]] : f32
+// CHECK: %[[res:.*]] = arith.addf %[[vc]], %[[prod]] : f32
+// CHECK: store %[[res]], %[[mC]][%[[m]], %[[n]]] : memref<?x?xf32>
+
+// CHECKPARALLEL-LABEL: @scaled_contract_as_loops
+// CHECKPARALLEL-SAME: %[[mA:[a-zA-Z0-9]+]]: memref<?x?xf8E5M2>
+// CHECKPARALLEL-SAME: %[[msA:[a-zA-Z0-9]+]]: memref<?xf8E8M0FNU>
+// CHECKPARALLEL-SAME: %[[mB:[a-zA-Z0-9]+]]: memref<?x?xf8E5M2>
+// CHECKPARALLEL-SAME: %[[msB:[a-zA-Z0-9]+]]: memref<?xf8E8M0FNU>
+// CHECKPARALLEL-SAME: %[[mC:[a-zA-Z0-9]+]]: memref<?x?xf32>
+// CHECKPARALLEL: %[[M:.*]] = memref.dim %[[mA]], %c0 : memref<?x?xf8E5M2>
+// CHECKPARALLEL: %[[K:.*]] = memref.dim %[[mA]], %c1 : memref<?x?xf8E5M2>
+// CHECKPARALLEL: %[[N:.*]] = memref.dim %[[mB]], %c0 : memref<?x?xf8E5M2>
+// CHECKPARALLEL: scf.parallel (%[[m:.*]], %[[n:.*]]) = ({{.*}}) to (%[[M]], %[[N]]) step ({{.*}}) {
+// CHECKPARALLEL: scf.for %[[k:.*]] = %{{.*}} to %[[K]]
+// CHECKPARALLEL: %[[va:.*]] = memref.load %[[mA]][%[[m]], %[[k]]] : memref<?x?xf8E5M2>
+// CHECKPARALLEL: %[[vsa:.*]] = memref.load %[[msA]][%[[m]]] : memref<?xf8E8M0FNU>
+// CHECKPARALLEL: %[[vb:.*]] = memref.load %[[mB]][%[[n]], %[[k]]] : memref<?x?xf8E5M2>
+// CHECKPARALLEL: %[[vsb:.*]] = memref.load %[[msB]][%[[n]]] : memref<?xf8E8M0FNU>
+// CHECKPARALLEL: %[[vc:.*]] = memref.load %[[mC]][%[[m]], %[[n]]] : memref<?x?xf32>
+// CHECKPARALLEL: %[[sa:.*]] = arith.scaling_extf %[[va]], %[[vsa]] : f8E5M2, f8E8M0FNU to f32
+// CHECKPARALLEL: %[[sb:.*]] = arith.scaling_extf %[[vb]], %[[vsb]] : f8E5M2, f8E8M0FNU to f32
+// CHECKPARALLEL: %[[prod:.*]] = arith.mulf %[[sa]], %[[sb]] : f32
+// CHECKPARALLEL: %[[res:.*]] = arith.addf %[[vc]], %[[prod]] : f32
+// CHECKPARALLEL: store %[[res]], %[[mC]][%[[m]], %[[n]]] : memref<?x?xf32>
+
func.func @named_batch_matmul(%A: memref<?x?x?xf32>, %B: memref<?x?x?xf32>, %C: memref<?x?x?xf32>) {
linalg.batch_matmul ins(%A, %B : memref<?x?x?xf32>, memref<?x?x?xf32>)
outs(%C : memref<?x?x?xf32>)
diff --git a/mlir/test/Dialect/Linalg/named-ops.mlir b/mlir/test/Dialect/Linalg/named-ops.mlir
index 8068c23a4a0fd..b9cb8e012e651 100644
--- a/mlir/test/Dialect/Linalg/named-ops.mlir
+++ b/mlir/test/Dialect/Linalg/named-ops.mlir
@@ -1978,6 +1978,168 @@ func.func @contract_matmul_bcast_b_transpose_a(%A: memref<5x3xf32>, %B: memref<5
// -----
+// CHECK: #[[$ACCESS_A:.+]] = affine_map<(d0, d1, d2) -> (d0, d2)>
+// CHECK: #[[$ACCESS_SCALE_A:.+]] = affine_map<(d0, d1, d2) -> (d0 floordiv 32, d2 floordiv 128)>
+// CHECK: #[[$ACCESS_B:.+]] = affine_map<(d0, d1, d2) -> (d1, d2)>
+// CHECK: #[[$ACCESS_SCALE_B:.+]] = affine_map<(d0, d1, d2) -> (d1)>
+// CHECK: #[[$ACCESS_C:.+]] = affine_map<(d0, d1, d2) -> (d0, d1)>
+// CHECK-LABEL: func @scaled_contract
+func.func @scaled_contract(
+ %A: tensor<256x512xi8>, %sA: tensor<8x4xf8E8M0FNU>,
+ %B: tensor<128x512xi8>, %sB: tensor<128xf8E8M0FNU>,
+ %C: tensor<256x128xf32>) -> tensor<256x128xf32> {
+// CHECK: linalg.scaled_contract
+// CHECK-SAME: indexing_maps = [#[[$ACCESS_A]], #[[$ACCESS_SCALE_A]], #[[$ACCESS_B]], #[[$ACCESS_SCALE_B]], #[[$ACCESS_C]]]
+// CHECK-SAME: ins(%{{.+}}, %{{.+}}, %{{.+}}, %{{.+}} : tensor<256x512xi8>, tensor<8x4xf8E8M0FNU>, tensor<128x512xi8>, tensor<128xf8E8M0FNU>)
+// CHECK-SAME: outs(%{{.+}} : tensor<256x128xf32>) -> tensor<256x128xf32>
+ %D = linalg.scaled_contract
+ indexing_maps = [affine_map<(m, n, k) -> (m, k)>,
+ affine_map<(m, n, k) -> (m floordiv 32, k floordiv 128)>,
+ affine_map<(m, n, k) -> (n, k)>,
+ affine_map<(m, n, k) -> (n)>,
+ affine_map<(m, n, k) -> (m, n)>]
+ ins(%A, %sA, %B, %sB : tensor<256x512xi8>, tensor<8x4xf8E8M0FNU>, tensor<128x512xi8>, tensor<128xf8E8M0FNU>)
+ outs(%C : tensor<256x128xf32>) -> tensor<256x128xf32>
+ return %D : tensor<256x128xf32>
+}
+
+// -----
+
+// CHECK: #[[$ACCESS_A:.+]] = affine_map<(d0, d1, d2) -> (d0, d2)>
+// CHECK: #[[$ACCESS_SCALE:.+]] = affine_map<(d0, d1, d2) -> ()>
+// CHECK: #[[$ACCESS_B:.+]] = affine_map<(d0, d1, d2) -> (d1, d2)>
+// CHECK: #[[$ACCESS_C:.+]] = affine_map<(d0, d1, d2) -> (d0, d1)>
+// CHECK-LABEL: func @scaled_contract_tensor_scale
+func.func @scaled_contract_tensor_scale(
+ %A: memref<256x512xi8>, %sA: memref<f8E8M0FNU>,
+ %B: memref<128x512xi8>, %sB: memref<f8E8M0FNU>,
+ %C: memref<256x128xf32>) {
+// CHECK: linalg.scaled_contract
+// CHECK-SAME: indexing_maps = [#[[$ACCESS_A]], #[[$ACCESS_SCALE]], #[[$ACCESS_B]], #[[$ACCESS_SCALE]], #[[$ACCESS_C]]]
+// CHECK-SAME: ins(%{{.+}}, %{{.+}}, %{{.+}}, %{{.+}} : memref<256x512xi8>, memref<f8E8M0FNU>, memref<128x512xi8>, memref<f8E8M0FNU>)
+// CHECK-SAME: outs(%{{.+}} : memref<256x128xf32>)
+ linalg.scaled_contract
+ indexing_maps = [affine_map<(m, n, k) -> (m, k)>,
+ affine_map<(m, n, k) -> ()>,
+ affine_map<(m, n, k) -> (n, k)>,
+ affine_map<(m, n, k) -> ()>,
+ affine_map<(m, n, k) -> (m, n)>]
+ ins(%A, %sA, %B, %sB : memref<256x512xi8>, memref<f8E8M0FNU>, memref<128x512xi8>, memref<f8E8M0FNU>)
+ outs(%C : memref<256x128xf32>)
+ return
+}
+
+// -----
+
+// CHECK: #[[$ACCESS_A:.+]] = affine_map<(d0, d1, d2) -> (d0, d2)>
+// CHECK: #[[$ACCESS_SCALE_A:.+]] = affine_map<(d0, d1, d2) -> (d0)>
+// CHECK: #[[$ACCESS_B:.+]] = affine_map<(d0, d1, d2) -> (d1, d2)>
+// CHECK: #[[$ACCESS_SCALE_B:.+]] = affine_map<(d0, d1, d2) -> (d1)>
+// CHECK: #[[$ACCESS_C:.+]] = affine_map<(d0, d1, d2) -> (d0, d1)>
+// CHECK-LABEL: func @scaled_contract_dim_scale
+func.func @scaled_contract_dim_scale(
+ %A: tensor<256x512xi8>, %sA: tensor<256xf8E8M0FNU>,
+ %B: tensor<128x512xi8>, %sB: tensor<128xf8E8M0FNU>,
+ %C: tensor<256x128xf32>) -> tensor<256x128xf32> {
+// CHECK: linalg.scaled_contract
+// CHECK-SAME: indexing_maps = [#[[$ACCESS_A]], #[[$ACCESS_SCALE_A]], #[[$ACCESS_B]], #[[$ACCESS_SCALE_B]], #[[$ACCESS_C]]]
+// CHECK-SAME: ins(%{{.+}}, %{{.+}}, %{{.+}}, %{{.+}} : tensor<256x512xi8>, tensor<256xf8E8M0FNU>, tensor<128x512xi8>, tensor<128xf8E8M0FNU>)
+// CHECK-SAME: outs(%{{.+}} : tensor<256x128xf32>) -> tensor<256x128xf32>
+ %D = linalg.scaled_contract
+ indexing_maps = [affine_map<(m, n, k) -> (m, k)>,
+ affine_map<(m, n, k) -> (m)>,
+ affine_map<(m, n, k) -> (n, k)>,
+ affine_map<(m, n, k) -> (n)>,
+ affine_map<(m, n, k) -> (m, n)>]
+ ins(%A, %sA, %B, %sB : tensor<256x512xi8>, tensor<256xf8E8M0FNU>, tensor<128x512xi8>, tensor<128xf8E8M0FNU>)
+ outs(%C : tensor<256x128xf32>) -> tensor<256x128xf32>
+ return %D : tensor<256x128xf32>
+}
+
+// -----
+
+// CHECK: #[[$ACCESS_A:.+]] = affine_map<(d0, d1, d2) -> (d0, d2)>
+// CHECK: #[[$ACCESS_SCALE_A:.+]] = affine_map<(d0, d1, d2) -> (d0, d2 floordiv 32)>
+// CHECK: #[[$ACCESS_B:.+]] = affine_map<(d0, d1, d2) -> (d1, d2)>
+// CHECK: #[[$ACCESS_SCALE_B:.+]] = affine_map<(d0, d1, d2) -> (d1, d2 floordiv 32)>
+// CHECK: #[[$ACCESS_C:.+]] = affine_map<(d0, d1, d2) -> (d0, d1)>
+// CHECK-LABEL: func @scaled_contract_block_scale
+func.func @scaled_contract_block_scale(
+ %A: tensor<?x?xi8>, %sA: tensor<?x?xf8E8M0FNU>,
+ %B: tensor<?x?xi8>, %sB: tensor<?x?xf8E8M0FNU>,
+ %C: tensor<?x?xf32>) -> tensor<?x?xf32> {
+// CHECK: linalg.scaled_contract
+// CHECK-SAME: indexing_maps = [#[[$ACCESS_A]], #[[$ACCESS_SCALE_A]], #[[$ACCESS_B]], #[[$ACCESS_SCALE_B]], #[[$ACCESS_C]]]
+// CHECK-SAME: ins(%{{.+}}, %{{.+}}, %{{.+}}, %{{.+}} : tensor<?x?xi8>, tensor<?x?xf8E8M0FNU>, tensor<?x?xi8>, tensor<?x?xf8E8M0FNU>)
+// CHECK-SAME: outs(%{{.+}} : tensor<?x?xf32>) -> tensor<?x?xf32>
+ %D = linalg.scaled_contract
+ indexing_maps = [affine_map<(m, n, k) -> (m, k)>,
+ affine_map<(m, n, k) -> (m, k floordiv 32)>,
+ affine_map<(m, n, k) -> (n, k)>,
+ affine_map<(m, n, k) -> (n, k floordiv 32)>,
+ affine_map<(m, n, k) -> (m, n)>]
+ ins(%A, %sA, %B, %sB : tensor<?x?xi8>, tensor<?x?xf8E8M0FNU>, tensor<?x?xi8>, tensor<?x?xf8E8M0FNU>)
+ outs(%C : tensor<?x?xf32>) -> tensor<?x?xf32>
+ return %D : tensor<?x?xf32>
+}
+
+// -----
+
+// CHECK: #[[$ACCESS_A:.+]] = affine_map<(d0, d1, d2) -> (d0, d2)>
+// CHECK: #[[$ACCESS_SCALE_A:.+]] = affine_map<(d0, d1, d2) -> (d0 floordiv 32, d2 floordiv 128)>
+// CHECK: #[[$ACCESS_B:.+]] = affine_map<(d0, d1, d2) -> (d1, d2)>
+// CHECK: #[[$ACCESS_SCALE_B:.+]] = affine_map<(d0, d1, d2) -> (d1)>
+// CHECK: #[[$ACCESS_C:.+]] = affine_map<(d0, d1, d2) -> (d0, d1)>
+// CHECK-LABEL: func @scaled_contract_cast_unsigned
+func.func @scaled_contract_cast_unsigned(
+ %A: tensor<256x512xi8>, %sA: tensor<8x4xf8E8M0FNU>,
+ %B: tensor<128x512xi8>, %sB: tensor<128xf8E8M0FNU>,
+ %C: tensor<256x128xf32>) -> tensor<256x128xf32> {
+// CHECK: linalg.scaled_contract
+// CHECK-SAME: indexing_maps = [#[[$ACCESS_A]], #[[$ACCESS_SCALE_A]], #[[$ACCESS_B]], #[[$ACCESS_SCALE_B]], #[[$ACCESS_C]]]
+// CHECK-SAME: {cast = #linalg.type_fn<cast_unsigned>}
+// CHECK-SAME: ins(%{{.+}}, %{{.+}}, %{{.+}}, %{{.+}} : tensor<256x512xi8>, tensor<8x4xf8E8M0FNU>, tensor<128x512xi8>, tensor<128xf8E8M0FNU>)
+// CHECK-SAME: outs(%{{.+}} : tensor<256x128xf32>) -> tensor<256x128xf32>
+ %D = linalg.scaled_contract
+ indexing_maps = [affine_map<(m, n, k) -> (m, k)>,
+ affine_map<(m, n, k) -> (m floordiv 32, k floordiv 128)>,
+ affine_map<(m, n, k) -> (n, k)>,
+ affine_map<(m, n, k) -> (n)>,
+ affine_map<(m, n, k) -> (m, n)>]
+ ins(%A, %sA, %B, %sB : tensor<256x512xi8>, tensor<8x4xf8E8M0FNU>, tensor<128x512xi8>, tensor<128xf8E8M0FNU>)
+ outs(%C : tensor<256x128xf32>) {cast = #linalg.type_fn<cast_unsigned>} -> tensor<256x128xf32>
+ return %D : tensor<256x128xf32>
+}
+
+// -----
+
+// CHECK: #[[$ACCESS_A:.+]] = affine_map<(d0, d1, d2) -> (d0, d2)>
+// CHECK: #[[$ACCESS_SCALE_A:.+]] = affine_map<(d0, d1, d2) -> (d2 floordiv 128, d0 floordiv 32)>
+// CHECK: #[[$ACCESS_B:.+]] = affine_map<(d0, d1, d2) -> (d1, d2)>
+// CHECK: #[[$ACCESS_SCALE_B:.+]] = affine_map<(d0, d1, d2) -> (d1)>
+// CHECK: #[[$ACCESS_C:.+]] = affine_map<(d0, d1, d2) -> (d0, d1)>
+// CHECK-LABEL: func @scaled_contract_permuted_scale
+func.func @scaled_contract_permuted_scale(
+ %A: tensor<256x512xi8>, %sA: tensor<4x8xf8E8M0FNU>,
+ %B: tensor<128x512xi8>, %sB: tensor<128xf8E8M0FNU>,
+ %C: tensor<256x128xf32>) -> tensor<256x128xf32> {
+// CHECK: linalg.scaled_contract
+// CHECK-SAME: indexing_maps = [#[[$ACCESS_A]], #[[$ACCESS_SCALE_A]], #[[$ACCESS_B]], #[[$ACCESS_SCALE_B]], #[[$ACCESS_C]]]
+// CHECK-SAME: ins(%{{.+}}, %{{.+}}, %{{.+}}, %{{.+}} : tensor<256x512xi8>, tensor<4x8xf8E8M0FNU>, tensor<128x512xi8>, tensor<128xf8E8M0FNU>)
+// CHECK-SAME: outs(%{{.+}} : tensor<256x128xf32>) -> tensor<256x128xf32>
+ %D = linalg.scaled_contract
+ indexing_maps = [affine_map<(m, n, k) -> (m, k)>,
+ affine_map<(m, n, k) -> (k floordiv 128, m floordiv 32)>,
+ affine_map<(m, n, k) -> (n, k)>,
+ affine_map<(m, n, k) -> (n)>,
+ affine_map<(m, n, k) -> (m, n)>]
+ ins(%A, %sA, %B, %sB : tensor<256x512xi8>, tensor<4x8xf8E8M0FNU>, tensor<128x512xi8>, tensor<128xf8E8M0FNU>)
+ outs(%C : tensor<256x128xf32>) -> tensor<256x128xf32>
+ return %D : tensor<256x128xf32>
+}
+
+// -----
+
// CHECK-LABEL: func @mmt4d
func.func @mmt4d(%A: tensor<10x32x8x1xf32>, %B: tensor<80x32x4x1xf32>, %C: tensor<10x80x8x4xf32>) -> tensor<10x80x8x4xf32> {
// CHECK: %{{.+}} = linalg.mmt4d
diff --git a/mlir/test/Dialect/Linalg/roundtrip.mlir b/mlir/test/Dialect/Linalg/roundtrip.mlir
index 2ad69d654ab99..53c32859c85c3 100644
--- a/mlir/test/Dialect/Linalg/roundtrip.mlir
+++ b/mlir/test/Dialect/Linalg/roundtrip.mlir
@@ -308,6 +308,34 @@ func.func @named_ops(%a3: memref<?x?x?xf32>, %b3: memref<?x?x?xf32>, %c3: memref
// -----
+#scaledA = affine_map<(m, n, k) -> (m, k)>
+#scaledScaleA = affine_map<(m, n, k) -> (m floordiv 32, k floordiv 128)>
+#scaledB = affine_map<(m, n, k) -> (n, k)>
+#scaledScaleB = affine_map<(m, n, k) -> (n)>
+#scaledC = affine_map<(m, n, k) -> (m, n)>
+func.func @scaled_contract_named_op(
+ %a: memref<256x512xi8>, %sa: memref<8x4xf8E8M0FNU>, %b: memref<128x512xi8>,
+ %sb: memref<128xf8E8M0FNU>, %c: memref<256x128xf32>,
+ %ta: tensor<256x512xi8>, %tsa: tensor<8x4xf8E8M0FNU>, %tb: tensor<128x512xi8>,
+ %tsb: tensor<128xf8E8M0FNU>, %tc: tensor<256x128xf32>)
+ -> tensor<256x128xf32>
+{
+ linalg.scaled_contract
+ indexing_maps = [#scaledA, #scaledScaleA, #scaledB, #scaledScaleB, #scaledC]
+ ins(%a, %sa, %b, %sb : memref<256x512xi8>, memref<8x4xf8E8M0FNU>, memref<128x512xi8>, memref<128xf8E8M0FNU>)
+ outs(%c : memref<256x128xf32>)
+ %res = linalg.scaled_contract
+ indexing_maps = [#scaledA, #scaledScaleA, #scaledB, #scaledScaleB, #scaledC]
+ ins(%ta, %tsa, %tb, %tsb : tensor<256x512xi8>, tensor<8x4xf8E8M0FNU>, tensor<128x512xi8>, tensor<128xf8E8M0FNU>)
+ outs(%tc : tensor<256x128xf32>) -> tensor<256x128xf32>
+ return %res : tensor<256x128xf32>
+}
+// CHECK-LABEL: func @scaled_contract_named_op
+// CHECK: linalg.scaled_contract
+// CHECK: linalg.scaled_contract
+
+// -----
+
func.func @fill_tensor(%arg0 : index, %arg1 : index, %arg2 : f32) -> tensor<?x?xf32> {
%0 = tensor.empty(%arg0, %arg1) : tensor<?x?xf32>
%1 = linalg.fill ins(%arg2 : f32) outs(%0 : tensor<?x?xf32>) -> tensor<?x?xf32>
diff --git a/mlir/test/Dialect/Linalg/transform-op-fuse.mlir b/mlir/test/Dialect/Linalg/transform-op-fuse.mlir
index a9e3f06736174..bf6f691261baf 100644
--- a/mlir/test/Dialect/Linalg/transform-op-fuse.mlir
+++ b/mlir/test/Dialect/Linalg/transform-op-fuse.mlir
@@ -724,3 +724,178 @@ module attributes {transform.with_named_sequence} {
transform.yield
}
}
+
+// -----
+
+// Tiling and fusing `linalg.scaled_contract` must respect the block-scaling
+// scheme encoded in the scale indexing maps: along every block-scaled iteration
+// dimension the tile size must divide or be divisible by the block factor.
+
+// CHECK-DAG: #[[$MAP_M:.+]] = affine_map<(d0) -> (d0 floordiv 32)>
+// CHECK-LABEL: func.func @fuse_scaled_contract_with_producer(
+// CHECK-SAME: %[[A:[a-zA-Z0-9]+]]: tensor<256x512xi8>
+// CHECK-SAME: %[[SCALE_A:[a-zA-Z0-9]+]]: tensor<8x4xf8E8M0FNU>
+// CHECK-SAME: %[[B:[a-zA-Z0-9]+]]: tensor<128x512xi8>
+// CHECK-SAME: %[[SCALE_B:[a-zA-Z0-9]+]]: tensor<128xf8E8M0FNU>
+// CHECK-SAME: %[[INIT:[a-zA-Z0-9]+]]: tensor<256x128xf32>
+func.func @fuse_scaled_contract_with_producer(%A: tensor<256x512xi8>, %scaleA: tensor<8x4xf8E8M0FNU>,
+ %B: tensor<128x512xi8>, %scaleB: tensor<128xf8E8M0FNU>,
+ %init: tensor<256x128xf32>) -> tensor<256x128xf32> {
+// CHECK-DAG: %[[CST:.+]] = arith.constant 0.0{{.*}} : f32
+// CHECK: scf.for %[[IV_M:[a-zA-Z0-9]+]] = %{{.+}} step %{{.+}} iter_args(%[[INIT_M:.+]] = %[[INIT]])
+// CHECK: scf.for %[[IV_N:[a-zA-Z0-9]+]] = %{{.+}} step %{{.+}} iter_args(%[[INIT_N:.+]] = %[[INIT_M]])
+// CHECK-DAG: %[[OFF_M:.+]] = affine.apply #[[$MAP_M]](%[[IV_M]])
+// CHECK-DAG: %[[A_TILE:.+]] = tensor.extract_slice %[[A]][%[[IV_M]], 0] [32, 512] [1, 1]
+// CHECK-DAG: %[[SA_TILE:.+]] = tensor.extract_slice %[[SCALE_A]][%[[OFF_M]], 0] [1, 4] [1, 1]
+// CHECK-DAG: %[[B_TILE:.+]] = tensor.extract_slice %[[B]][%[[IV_N]], 0] [16, 512] [1, 1]
+// CHECK-DAG: %[[SB_TILE:.+]] = tensor.extract_slice %[[SCALE_B]][%[[IV_N]]] [16] [1]
+// CHECK-DAG: %[[C_TILE:.+]] = tensor.extract_slice %[[INIT_N]][%[[IV_M]], %[[IV_N]]] [32, 16] [1, 1]
+// CHECK: %[[FILL:.+]] = linalg.fill ins(%[[CST]] : f32) outs(%[[C_TILE]] : tensor<32x16xf32>)
+// CHECK: %[[RES:.+]] = linalg.scaled_contract
+// CHECK-SAME: ins(%[[A_TILE]], %[[SA_TILE]], %[[B_TILE]], %[[SB_TILE]] :
+// CHECK-SAME: outs(%[[FILL]] : tensor<32x16xf32>) -> tensor<32x16xf32>
+// CHECK: tensor.insert_slice %[[RES]] into %[[INIT_N]][%[[IV_M]], %[[IV_N]]] [32, 16] [1, 1]
+ %cst = arith.constant 0.0 : f32
+ %C = linalg.fill ins(%cst : f32) outs(%init : tensor<256x128xf32>) -> tensor<256x128xf32>
+ %D = linalg.scaled_contract
+ indexing_maps = [
+ affine_map<(m, n, k) -> (m, k)>,
+ affine_map<(m, n, k) -> (m floordiv 32, k floordiv 128)>,
+ affine_map<(m, n, k) -> (n, k)>,
+ affine_map<(m, n, k) -> (n)>,
+ affine_map<(m, n, k) -> (m, n)>]
+ ins(%A, %scaleA, %B, %scaleB
+ : tensor<256x512xi8>, tensor<8x4xf8E8M0FNU>, tensor<128x512xi8>, tensor<128xf8E8M0FNU>)
+ outs(%C : tensor<256x128xf32>) -> tensor<256x128xf32>
+ return %D : tensor<256x128xf32>
+}
+module attributes {transform.with_named_sequence} {
+ transform.named_sequence @__transform_main(%arg1: !transform.any_op {transform.readonly}) {
+ %0 = transform.structured.match ops{["linalg.scaled_contract"]} in %arg1 : (!transform.any_op) -> !transform.any_op
+ %1, %loops:2 = transform.structured.fuse %0 tile_sizes [32, 16, 0]
+ : (!transform.any_op) -> (!transform.any_op, !transform.any_op, !transform.any_op)
+ transform.yield
+ }
+}
+
+// -----
+
+// CHECK-DAG: #[[$MAP_M:.+]] = affine_map<(d0) -> (d0 floordiv 32)>
+// CHECK-LABEL: func.func @fuse_scaled_contract_with_consumer(
+// CHECK-SAME: %[[A:[a-zA-Z0-9]+]]: tensor<256x512xi8>
+// CHECK-SAME: %[[SCALE_A:[a-zA-Z0-9]+]]: tensor<8x4xf8E8M0FNU>
+// CHECK-SAME: %[[B:[a-zA-Z0-9]+]]: tensor<128x512xi8>
+// CHECK-SAME: %[[SCALE_B:[a-zA-Z0-9]+]]: tensor<128xf8E8M0FNU>
+// CHECK-SAME: %[[C:[a-zA-Z0-9]+]]: tensor<256x128xf32>
+// CHECK-SAME: %[[OUT:[a-zA-Z0-9]+]]: tensor<256x128xf32>
+func.func @fuse_scaled_contract_with_consumer(%A: tensor<256x512xi8>, %scaleA: tensor<8x4xf8E8M0FNU>,
+ %B: tensor<128x512xi8>, %scaleB: tensor<128xf8E8M0FNU>,
+ %C: tensor<256x128xf32>, %out: tensor<256x128xf32>) -> tensor<256x128xf32> {
+// CHECK: scf.for %[[IV_M:[a-zA-Z0-9]+]] = %{{.+}} step %{{.+}} iter_args(%[[INIT_M:.+]] = %[[OUT]])
+// CHECK: scf.for %[[IV_N:[a-zA-Z0-9]+]] = %{{.+}} step %{{.+}} iter_args(%[[INIT_N:.+]] = %[[INIT_M]])
+// CHECK-DAG: %[[OFF_M:.+]] = affine.apply #[[$MAP_M]](%[[IV_M]])
+// CHECK-DAG: %[[A_TILE:.+]] = tensor.extract_slice %[[A]][%[[IV_M]], 0] [32, 512] [1, 1]
+// CHECK-DAG: %[[SA_TILE:.+]] = tensor.extract_slice %[[SCALE_A]][%[[OFF_M]], 0] [1, 4] [1, 1]
+// CHECK-DAG: %[[B_TILE:.+]] = tensor.extract_slice %[[B]][%[[IV_N]], 0] [16, 512] [1, 1]
+// CHECK-DAG: %[[SB_TILE:.+]] = tensor.extract_slice %[[SCALE_B]][%[[IV_N]]] [16] [1]
+// CHECK-DAG: %[[C_TILE:.+]] = tensor.extract_slice %[[C]][%[[IV_M]], %[[IV_N]]] [32, 16] [1, 1]
+// CHECK: %[[SC:.+]] = linalg.scaled_contract
+// CHECK-SAME: ins(%[[A_TILE]], %[[SA_TILE]], %[[B_TILE]], %[[SB_TILE]] :
+// CHECK-SAME: outs(%[[C_TILE]] : tensor<32x16xf32>) -> tensor<32x16xf32>
+// CHECK: %[[OUT_TILE:.+]] = tensor.extract_slice %[[INIT_N]][%[[IV_M]], %[[IV_N]]] [32, 16] [1, 1]
+// CHECK: %[[COPY:.+]] = linalg.copy ins(%[[SC]] : tensor<32x16xf32>) outs(%[[OUT_TILE]] : tensor<32x16xf32>)
+// CHECK: tensor.insert_slice %[[COPY]] into %[[INIT_N]][%[[IV_M]], %[[IV_N]]] [32, 16] [1, 1]
+ %D = linalg.scaled_contract
+ indexing_maps = [
+ affine_map<(m, n, k) -> (m, k)>,
+ affine_map<(m, n, k) -> (m floordiv 32, k floordiv 128)>,
+ affine_map<(m, n, k) -> (n, k)>,
+ affine_map<(m, n, k) -> (n)>,
+ affine_map<(m, n, k) -> (m, n)>]
+ ins(%A, %scaleA, %B, %scaleB
+ : tensor<256x512xi8>, tensor<8x4xf8E8M0FNU>, tensor<128x512xi8>, tensor<128xf8E8M0FNU>)
+ outs(%C : tensor<256x128xf32>) -> tensor<256x128xf32>
+ %E = linalg.copy ins(%D : tensor<256x128xf32>) outs(%out : tensor<256x128xf32>) -> tensor<256x128xf32>
+ return %E : tensor<256x128xf32>
+}
+module attributes {transform.with_named_sequence} {
+ transform.named_sequence @__transform_main(%arg1: !transform.any_op {transform.readonly}) {
+ %0 = transform.structured.match ops{["linalg.copy"]} in %arg1 : (!transform.any_op) -> !transform.any_op
+ %1, %loops:2 = transform.structured.fuse %0 tile_sizes [32, 16]
+ : (!transform.any_op) -> (!transform.any_op, !transform.any_op, !transform.any_op)
+ transform.yield
+ }
+}
+
+// -----
+
+func.func @negative_fuse_scaled_contract_partial_scaling_block(%A: tensor<256x512xi8>, %scaleA: tensor<8x4xf8E8M0FNU>,
+ %B: tensor<128x512xi8>, %scaleB: tensor<128xf8E8M0FNU>,
+ %init: tensor<256x128xf32>) -> tensor<256x128xf32> {
+ %cst = arith.constant 0.0 : f32
+ %C = linalg.fill ins(%cst : f32) outs(%init : tensor<256x128xf32>) -> tensor<256x128xf32>
+ // expected-error @below {{'linalg.scaled_contract' op tiling is not supported for the semi-affine indexing map: tile size 48 for dimension d0 must divide or be divisible by the step 32}}
+ // expected-error @below {{'linalg.scaled_contract' op failed to tile operation}}
+ // expected-error @below {{'linalg.scaled_contract' op failed to generate tiling loops}}
+ %D = linalg.scaled_contract
+ indexing_maps = [
+ affine_map<(m, n, k) -> (m, k)>,
+ affine_map<(m, n, k) -> (m floordiv 32, k floordiv 128)>,
+ affine_map<(m, n, k) -> (n, k)>,
+ affine_map<(m, n, k) -> (n)>,
+ affine_map<(m, n, k) -> (m, n)>]
+ ins(%A, %scaleA, %B, %scaleB
+ : tensor<256x512xi8>, tensor<8x4xf8E8M0FNU>, tensor<128x512xi8>, tensor<128xf8E8M0FNU>)
+ outs(%C : tensor<256x128xf32>) -> tensor<256x128xf32>
+ return %D : tensor<256x128xf32>
+}
+module attributes {transform.with_named_sequence} {
+ transform.named_sequence @__transform_main(%arg1: !transform.any_op {transform.readonly}) {
+ %0 = transform.structured.match ops{["linalg.scaled_contract"]} in %arg1 : (!transform.any_op) -> !transform.any_op
+ %1, %loops:2 = transform.structured.fuse %0 tile_sizes [48, 16, 0]
+ : (!transform.any_op) -> (!transform.any_op, !transform.any_op, !transform.any_op)
+ transform.yield
+ }
+}
+
+// -----
+
+// CHECK-LABEL: func.func @negative_fuse_scaled_contract_partial_consumer_scaling_block(
+// CHECK-SAME: %[[A:[a-zA-Z0-9]+]]: tensor<256x512xi8>
+// CHECK-SAME: %[[SCALE_A:[a-zA-Z0-9]+]]: tensor<8x4xf8E8M0FNU>
+// CHECK-SAME: %[[B:[a-zA-Z0-9]+]]: tensor<128x512xi8>
+// CHECK-SAME: %[[SCALE_B:[a-zA-Z0-9]+]]: tensor<128xf8E8M0FNU>
+// CHECK-SAME: %[[C:[a-zA-Z0-9]+]]: tensor<256x128xf32>
+// CHECK-SAME: %[[OUT:[a-zA-Z0-9]+]]: tensor<256x128xf32>
+func.func @negative_fuse_scaled_contract_partial_consumer_scaling_block(%A: tensor<256x512xi8>, %scaleA: tensor<8x4xf8E8M0FNU>,
+ %B: tensor<128x512xi8>, %scaleB: tensor<128xf8E8M0FNU>,
+ %C: tensor<256x128xf32>, %out: tensor<256x128xf32>) -> tensor<256x128xf32> {
+// CHECK: %[[FULL:.+]] = linalg.scaled_contract
+// CHECK-SAME: outs(%[[C]] : tensor<256x128xf32>) -> tensor<256x128xf32>
+// CHECK: scf.for %[[IV_M:[a-zA-Z0-9]+]] = %{{.+}} step %{{.+}}
+// CHECK: scf.for %[[IV_N:[a-zA-Z0-9]+]] = %{{.+}} step %{{.+}}
+// CHECK: %[[D_TILE:.+]] = tensor.extract_slice %[[FULL]][%[[IV_M]], %[[IV_N]]] [%{{.+}}, 16] [1, 1]
+// CHECK: %[[COPY:.+]] = linalg.copy ins(%[[D_TILE]] :
+// CHECK: tensor.insert_slice %[[COPY]]
+ // expected-error @below {{'linalg.scaled_contract' op tiling is not supported for the semi-affine indexing map: tile size 48 for dimension d0 must divide or be divisible by the step 32}}
+ %D = linalg.scaled_contract
+ indexing_maps = [
+ affine_map<(m, n, k) -> (m, k)>,
+ affine_map<(m, n, k) -> (m floordiv 32, k floordiv 128)>,
+ affine_map<(m, n, k) -> (n, k)>,
+ affine_map<(m, n, k) -> (n)>,
+ affine_map<(m, n, k) -> (m, n)>]
+ ins(%A, %scaleA, %B, %scaleB
+ : tensor<256x512xi8>, tensor<8x4xf8E8M0FNU>, tensor<128x512xi8>, tensor<128xf8E8M0FNU>)
+ outs(%C : tensor<256x128xf32>) -> tensor<256x128xf32>
+ %E = linalg.copy ins(%D : tensor<256x128xf32>) outs(%out : tensor<256x128xf32>) -> tensor<256x128xf32>
+ return %E : tensor<256x128xf32>
+}
+module attributes {transform.with_named_sequence} {
+ transform.named_sequence @__transform_main(%arg1: !transform.any_op {transform.readonly}) {
+ %0 = transform.structured.match ops{["linalg.copy"]} in %arg1 : (!transform.any_op) -> !transform.any_op
+ %1, %loops:2 = transform.structured.fuse %0 tile_sizes [48, 16]
+ : (!transform.any_op) -> (!transform.any_op, !transform.any_op, !transform.any_op)
+ transform.yield
+ }
+}
diff --git a/mlir/test/Dialect/Linalg/transform-op-tile.mlir b/mlir/test/Dialect/Linalg/transform-op-tile.mlir
index eaa9d4e56e8fc..049d60ae9f7f3 100644
--- a/mlir/test/Dialect/Linalg/transform-op-tile.mlir
+++ b/mlir/test/Dialect/Linalg/transform-op-tile.mlir
@@ -586,3 +586,199 @@ func.func @negative_tile_linalg_matmul_zero_tiles_loop_result_arity_mismatch(
-> tensor<128x128xf32>
return %0 : tensor<128x128xf32>
}
+
+// -----
+
+// Tiling `linalg.scaled_contract` must respect the block-scaling scheme encoded in the scale
+// indexing maps: along every iteration dimension that is block-scaled (`d floordiv B`),
+// the tile size must divide or be divisible by the block factor `B`.
+
+// CHECK-LABEL: func.func @tile_scaled_contract_multiple_of_block_scale(
+// CHECK-SAME: %[[A:[a-zA-Z0-9]+]]: tensor<256x512xi8>
+// CHECK-SAME: %[[SCALE_A:[a-zA-Z0-9]+]]: tensor<8x4xf8E8M0FNU>
+// CHECK-SAME: %[[B:[a-zA-Z0-9]+]]: tensor<128x512xi8>
+// CHECK-SAME: %[[SCALE_B:[a-zA-Z0-9]+]]: tensor<128xf8E8M0FNU>
+// CHECK-SAME: %[[C:[a-zA-Z0-9]+]]: tensor<256x128xf32>
+// CHECK-DAG: %[[C16:.+]] = arith.constant 16 : index
+// CHECK-DAG: %[[C32:.+]] = arith.constant 32 : index
+// CHECK-DAG: %[[C128:.+]] = arith.constant 128 : index
+// CHECK: scf.for %[[IV_M:[a-zA-Z0-9]+]] = %{{.+}} to %{{.+}} step %[[C32]] iter_args(%[[INIT_M:.+]] = %[[C]])
+// CHECK: scf.for %[[IV_N:[a-zA-Z0-9]+]] = %{{.+}} to %{{.+}} step %[[C16]] iter_args(%[[INIT_N:.+]] = %[[INIT_M]])
+// CHECK: scf.for %[[IV_K:[a-zA-Z0-9]+]] = %{{.+}} to %{{.+}} step %[[C128]] iter_args(%[[INIT_K:.+]] = %[[INIT_N]])
+// CHECK-DAG: %[[OFF_M:.+]] = affine.apply affine_map<(d0) -> (d0 floordiv 32)>(%[[IV_M]])
+// CHECK-DAG: %[[OFF_K:.+]] = affine.apply affine_map<(d0) -> (d0 floordiv 128)>(%[[IV_K]])
+// CHECK-DAG: %[[A_TILE:.+]] = tensor.extract_slice %[[A]][%[[IV_M]], %[[IV_K]]] [32, 128] [1, 1]
+// CHECK-DAG: %[[SA_TILE:.+]] = tensor.extract_slice %[[SCALE_A]][%[[OFF_M]], %[[OFF_K]]] [1, 1] [1, 1]
+// CHECK-DAG: %[[B_TILE:.+]] = tensor.extract_slice %[[B]][%[[IV_N]], %[[IV_K]]] [16, 128] [1, 1]
+// CHECK-DAG: %[[SB_TILE:.+]] = tensor.extract_slice %[[SCALE_B]][%[[IV_N]]] [16] [1]
+// CHECK-DAG: %[[C_TILE:.+]] = tensor.extract_slice %[[INIT_K]][%[[IV_M]], %[[IV_N]]] [32, 16] [1, 1]
+// CHECK: %[[RES:.+]] = linalg.scaled_contract
+// CHECK-SAME: ins(%[[A_TILE]], %[[SA_TILE]], %[[B_TILE]], %[[SB_TILE]] :
+// CHECK-SAME: outs(%[[C_TILE]] :
+// CHECK-SAME: -> tensor<32x16xf32>
+// CHECK: tensor.insert_slice %[[RES]] into %[[INIT_K]][%[[IV_M]], %[[IV_N]]] [32, 16] [1, 1]
+func.func @tile_scaled_contract_multiple_of_block_scale(
+ %A: tensor<256x512xi8>, %scaleA: tensor<8x4xf8E8M0FNU>,
+ %B: tensor<128x512xi8>, %scaleB: tensor<128xf8E8M0FNU>,
+ %C: tensor<256x128xf32>) -> tensor<256x128xf32> {
+ %D = linalg.scaled_contract
+ indexing_maps = [
+ affine_map<(m, n, k) -> (m, k)>,
+ affine_map<(m, n, k) -> (m floordiv 32, k floordiv 128)>,
+ affine_map<(m, n, k) -> (n, k)>,
+ affine_map<(m, n, k) -> (n)>,
+ affine_map<(m, n, k) -> (m, n)>]
+ ins(%A, %scaleA, %B, %scaleB
+ : tensor<256x512xi8>, tensor<8x4xf8E8M0FNU>, tensor<128x512xi8>, tensor<128xf8E8M0FNU>)
+ outs(%C : tensor<256x128xf32>) -> tensor<256x128xf32>
+ return %D : tensor<256x128xf32>
+}
+module attributes {transform.with_named_sequence} {
+ transform.named_sequence @__transform_main(%arg1: !transform.any_op {transform.readonly}) {
+ %0 = transform.structured.match ops{["linalg.scaled_contract"]} in %arg1 : (!transform.any_op) -> !transform.any_op
+ %1, %loops:3 = transform.structured.tile_using_for %0 tile_sizes [32, 16, 128] : (!transform.any_op) -> (!transform.any_op, !transform.any_op, !transform.any_op, !transform.any_op)
+ transform.yield
+ }
+}
+
+// -----
+
+// CHECK-LABEL: func.func @tile_scaled_contract_divisor_of_block_scale(
+// CHECK-SAME: %[[A:[a-zA-Z0-9]+]]: tensor<256x512xi8>
+// CHECK-SAME: %[[SCALE_A:[a-zA-Z0-9]+]]: tensor<8x4xf8E8M0FNU>
+// CHECK: scf.for %[[IV_M:[a-zA-Z0-9]+]] = %{{.+}} step %{{.+}}
+// CHECK: scf.for %[[IV_N:[a-zA-Z0-9]+]] = %{{.+}} step %{{.+}}
+// CHECK: scf.for %[[IV_K:[a-zA-Z0-9]+]] = %{{.+}} step %{{.+}}
+// CHECK-DAG: %[[OFF_M:.+]] = affine.apply affine_map<(d0) -> (d0 floordiv 32)>(%[[IV_M]])
+// CHECK-DAG: %[[OFF_K:.+]] = affine.apply affine_map<(d0) -> (d0 floordiv 128)>(%[[IV_K]])
+// CHECK-DAG: %[[A_TILE:.+]] = tensor.extract_slice %[[A]][%[[IV_M]], %[[IV_K]]] [32, 64] [1, 1]
+// CHECK-DAG: %[[SA_TILE:.+]] = tensor.extract_slice %[[SCALE_A]][%[[OFF_M]], %[[OFF_K]]] [1, 1] [1, 1]
+// CHECK: %[[RES:.+]] = linalg.scaled_contract
+// CHECK-SAME: -> tensor<32x16xf32>
+func.func @tile_scaled_contract_divisor_of_block_scale(
+ %A: tensor<256x512xi8>, %scaleA: tensor<8x4xf8E8M0FNU>,
+ %B: tensor<128x512xi8>, %scaleB: tensor<128xf8E8M0FNU>,
+ %C: tensor<256x128xf32>) -> tensor<256x128xf32> {
+ %D = linalg.scaled_contract
+ indexing_maps = [
+ affine_map<(m, n, k) -> (m, k)>,
+ affine_map<(m, n, k) -> (m floordiv 32, k floordiv 128)>,
+ affine_map<(m, n, k) -> (n, k)>,
+ affine_map<(m, n, k) -> (n)>,
+ affine_map<(m, n, k) -> (m, n)>]
+ ins(%A, %scaleA, %B, %scaleB
+ : tensor<256x512xi8>, tensor<8x4xf8E8M0FNU>, tensor<128x512xi8>, tensor<128xf8E8M0FNU>)
+ outs(%C : tensor<256x128xf32>) -> tensor<256x128xf32>
+ return %D : tensor<256x128xf32>
+}
+module attributes {transform.with_named_sequence} {
+ transform.named_sequence @__transform_main(%arg1: !transform.any_op {transform.readonly}) {
+ %0 = transform.structured.match ops{["linalg.scaled_contract"]} in %arg1 : (!transform.any_op) -> !transform.any_op
+ %1, %loops:3 = transform.structured.tile_using_for %0 tile_sizes [32, 16, 64] : (!transform.any_op) -> (!transform.any_op, !transform.any_op, !transform.any_op, !transform.any_op)
+ transform.yield
+ }
+}
+
+// -----
+
+// Scalar (whole-tensor) scale on the LHS and per-dimension scale on the RHS
+// impose no additional constraint, so the tile sizes can be arbitrary (and
+// need not divide the operand shapes).
+
+// CHECK-LABEL: func.func @tile_scaled_contract_scalar_and_dimension_scale(
+// CHECK-SAME: %[[A:[a-zA-Z0-9]+]]: tensor<256x100xi8>
+// CHECK-SAME: %[[SCALE_A:[a-zA-Z0-9]+]]: tensor<f8E8M0FNU>
+// CHECK: scf.for %[[IV_M:[a-zA-Z0-9]+]] = %{{.+}} step %{{.+}}
+// CHECK: scf.for %[[IV_N:[a-zA-Z0-9]+]] = %{{.+}} step %{{.+}}
+// CHECK: scf.for %[[IV_K:[a-zA-Z0-9]+]] = %{{.+}} step %{{.+}}
+// CHECK: %[[RES:.+]] = linalg.scaled_contract
+// CHECK-SAME: ins(%{{.+}}, %[[SCALE_A]], %{{.+}}, %{{.+}} :
+// CHECK-SAME: -> tensor<?x?xf32>
+func.func @tile_scaled_contract_scalar_and_dimension_scale(
+ %A: tensor<256x100xi8>, %scaleA: tensor<f8E8M0FNU>,
+ %B: tensor<100x128xi8>, %scaleB: tensor<128xf8E8M0FNU>,
+ %C: tensor<256x128xf32>) -> tensor<256x128xf32> {
+ %D = linalg.scaled_contract
+ indexing_maps = [
+ affine_map<(m, n, k) -> (m, k)>,
+ affine_map<(m, n, k) -> ()>,
+ affine_map<(m, n, k) -> (k, n)>,
+ affine_map<(m, n, k) -> (n)>,
+ affine_map<(m, n, k) -> (m, n)>]
+ ins(%A, %scaleA, %B, %scaleB
+ : tensor<256x100xi8>, tensor<f8E8M0FNU>, tensor<100x128xi8>, tensor<128xf8E8M0FNU>)
+ outs(%C : tensor<256x128xf32>) -> tensor<256x128xf32>
+ return %D : tensor<256x128xf32>
+}
+module attributes {transform.with_named_sequence} {
+ transform.named_sequence @__transform_main(%arg1: !transform.any_op {transform.readonly}) {
+ %0 = transform.structured.match ops{["linalg.scaled_contract"]} in %arg1 : (!transform.any_op) -> !transform.any_op
+ %1, %loops:3 = transform.structured.tile_using_for %0 tile_sizes [30, 17, 25] : (!transform.any_op) -> (!transform.any_op, !transform.any_op, !transform.any_op, !transform.any_op)
+ transform.yield
+ }
+}
+
+// -----
+
+// Dynamic operand shapes with static, valid tile sizes. The constraint is
+// checked against the (constant) upper bound of the tile size, so block-scaled
+// tiling of dynamic tensors is allowed as long as the tile sizes divide or are
+// divisible by the block factors.
+// CHECK-LABEL: func.func @tile_scaled_contract_dynamic_shapes(
+// CHECK: scf.for %[[IV_M:[a-zA-Z0-9]+]] = %{{.+}} step %{{.+}}
+// CHECK: scf.for %[[IV_N:[a-zA-Z0-9]+]] = %{{.+}} step %{{.+}}
+// CHECK: scf.for %[[IV_K:[a-zA-Z0-9]+]] = %{{.+}} step %{{.+}}
+// CHECK: %[[RES:.+]] = linalg.scaled_contract
+// CHECK-SAME: -> tensor<?x?xf32>
+func.func @tile_scaled_contract_dynamic_shapes(
+ %A: tensor<?x?xi8>, %scaleA: tensor<?x?xf8E8M0FNU>,
+ %B: tensor<?x?xi8>, %scaleB: tensor<?xf8E8M0FNU>,
+ %C: tensor<?x?xf32>) -> tensor<?x?xf32> {
+ %D = linalg.scaled_contract
+ indexing_maps = [
+ affine_map<(m, n, k) -> (m, k)>,
+ affine_map<(m, n, k) -> (m floordiv 32, k floordiv 128)>,
+ affine_map<(m, n, k) -> (n, k)>,
+ affine_map<(m, n, k) -> (n)>,
+ affine_map<(m, n, k) -> (m, n)>]
+ ins(%A, %scaleA, %B, %scaleB
+ : tensor<?x?xi8>, tensor<?x?xf8E8M0FNU>, tensor<?x?xi8>, tensor<?xf8E8M0FNU>)
+ outs(%C : tensor<?x?xf32>) -> tensor<?x?xf32>
+ return %D : tensor<?x?xf32>
+}
+module attributes {transform.with_named_sequence} {
+ transform.named_sequence @__transform_main(%arg1: !transform.any_op {transform.readonly}) {
+ %0 = transform.structured.match ops{["linalg.scaled_contract"]} in %arg1 : (!transform.any_op) -> !transform.any_op
+ %1, %loops:3 = transform.structured.tile_using_for %0 tile_sizes [32, 16, 128] : (!transform.any_op) -> (!transform.any_op, !transform.any_op, !transform.any_op, !transform.any_op)
+ transform.yield
+ }
+}
+
+// -----
+
+func.func @negative_tile_scaled_contract_non_divisible_block_scale(
+ %A: tensor<256x512xi8>, %scaleA: tensor<8x4xf8E8M0FNU>,
+ %B: tensor<128x512xi8>, %scaleB: tensor<128xf8E8M0FNU>,
+ %C: tensor<256x128xf32>) -> tensor<256x128xf32> {
+ // expected-error @below {{'linalg.scaled_contract' op tiling is not supported for the semi-affine indexing map: tile size 48 for dimension d0 must divide or be divisible by the step 32}}
+ // expected-error @below {{'linalg.scaled_contract' op failed to tile operation}}
+ // expected-error @below {{'linalg.scaled_contract' op failed to generate tiling loops}}
+ %D = linalg.scaled_contract
+ indexing_maps = [
+ affine_map<(m, n, k) -> (m, k)>,
+ affine_map<(m, n, k) -> (m floordiv 32, k floordiv 128)>,
+ affine_map<(m, n, k) -> (n, k)>,
+ affine_map<(m, n, k) -> (n)>,
+ affine_map<(m, n, k) -> (m, n)>]
+ ins(%A, %scaleA, %B, %scaleB
+ : tensor<256x512xi8>, tensor<8x4xf8E8M0FNU>, tensor<128x512xi8>, tensor<128xf8E8M0FNU>)
+ outs(%C : tensor<256x128xf32>) -> tensor<256x128xf32>
+ return %D : tensor<256x128xf32>
+}
+module attributes {transform.with_named_sequence} {
+ transform.named_sequence @__transform_main(%arg1: !transform.any_op {transform.readonly}) {
+ %0 = transform.structured.match ops{["linalg.scaled_contract"]} in %arg1 : (!transform.any_op) -> !transform.any_op
+ %1, %loops:3 = transform.structured.tile_using_for %0 tile_sizes [48, 16, 128] : (!transform.any_op) -> (!transform.any_op, !transform.any_op, !transform.any_op, !transform.any_op)
+ transform.yield
+ }
+}
More information about the Mlir-commits
mailing list