[LLVMGPUVectorDistribute] Distribute vector.step (#18903)
This commit enables distribution
of vector.step via recreating the distributed
smaller constant and its dynamic offsets.
Signed-off-by: Manupa Karunaratne <manupa.karunaratne@amd.com>
diff --git a/compiler/src/iree/compiler/Codegen/Common/GPU/GPUNestedLayoutDistributionPatterns.cpp b/compiler/src/iree/compiler/Codegen/Common/GPU/GPUNestedLayoutDistributionPatterns.cpp
index 4675d1d..80647b9 100644
--- a/compiler/src/iree/compiler/Codegen/Common/GPU/GPUNestedLayoutDistributionPatterns.cpp
+++ b/compiler/src/iree/compiler/Codegen/Common/GPU/GPUNestedLayoutDistributionPatterns.cpp
@@ -930,6 +930,190 @@
}
};
+struct DistributeStep final : OpDistributionPattern<vector::StepOp> {
+ using OpDistributionPattern::OpDistributionPattern;
+
+ // This is a helper aggregate
+ // to hold the information about
+ // a dimension.
+ // For e.g. : 3x4x2 shape will
+ // have lengths = [3, 4, 2]
+ // and strides = [8, 2, 1]
+ struct DimInfo {
+ std::optional<Value> dimIdx;
+ int64_t dimLen;
+ int64_t dimStride;
+ };
+
+ // This is a helper function to extract the remaining
+ // dimensions with their original strides once the
+ // distributed dimensions are extracted out
+ // threads
+ // V
+ // E.g. 3 x 4 x 2
+ // This will return back remaining dimensions that
+ // have lengths = [3, 2] and strides = [8, 1]
+ SmallVector<DimInfo> getRemainingDims(ArrayRef<DimInfo> distributedStrides,
+ int64_t originalLen) const {
+ SmallVector<DimInfo> remainingDims;
+ int64_t currLen = originalLen;
+ for (const DimInfo &dInfo : distributedStrides) {
+ if (dInfo.dimStride != 0) {
+ int64_t dStride = dInfo.dimStride;
+ int64_t dLen = dInfo.dimLen;
+ int64_t higherStride = dLen * dStride;
+ if (higherStride < currLen) {
+ remainingDims.push_back(
+ {std::nullopt, currLen / higherStride, higherStride});
+ }
+ currLen = dStride;
+ }
+ }
+ remainingDims.push_back({std::nullopt, currLen, 1});
+ return remainingDims;
+ }
+
+ // This is a helper to extract lengths of all dimensions
+ SmallVector<int64_t> getLens(ArrayRef<DimInfo> dimInfos) const {
+ SmallVector<int64_t> lens;
+ lens.reserve(dimInfos.size());
+ for (const DimInfo &dInfo : dimInfos) {
+ lens.push_back(dInfo.dimLen);
+ }
+ return lens;
+ }
+
+ // Once we are in the realm of remaining dimensions,
+ // the strides are not packed. This is a helper to
+ // obtain the packed strides of the remaining dimensions.
+ // (See above for an example of remaining dimensions under
+ // getRemainingDims)
+ SmallVector<int64_t> getPackedStrides(ArrayRef<DimInfo> dims) const {
+ SmallVector<int64_t> lens = getLens(dims);
+ int64_t elementCount = ShapedType::getNumElements(lens);
+ SmallVector<int64_t> packedStrides;
+ int64_t currStride = elementCount;
+ for (int64_t len : lens) {
+ currStride = currStride / len;
+ packedStrides.push_back(currStride);
+ }
+ return packedStrides;
+ }
+
+ // This function emulates the slicing of otherwise large constant
+ // across threads and subgroups.
+ VectorValue generateSlicedStep(OpBuilder &builder, Location loc,
+ ArrayRef<DimInfo> distributedDims,
+ int64_t distributedLen,
+ int64_t originalLen) const {
+ SmallVector<DimInfo> remainingDims =
+ getRemainingDims(distributedDims, originalLen);
+ SmallVector<int64_t> remainingPackedStrides =
+ getPackedStrides(remainingDims);
+
+ SmallVector<APInt> offsets;
+ offsets.reserve(distributedLen);
+ // As for a complex example what the following
+ // maths would achieve:
+ // wave
+ // | threads
+ // V V
+ // 2 x 3 x 4 x 2 = 0 1 2 .... 48
+ // say vector.step : vector<48xindex> is to be distributed.
+ // --------------------------------------------------------
+ // The the distribution should be as follows:
+ // wave0:
+ // t0: 0 1 24 25
+ // t1: 2 3 26 27
+ // t2: 4 5 28 29
+ // t4: 6 7 30 31
+ //
+ // wave1:
+ // t0: 8 9 32 33
+ // t1: 10 11 34 35
+ // t2: 12 13 36 37
+ // t4: 14 15 38 39
+ // ... etc
+ //
+ // So wave0 & t0 value this constant offset that we generate
+ // below initially. Then followed by thread and subgroup weighted
+ // addition that is weighted by their stride.
+ for (size_t i = 0; i < distributedLen; i++) {
+ int64_t offset = 0;
+ for (const auto &[dimInfo, packedStride] :
+ zip(remainingDims, remainingPackedStrides)) {
+ offset += ((i / packedStride) % dimInfo.dimLen) * dimInfo.dimStride;
+ }
+ offsets.push_back(APInt(/*width=*/64, offset));
+ }
+ VectorType offsetType =
+ VectorType::get({distributedLen}, builder.getIndexType());
+ auto constOffset = builder.create<arith::ConstantOp>(
+ loc, DenseElementsAttr::get(offsetType, offsets));
+ Value finalOffset = constOffset;
+ for (const DimInfo &dimInfo : distributedDims) {
+ assert(dimInfo.dimIdx.has_value());
+ if (dimInfo.dimStride != 0) {
+ auto strideVal =
+ builder.create<arith::ConstantIndexOp>(loc, dimInfo.dimStride);
+ auto dimIdxOffsetPerElem = builder.create<arith::MulIOp>(
+ loc, strideVal, dimInfo.dimIdx.value());
+ auto dimIdxOffset = builder.create<vector::BroadcastOp>(
+ loc, offsetType, dimIdxOffsetPerElem);
+ finalOffset =
+ builder.create<arith::AddIOp>(loc, finalOffset, dimIdxOffset);
+ }
+ }
+ return cast<VectorValue>(finalOffset);
+ }
+
+ DistributeStep(MLIRContext *context, Value threadId, int64_t subgroupSize)
+ : OpDistributionPattern(context), threadId(threadId),
+ subgroupSize(subgroupSize) {}
+ LogicalResult matchAndRewrite(vector::StepOp stepOp,
+ DistributionSignature &signature,
+ PatternRewriter &rewriter) const override {
+ Location loc = stepOp.getLoc();
+ VectorValue result = stepOp.getResult();
+ NestedLayoutAttr resultLayout =
+ dyn_cast<NestedLayoutAttr>(signature[result]);
+ if (!resultLayout) {
+ return rewriter.notifyMatchFailure(
+ stepOp, "missing nested layout for step op result");
+ }
+ SmallVector<Value> subgroupIndices, threadIndices;
+ populateWarpAndThreadIndices(rewriter, threadId, subgroupSize, resultLayout,
+ subgroupIndices, threadIndices);
+ ArrayRef<int64_t> subgroupStrides = resultLayout.getSubgroupStrides();
+ ArrayRef<int64_t> subgroupLengths = resultLayout.getSubgroupTile();
+ ArrayRef<int64_t> threadStrides = resultLayout.getThreadStrides();
+ ArrayRef<int64_t> threadLengths = resultLayout.getThreadTile();
+ // Step op by definition should be single dimensional.
+ SmallVector<int64_t> distributedShape =
+ signature[result].getDistributedShape();
+
+ int64_t distributedElements = ShapedType::getNumElements(distributedShape);
+ int64_t originalElements = result.getType().getNumElements();
+ SmallVector<DimInfo, 2> distributedDims{
+ {subgroupIndices[0], subgroupLengths[0], subgroupStrides[0]},
+ {threadIndices[0], threadLengths[0], threadStrides[0]}};
+ llvm::sort(distributedDims, [](const DimInfo &lhs, const DimInfo &rhs) {
+ return lhs.dimStride > rhs.dimStride;
+ });
+ VectorValue slicedStepOp = generateSlicedStep(
+ rewriter, loc, distributedDims, distributedElements, originalElements);
+ VectorType finalSlicedStepOpType =
+ VectorType::get({distributedShape}, result.getType().getElementType());
+ auto finalSlicedStepOp = rewriter.create<vector::ShapeCastOp>(
+ loc, finalSlicedStepOpType, slicedStepOp);
+ replaceOpWithDistributedValues(rewriter, stepOp, {finalSlicedStepOp});
+ return success();
+ }
+
+ Value threadId;
+ int64_t subgroupSize;
+};
+
} // namespace
void populateGPUDistributeNestedLayoutAttrPatterns(RewritePatternSet &patterns,
@@ -944,6 +1128,7 @@
patterns.add<DistributeContract>(patterns.getContext(), subgroupSize,
maxBitsPerShuffle);
patterns.add<DistributeBatchOuterToLayoutConversions>(patterns.getContext());
+ patterns.add<DistributeStep>(patterns.getContext(), threadId, subgroupSize);
}
}; // namespace mlir::iree_compiler
diff --git a/compiler/src/iree/compiler/Codegen/Common/GPU/test/BUILD.bazel b/compiler/src/iree/compiler/Codegen/Common/GPU/test/BUILD.bazel
index 126ebe6..608ff2e 100644
--- a/compiler/src/iree/compiler/Codegen/Common/GPU/test/BUILD.bazel
+++ b/compiler/src/iree/compiler/Codegen/Common/GPU/test/BUILD.bazel
@@ -34,6 +34,7 @@
"gpu_materialize_encoding_gfx1100.mlir",
"gpu_nested_layout_contract_amdgpu.mlir",
"gpu_nested_layout_vector_distribution.mlir",
+ "gpu_nested_layout_vector_distribution_step.mlir",
"gpu_pipeline.mlir",
"gpu_promote_matmul_operands.mlir",
"gpu_reorder_workgroups_static.mlir",
diff --git a/compiler/src/iree/compiler/Codegen/Common/GPU/test/CMakeLists.txt b/compiler/src/iree/compiler/Codegen/Common/GPU/test/CMakeLists.txt
index cddf4eb..ba0a751 100644
--- a/compiler/src/iree/compiler/Codegen/Common/GPU/test/CMakeLists.txt
+++ b/compiler/src/iree/compiler/Codegen/Common/GPU/test/CMakeLists.txt
@@ -30,6 +30,7 @@
"gpu_materialize_encoding_gfx942.mlir"
"gpu_nested_layout_contract_amdgpu.mlir"
"gpu_nested_layout_vector_distribution.mlir"
+ "gpu_nested_layout_vector_distribution_step.mlir"
"gpu_pipeline.mlir"
"gpu_promote_matmul_operands.mlir"
"gpu_reorder_workgroups.mlir"
diff --git a/compiler/src/iree/compiler/Codegen/Common/GPU/test/gpu_nested_layout_vector_distribution_step.mlir b/compiler/src/iree/compiler/Codegen/Common/GPU/test/gpu_nested_layout_vector_distribution_step.mlir
new file mode 100644
index 0000000..fbc5325
--- /dev/null
+++ b/compiler/src/iree/compiler/Codegen/Common/GPU/test/gpu_nested_layout_vector_distribution_step.mlir
@@ -0,0 +1,138 @@
+// RUN: iree-opt --iree-transform-dialect-interpreter --split-input-file --canonicalize --cse --mlir-print-local-scope %s | FileCheck %s
+
+#nested = #iree_vector_ext.nested_layout<
+ subgroup_tile = [1],
+ batch_tile = [4],
+ outer_tile = [1],
+ thread_tile = [4],
+ element_tile = [1],
+
+ subgroup_strides = [0],
+ thread_strides = [16]
+>
+
+func.func @step_1() -> vector<16xindex> {
+ %step = vector.step : vector<16xindex>
+ %stepl = iree_vector_ext.to_layout %step to layout(#nested) : vector<16xindex>
+ return %stepl : vector<16xindex>
+}
+
+builtin.module attributes { transform.with_named_sequence } {
+ transform.named_sequence @__transform_main(%variant_op: !transform.any_op {transform.readonly}) {
+ %top_level_func = transform.structured.match ops{["func.func"]} in %variant_op : (!transform.any_op) -> !transform.any_op
+ transform.iree.test_gpu_vector_distribution %top_level_func : !transform.any_op
+ transform.yield
+ }
+}
+
+// CHECK-LABEL: func @step_1
+// CHECK: %[[CST:.+]] = arith.constant dense<[0, 1, 2, 3]> : vector<4xindex>
+// CHECK: %[[TID:.+]] = affine.apply affine_map<()[s0] -> ((s0 floordiv 16) mod 4)>()[%thread_id_x]
+// CHECK: %[[TID_STRIDE:.+]] = arith.muli %[[TID]], %c16 : index
+// CHECK: %[[TID_STRIDEV:.+]] = vector.broadcast %[[TID_STRIDE]] : index to vector<4xindex>
+// CHECK: %[[OFFSET:.+]] = arith.addi %[[TID_STRIDEV]], %[[CST]] : vector<4xindex>
+
+// -----
+
+#nested = #iree_vector_ext.nested_layout<
+ subgroup_tile = [1],
+ batch_tile = [1],
+ outer_tile = [3],
+ thread_tile = [4],
+ element_tile = [2],
+
+ subgroup_strides = [0],
+ thread_strides = [2]
+>
+
+func.func @step_2() -> vector<24xindex> {
+ %step = vector.step : vector<24xindex>
+ %stepl = iree_vector_ext.to_layout %step to layout(#nested) : vector<24xindex>
+ return %stepl : vector<24xindex>
+}
+
+builtin.module attributes { transform.with_named_sequence } {
+ transform.named_sequence @__transform_main(%variant_op: !transform.any_op {transform.readonly}) {
+ %top_level_func = transform.structured.match ops{["func.func"]} in %variant_op : (!transform.any_op) -> !transform.any_op
+ transform.iree.test_gpu_vector_distribution %top_level_func : !transform.any_op
+ transform.yield
+ }
+}
+
+// CHECK-LABEL: func @step_2
+// CHECK: %[[CST:.+]] = arith.constant dense<[0, 1, 8, 9, 16, 17]> : vector<6xindex>
+// CHECK: %[[TID:.+]] = affine.apply affine_map<()[s0] -> ((s0 floordiv 2) mod 4)>()[%thread_id_x]
+// CHECK: %[[TID_STRIDE:.+]] = arith.muli %[[TID]], %c2 : index
+// CHECK: %[[TID_STRIDEV:.+]] = vector.broadcast %[[TID_STRIDE]] : index to vector<6xindex>
+// CHECK: %[[OFFSET:.+]] = arith.addi %[[TID_STRIDEV]], %[[CST]] : vector<6xindex>
+
+// -----
+
+#nested = #iree_vector_ext.nested_layout<
+ subgroup_tile = [3],
+ batch_tile = [1],
+ outer_tile = [2],
+ thread_tile = [4],
+ element_tile = [2],
+
+ subgroup_strides = [8],
+ thread_strides = [2]
+>
+
+func.func @step_3() -> vector<48xindex> {
+ %step = vector.step : vector<48xindex>
+ %stepl = iree_vector_ext.to_layout %step to layout(#nested) : vector<48xindex>
+ return %stepl : vector<48xindex>
+}
+
+builtin.module attributes { transform.with_named_sequence } {
+ transform.named_sequence @__transform_main(%variant_op: !transform.any_op {transform.readonly}) {
+ %top_level_func = transform.structured.match ops{["func.func"]} in %variant_op : (!transform.any_op) -> !transform.any_op
+ transform.iree.test_gpu_vector_distribution %top_level_func : !transform.any_op
+ transform.yield
+ }
+}
+
+// CHECK-LABEL: func @step_3
+// CHECK: %[[CST:.+]] = arith.constant dense<[0, 1, 24, 25]> : vector<4xindex>
+// CHECK: %[[WID:.+]] = affine.apply affine_map<()[s0] -> ((s0 floordiv 512) mod 3)>()[%thread_id_x]
+// CHECK: %[[TID:.+]] = affine.apply affine_map<()[s0] -> ((s0 floordiv 2) mod 4)>()[%thread_id_x]
+// CHECK: %[[WID_STRIDE:.+]] = arith.muli %[[WID]], %c8 : index
+// CHECK: %[[WID_STRIDEV:.+]] = vector.broadcast %[[WID_STRIDE]] : index to vector<4xindex>
+// CHECK: %[[OFFSET0:.+]] = arith.addi %[[WID_STRIDEV]], %[[CST]] : vector<4xindex>
+// CHECK: %[[TID_STRIDE:.+]] = arith.muli %[[TID]], %c2 : index
+// CHECK: %[[TID_STRIDEV:.+]] = vector.broadcast %[[TID_STRIDE]] : index to vector<4xindex>
+// CHECK: %[[OFFSET1:.+]] = arith.addi %[[OFFSET0]], %[[TID_STRIDEV]] : vector<4xindex>
+
+// -----
+
+#nested = #iree_vector_ext.nested_layout<
+ subgroup_tile = [1],
+ batch_tile = [1],
+ outer_tile = [1],
+ thread_tile = [16],
+ element_tile = [8],
+
+ subgroup_strides = [0],
+ thread_strides = [1]
+>
+
+func.func @step_4() -> vector<128xindex> {
+ %step = vector.step : vector<128xindex>
+ %stepl = iree_vector_ext.to_layout %step to layout(#nested) : vector<128xindex>
+ return %stepl : vector<128xindex>
+}
+
+builtin.module attributes { transform.with_named_sequence } {
+ transform.named_sequence @__transform_main(%variant_op: !transform.any_op {transform.readonly}) {
+ %top_level_func = transform.structured.match ops{["func.func"]} in %variant_op : (!transform.any_op) -> !transform.any_op
+ transform.iree.test_gpu_vector_distribution %top_level_func : !transform.any_op
+ transform.yield
+ }
+}
+
+// CHECK-LABEL: func @step_4
+// CHECK: %[[CST:.+]] = arith.constant dense<[0, 16, 32, 48, 64, 80, 96, 112]> : vector<8xindex>
+// CHECK: %[[TID:.+]] = affine.apply affine_map<()[s0] -> (s0 mod 16)>()[%thread_id_x]
+// CHECK: %[[TIDV:.+]] = vector.broadcast %[[TID]] : index to vector<8xindex>
+// CHECK: %[[OFFSET:.+]] = arith.addi %[[TIDV]], %[[CST]] : vector<8xindex>