Skip to content

[Codegen] Support more operations in tile size analysis#23971

Open
sommerlukas wants to merge 3 commits intoiree-org:mainfrom
sommerlukas:pack-unpack-tile-size-analysis
Open

[Codegen] Support more operations in tile size analysis#23971
sommerlukas wants to merge 3 commits intoiree-org:mainfrom
sommerlukas:pack-unpack-tile-size-analysis

Conversation

@sommerlukas
Copy link
Copy Markdown
Contributor

Add support for inner_tiled, linalg.pack and linalg.unpack operations to the vector tile size analysis.

This is part of #23415.

Assisted-by: Claude Code

Signed-off-by: Lukas Sommer <lukas.sommer@amd.com>
Signed-off-by: Lukas Sommer <lukas.sommer@amd.com>
@sommerlukas sommerlukas requested a review from Groverkss March 31, 2026 12:06
@sommerlukas sommerlukas changed the title [Codegen] Support more operation in tile size analysis [Codegen] Support more operations in tile size analysis Mar 31, 2026
Copy link
Copy Markdown
Contributor

@hanhanW hanhanW left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel that dynamic cases are not exercised enough, because I'm seeing weird assumption in the implementation. E.g., the inlined comment in mapPackSourceToDest.

Not saying dynamic inner tile sizes, the inner tile sizes are static in the scope. The outer dimension can be dynamic and the op can carry padding value.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should have tests with dynamic shapes.

Comment on lines +182 to +184
if (!outerDimsPerm.empty()) {
applyPermutationToVector(result.dims, outerDimsPerm);
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need a test for outer_dims_perm. Please use a permutation that is not identical to the inversed permutation. e.g., do not use [1, 0].

Comment on lines +185 to +187
for (int64_t t : staticInnerTiles) {
result.dims.push_back(t);
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

result.dims.append(staticInnerTiles)?

Comment on lines +171 to +181
for (auto [dimPos, tileSize] :
llvm::zip_equal(innerDimsPos, staticInnerTiles)) {
if (result.dims[dimPos] == kUninitialized ||
result.dims[dimPos] == kOverdefined) {
return {};
}
if (result.dims[dimPos] % tileSize != 0) {
return {};
}
result.dims[dimPos] /= tileSize;
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Q1: Don't we need to check if tileSize is static or not?
Q2: don't we preserve kUninitialized and kOverdefined when it happens? Why do you return {}?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Q1: This is already checked at the call-sites.
Q2: Changed to preserve.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

RE Q1: If we look the code from class's perspective, it is better to document the assumption in method comment, or we should just handle it here.

Comment on lines +191 to +196
/// Append dimensions from `suffix` to produce a higher-rank TileSizes.
TileSizes extend(ArrayRef<int64_t> suffix) const {
SmallVector<int64_t> fullDims(dims);
fullDims.append(suffix.begin(), suffix.end());
return TileSizes(fullDims);
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about naming it as append which matches the comment and methods from SmallVector?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

linalgOp.emitOpError()
<< "tile size analysis did not determine a valid tile size";
auto materialize =
[&](Operation *op, TileSizes tileSizes) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you need to capture anything?

Suggested change
[&](Operation *op, TileSizes tileSizes) {
[](Operation *op, TileSizes tileSizes) {

Comment on lines +591 to 593
static TileSizes getIterationSpaceTileSizes(Operation *op, unsigned numLoops,
ArrayRef<AffineMap> indexingMaps,
const DataFlowSolver &solver) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel that you want to pass in TilingInterface op which has the additional information.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IIUC, TilingInterface doesn't give us access to the indexing maps, so we won't gain much here.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, I missed that. I was going to suggest IndexingMapOpInterface, then I found that InnerTileOp haven't implemented the interface, so it is okay for now.

Comment on lines +649 to 667
if (auto linalgOp = dyn_cast<linalg::LinalgOp>(op)) {
SmallVector<AffineMap> indexingMaps = linalgOp.getIndexingMapsArray();
TileSizes tileSizes = getIterationSpaceTileSizes(
op, linalgOp.getNumLoops(), indexingMaps, solver);
assert(!tileSizes.isDefined() ||
tileSizes.rank() == linalgOp.getNumLoops());
materialize(op, tileSizes);
return;
}
if (!tileSizes.isDefined()) {
LDBG() << "Analysis did not determine tile size for " << *linalgOp;

if (auto innerTiledOp = dyn_cast<IREE::Codegen::InnerTiledOp>(op)) {
SmallVector<AffineMap> indexingMaps =
innerTiledOp.getIndexingMapsArray();
unsigned numLoops = indexingMaps[0].getNumDims();
TileSizes tileSizes =
getIterationSpaceTileSizes(op, numLoops, indexingMaps, solver);
materialize(op, tileSizes);
return;
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Like the other comment, I feel that we should structure these like:

// Special cases like pack/unpack
// TilingInterface op. Or isa<linalg::LinalgOp, InnerTiledOp>

Comment on lines +635 to +637
op->emitOpError()
<< "tile size analysis did not determine a valid tile size";
return;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I missed this when you landed the previous PR. I was not clear enough. I think the pass should signal failure if it is overdefined. Emitting error messages is bundled with pass failure to me, and I did not explicitly spell it out in my other comment.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added proper pass failure.

Comment on lines +177 to +180
if (result.dims[dimPos] % tileSize != 0) {
return {};
}
result.dims[dimPos] /= tileSize;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought that it is always ceilDiv in this case, because pack have padding semantics?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've switched to using ceil division, but in the same course also deactivated backward propagation for pack with padding.

That propagation would in the padding case would otherwise propagate too large tile sizes (or lead to overdefined).

Signed-off-by: Lukas Sommer <lukas.sommer@amd.com>
Copy link
Copy Markdown
Contributor Author

@sommerlukas sommerlukas left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the feedback! I've added tests for dynamic shapes and outer_dims_perm, some more comments inline.

Comment on lines +177 to +180
if (result.dims[dimPos] % tileSize != 0) {
return {};
}
result.dims[dimPos] /= tileSize;
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've switched to using ceil division, but in the same course also deactivated backward propagation for pack with padding.

That propagation would in the padding case would otherwise propagate too large tile sizes (or lead to overdefined).

return success();
}

// InnerTiledOp: propagate through indexing maps (outer dims only).
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment isn't phrased very well. We don't exclude the inner dims (we append them before propagating, see below), but they don't participate in the propagation through indexing maps (because they don't need to). I've clarified the comment. We also don't materialize the inner dims in the attribute on inner_tiled, because it would duplicate the information.

Comment on lines +221 to +224
// Uninitialized (0) stays 0 after multiply; overdefined is preserved
// by skipping. Safe because we only multiply here, unlike
// mapPackSourceToDest which must divide (and can't divide by zero).
continue;
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Clarified.

Comment on lines +171 to +181
for (auto [dimPos, tileSize] :
llvm::zip_equal(innerDimsPos, staticInnerTiles)) {
if (result.dims[dimPos] == kUninitialized ||
result.dims[dimPos] == kOverdefined) {
return {};
}
if (result.dims[dimPos] % tileSize != 0) {
return {};
}
result.dims[dimPos] /= tileSize;
}
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Q1: This is already checked at the call-sites.
Q2: Changed to preserve.

Comment on lines +191 to +196
/// Append dimensions from `suffix` to produce a higher-rank TileSizes.
TileSizes extend(ArrayRef<int64_t> suffix) const {
SmallVector<int64_t> fullDims(dims);
fullDims.append(suffix.begin(), suffix.end());
return TileSizes(fullDims);
}
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

Comment on lines +635 to +637
op->emitOpError()
<< "tile size analysis did not determine a valid tile size";
return;
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added proper pass failure.

Comment on lines +591 to 593
static TileSizes getIterationSpaceTileSizes(Operation *op, unsigned numLoops,
ArrayRef<AffineMap> indexingMaps,
const DataFlowSolver &solver) {
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IIUC, TilingInterface doesn't give us access to the indexing maps, so we won't gain much here.

Copy link
Copy Markdown
Contributor

@keshavvinayak01 keshavvinayak01 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LG

@sommerlukas sommerlukas requested a review from hanhanW April 2, 2026 23:24
Comment on lines +171 to +181
for (auto [dimPos, tileSize] :
llvm::zip_equal(innerDimsPos, staticInnerTiles)) {
if (result.dims[dimPos] == kUninitialized ||
result.dims[dimPos] == kOverdefined) {
return {};
}
if (result.dims[dimPos] % tileSize != 0) {
return {};
}
result.dims[dimPos] /= tileSize;
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

RE Q1: If we look the code from class's perspective, it is better to document the assumption in method comment, or we should just handle it here.

return success();
}

// InnerTiledOp: propagate through indexing maps (outer dims only).
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've clarified the comment.

I don't see the update on the comment. Do you miss the change?

if (ShapedType::isDynamicShape(packOp.getStaticInnerTiles())) {
return success();
}
if (packOp.getPaddingValue()) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we have a test for padding value?

Comment on lines +591 to 593
static TileSizes getIterationSpaceTileSizes(Operation *op, unsigned numLoops,
ArrayRef<AffineMap> indexingMaps,
const DataFlowSolver &solver) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, I missed that. I was going to suggest IndexingMapOpInterface, then I found that InnerTileOp haven't implemented the interface, so it is okay for now.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants