MLIR 24.0.0git
XeGPUArrayLengthOptimization.cpp
Go to the documentation of this file.
1//===- XeGPUArrayLengthOptimization.cpp - Array Length Opt -----*- C++ -*-===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8
16#include "llvm/ADT/SmallVector.h"
17
18#define DEBUG_TYPE "xegpu-array-length-optimization"
19
20using namespace mlir;
21
22namespace {
23
24// Fallback subgroup size used when the target uArch cannot be resolved from
25// the op (e.g. standalone unit tests with no chip attribute attached).
26constexpr int64_t DEFAULT_SUBGROUP_SIZE = 16;
27
28/// Return the subgroup size for `op`'s target uArch, falling back to
29/// DEFAULT_SUBGROUP_SIZE if no chip attribute is attached or the chip is not
30/// recognized.
31static int64_t getSubgroupSize(Operation *op) {
32 auto chipStr = xegpu::getChipStr(op);
33 if (!chipStr)
34 return DEFAULT_SUBGROUP_SIZE;
35 const xegpu::uArch::uArch *targetUArch =
36 xegpu::uArch::getUArch(chipStr.value());
37 if (!targetUArch)
38 return DEFAULT_SUBGROUP_SIZE;
39 return targetUArch->getSubgroupSize();
40}
41
42/// Helper to compute array_length from FCD and subgroup size.
43/// TODO: Currently, we are only allowing subgroupSize as our new FCD for LANE
44/// level distribution simplicity. But it can be different, and in the future,
45/// we can add that support.
46static int64_t computeArrayLength(int64_t fcdSize, int64_t subgroupSize) {
47 if (fcdSize <= subgroupSize)
48 return 1;
49 return fcdSize / subgroupSize;
50}
51
52/// Check if a 2D `xegpu.create_nd_tdesc` can be optimized into an
53/// array-length-enabled descriptor. Applies only when the FCD is an integer
54/// multiple of the subgroup size larger than the subgroup size itself and the
55/// tensor desc does not already carry an array_length.
56static bool needsOptimization(xegpu::TensorDescType tdescType,
57 int64_t subgroupSize) {
58 auto shape = tdescType.getShape();
59 if (shape.size() != 2)
60 return false;
61
62 int64_t fcd = shape[1];
63 if (fcd % subgroupSize != 0)
64 return false;
65
66 return fcd > subgroupSize && tdescType.getArrayLength() == 1;
67}
68
69/// Returns true if `loadOp` carries a non-identity transpose attribute. A
70/// transpose of `[0, 1]` is the identity and is therefore treated as absent.
71static bool hasNonIdentityTranspose(xegpu::LoadNdOp loadOp) {
72 auto transpose = loadOp.getTranspose();
73 if (!transpose)
74 return false;
75 ArrayRef<int64_t> perm = *transpose;
76 return !(perm.size() == 2 && perm[0] == 0 && perm[1] == 1);
77}
78
79/// Returns true if `tdescType` carries a lane layout that signals a
80/// transpose-intent load (lane_layout = `[SG, 1]`). Such descriptors are
81/// rewritten by the transpose peephole optimization and must not be touched
82/// here, since stacking the array blocks along the non-FCD dimension would
83/// invalidate that rewrite.
84static bool hasTransposeLaneLayout(xegpu::TensorDescType tdescType) {
85 auto layout = tdescType.getLayoutAttr();
86 if (!layout)
87 return false;
88 SmallVector<int64_t> laneLayout = layout.getEffectiveLaneLayoutAsInt();
89 if (laneLayout.size() != 2)
90 return false;
91 return laneLayout[0] != 1 && laneLayout[1] == 1;
92}
93
94/// Remaps a 2-D slice from the flattened array representation to the stacked
95/// register representation. Slices within the first array block are unchanged;
96/// later slices must start at a block boundary. Slices crossing a block
97/// boundary and non-2-D descriptors or slices cannot be represented and return
98/// failure.
99static FailureOr<SmallVector<int64_t>>
100getRemappedExtractOffsets(vector::ExtractStridedSliceOp op,
101 xegpu::TensorDescType tdescType) {
102 if (tdescType.getRank() != 2)
103 return failure();
104
105 auto offsets = op.getOffsets().getValue();
106 auto sizes = op.getSizes().getValue();
107 auto strides = op.getStrides().getValue();
108 if (offsets.size() != 2 || sizes.size() != 2 || strides.size() != 2)
109 return failure();
110
111 int64_t origOffset0 = cast<IntegerAttr>(offsets[0]).getInt();
112 int64_t origOffset1 = cast<IntegerAttr>(offsets[1]).getInt();
113 int64_t size1 = cast<IntegerAttr>(sizes[1]).getInt();
114 int64_t blockHeight = tdescType.getShape()[0];
115 int64_t arrayWidth = tdescType.getShape()[1];
116
117 int64_t localOffset1 = origOffset1 % arrayWidth;
118 if (localOffset1 + size1 > arrayWidth)
119 return failure();
120 if (origOffset1 < arrayWidth)
121 return SmallVector<int64_t>{origOffset0, origOffset1};
122 if (origOffset1 % arrayWidth != 0)
123 return failure();
124
125 int64_t arrayIndex = origOffset1 / arrayWidth;
126 return SmallVector<int64_t>{origOffset0 + arrayIndex * blockHeight,
127 /*offset1=*/0};
128}
129
130/// Rewrite `xegpu.create_nd_tdesc` to fold an array_length attribute into the
131/// resulting tensor descriptor type. Supports static memref, dynamic-shape
132/// memref, and raw-pointer (integer) sources — the memory region described by
133/// `shape`/`strides` is unchanged; only the tensor_desc view is narrowed along
134/// the FCD and tagged with `array_length`. Skipped if any consumer load_nd
135/// carries a non-identity transpose, since stacking the array blocks along the
136/// non-FCD dimension would invalidate that load.
137class OptimizeCreateNdDescOp : public OpRewritePattern<xegpu::CreateNdDescOp> {
138public:
139 using OpRewritePattern<xegpu::CreateNdDescOp>::OpRewritePattern;
140
141 LogicalResult matchAndRewrite(xegpu::CreateNdDescOp op,
142 PatternRewriter &rewriter) const override {
143 // sub-byte type is not supported for now.
144 if (op.getType().getElementTypeBitWidth() < 8)
145 return failure();
146 int64_t subgroupSize = getSubgroupSize(op);
147 auto tdescType = op.getType();
148 if (!needsOptimization(tdescType, subgroupSize))
149 return failure();
150
151 // A transpose lane layout marks this descriptor as a candidate for the
152 // separate transpose peephole; stacking the array blocks would break it.
153 if (hasTransposeLaneLayout(tdescType))
154 return failure();
155
156 Value source = op.getSource();
157 if (!isa<MemRefType, IntegerType>(source.getType()))
158 return failure();
159
160 // Bail out if any consumer is a transposing load_nd.
161 for (Operation *user : op.getResult().getUsers()) {
162 if (auto loadOp = dyn_cast<xegpu::LoadNdOp>(user))
163 if (hasNonIdentityTranspose(loadOp))
164 return failure();
165 }
166
167 auto shape = tdescType.getShape();
168 int64_t arrayLength = computeArrayLength(shape[1], subgroupSize);
169 SmallVector<int64_t> newShape = {shape[0], shape[1] / arrayLength};
170 if (auto layout = tdescType.getLayoutAttr();
171 layout && !layout.isDistributable(newShape))
172 return failure();
173
174 auto newTdescType = xegpu::TensorDescType::get(
175 newShape, tdescType.getElementType(), arrayLength,
176 tdescType.getBoundaryCheck(), tdescType.getMemorySpace(),
177 tdescType.getLayout());
178
180 for (Operation *descriptorUser : op.getResult().getUsers()) {
181 if (auto prefetchOp = dyn_cast<xegpu::PrefetchNdOp>(descriptorUser)) {
182 if (auto layout = prefetchOp.getAnchorLayout();
183 layout && !layout.isDistributable(newShape))
184 return failure();
185 continue;
186 }
187
188 auto loadOp = dyn_cast<xegpu::LoadNdOp>(descriptorUser);
189 if (!loadOp)
190 return failure();
191
192 if (auto layout = loadOp.getAnchorLayout();
193 layout && !layout.isDistributable(newShape))
194 return failure();
195 auto loadType = dyn_cast<VectorType>(loadOp.getType());
196 if (!loadType || loadType.getRank() != 2)
197 return failure();
198 for (Operation *loadResultUser : loadOp.getResult().getUsers()) {
199 auto extractOp =
200 dyn_cast<vector::ExtractStridedSliceOp>(loadResultUser);
201 if (!extractOp ||
202 failed(getRemappedExtractOffsets(extractOp, newTdescType)))
203 return failure();
204 }
205 loadOps.push_back(loadOp);
206 }
207
208 // Updating the descriptor alone temporarily invalidates its load users.
209 // Keep the descriptor, load results, and extract offsets consistent within
210 // this single pattern application.
211 for (xegpu::LoadNdOp loadOp : loadOps) {
212 for (Operation *loadResultUser : loadOp.getResult().getUsers()) {
213 auto extractOp = cast<vector::ExtractStridedSliceOp>(loadResultUser);
214 SmallVector<int64_t> newOffsets =
215 *getRemappedExtractOffsets(extractOp, newTdescType);
216 rewriter.modifyOpInPlace(extractOp, [&]() {
217 extractOp.setOffsetsAttr(rewriter.getI64ArrayAttr(newOffsets));
218 });
219 }
220
221 auto loadType = cast<VectorType>(loadOp.getType());
222 SmallVector<int64_t> newLoadShape = {newShape[0] * arrayLength,
223 newShape[1]};
224 auto newLoadType =
225 VectorType::get(newLoadShape, loadType.getElementType());
226 rewriter.modifyOpInPlace(
227 loadOp, [&]() { loadOp.getResult().setType(newLoadType); });
228 }
229 rewriter.modifyOpInPlace(op,
230 [&]() { op.getResult().setType(newTdescType); });
231 return success();
232 }
233};
234
235} // namespace
236
238 RewritePatternSet &patterns) {
239 patterns.add<OptimizeCreateNdDescOp>(patterns.getContext());
240}
return success()
ArrayAttr getI64ArrayAttr(ArrayRef< int64_t > values)
Definition Builders.cpp:290
Operation is the basic unit of execution within MLIR.
Definition Operation.h:87
OpResult getResult(unsigned idx)
Get the 'idx'th result of this operation.
Definition Operation.h:432
A special type of RewriterBase that coordinates the application of a rewrite pattern on the current I...
MLIRContext * getContext() const
RewritePatternSet & add(ConstructorArg &&arg, ConstructorArgs &&...args)
Add an instance of each of the pattern types 'Ts' to the pattern list with the given arguments.
void modifyOpInPlace(Operation *root, CallableT &&callable)
This method is a utility wrapper around an in-place modification of an operation.
This class represents an instance of an SSA value in the MLIR system, representing a computable value...
Definition Value.h:96
Type getType() const
Return the type of this value.
Definition Value.h:105
user_range getUsers() const
Definition Value.h:218
const uArch * getUArch(llvm::StringRef archName)
Definition uArchCommon.h:24
void populateXeGPUArrayLengthOptimizationPatterns(RewritePatternSet &patterns)
Appends patterns for array length optimization into patterns.
std::optional< std::string > getChipStr(Operation *op)
Retrieves the chip string from the XeVM target attribute of the parent GPU module operation.
Include the generated interface declarations.
OpRewritePattern is a wrapper around RewritePattern that allows for matching and rewriting against an...
virtual int getSubgroupSize() const =0