Files
TheRock/compiler/amd-llvm/mlir/unittests/Dialect/Linalg/InferConvolutionDimsTest.cpp
T
derek.holloway 7502bf64a4
CodeQL / Analyze (actions) (push) Canceled after 0s
CodeQL / Analyze (python) (push) Canceled after 0s
pre-commit / pre-commit (push) Canceled after 0s
Multi-Arch CI / setup (push) Canceled after 0s
Publish build_manylinux_x86_64 images / publish_build_manylinux_x86_64 (push) Canceled after 0s
Setup (Multi-Arch) / setup (push) Canceled after 0s
Multi-Arch CI / Linux::${{ fromJSON(needs.setup.outputs.linux_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubi10 image / publish_no_rocm_image_ubi10 (push) Canceled after 0s
Multi-Arch CI / Windows::${{ fromJSON(needs.setup.outputs.windows_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Multi-Arch CI / Manifest Diff (push) Canceled after 0s
TheRock Manifest Diff Report / Generate Manifest Diff Report (push) Canceled after 0s
Multi-Arch CI / CI Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_ocl_rt images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_rocgdb images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Unit Tests / Unit Tests :: ubuntu-24.04 (push) Canceled after 0s
Unit Tests / Unit Tests :: windows-2022 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_media (push) Canceled after 0s
Unit Tests / Unit Tests Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_openmpi (push) Canceled after 0s
Flatten subprojects into a single base monorepo
2026-09-02 16:34:47 -07:00

180 lines
7.5 KiB
C++

//===- InferConvolutionDimsTest.cpp - inferConvolutionDims unit tests -----===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#include "mlir/Dialect/Arith/IR/Arith.h"
#include "mlir/Dialect/Func/IR/FuncOps.h"
#include "mlir/Dialect/Linalg/IR/Linalg.h"
#include "mlir/Dialect/Linalg/IR/LinalgInterfaces.h"
#include "mlir/Dialect/Tensor/IR/Tensor.h"
#include "mlir/IR/Builders.h"
#include "mlir/IR/BuiltinOps.h"
#include "mlir/IR/BuiltinTypes.h"
#include "mlir/IR/MLIRContext.h"
#include "mlir/IR/OwningOpRef.h"
#include "gtest/gtest.h"
using namespace mlir;
using namespace mlir::linalg;
namespace {
class InferConvolutionDimsTest : public ::testing::Test {
protected:
void SetUp() override {
registry.insert<arith::ArithDialect, linalg::LinalgDialect,
tensor::TensorDialect, func::FuncDialect>();
ctx = std::make_unique<MLIRContext>(registry);
ctx->loadAllAvailableDialects();
}
DialectRegistry registry;
std::unique_ptr<MLIRContext> ctx;
};
/// Creates a Conv2DOp with loop order (d0, d1, d2, d3) where:
/// d0 = output height (oh), parallel
/// d1 = output width (ow), parallel
/// d2 = kernel height (kh), reduction
/// d3 = kernel width (kw), reduction
///
/// Indexing maps:
/// input: (d0 + d2, d1 + d3)
/// filter: (d2, d3)
/// output: (d0, d1)
///
/// Semantic pairing: d0 <-> d2, d1 <-> d3
static linalg::Conv2DOp createConv2DOp(OpBuilder &builder, int64_t oh,
int64_t ow, int64_t kh, int64_t kw) {
Location loc = builder.getUnknownLoc();
auto f32Type = builder.getF32Type();
int64_t ih = oh + kh - 1;
int64_t iw = ow + kw - 1;
auto inputType = RankedTensorType::get({ih, iw}, f32Type);
auto filterType = RankedTensorType::get({kh, kw}, f32Type);
auto outputType = RankedTensorType::get({oh, ow}, f32Type);
Value input = tensor::EmptyOp::create(builder, loc, inputType.getShape(),
inputType.getElementType());
Value filter = tensor::EmptyOp::create(builder, loc, filterType.getShape(),
filterType.getElementType());
Value output = tensor::EmptyOp::create(builder, loc, outputType.getShape(),
outputType.getElementType());
return linalg::Conv2DOp::create(
builder, loc, outputType, ValueRange{input, filter}, ValueRange{output});
}
/// Creates a linalg.generic equivalent to the given Conv2DOp but with filter
/// loop dimensions swapped. The resulting op has loop order (d0, d1, d2, d3):
/// d0 = output height (oh), parallel
/// d1 = output width (ow), parallel
/// d2 = kernel width (kw), reduction <-- swapped!
/// d3 = kernel height (kh), reduction <-- swapped!
///
/// Indexing maps:
/// input: (d0 + d3, d1 + d2)
/// filter: (d2, d3)
/// output: (d0, d1)
///
/// Semantic pairing: d0 <-> d3, d1 <-> d2
static linalg::GenericOp
createConv2DWithSwappedFilterLoops(OpBuilder &builder,
linalg::Conv2DOp conv2DOp) {
Location loc = conv2DOp.getLoc();
MLIRContext *ctx = builder.getContext();
// Extract dimensions from the Conv2DOp. Require static shapes for simplicity.
auto inputType = cast<RankedTensorType>(conv2DOp.getInputs()[0].getType());
auto filterType = cast<RankedTensorType>(conv2DOp.getInputs()[1].getType());
auto outputType = cast<RankedTensorType>(conv2DOp.getOutputs()[0].getType());
assert(inputType.hasStaticShape() && "expected static input shape");
assert(filterType.hasStaticShape() && "expected static filter shape");
assert(outputType.hasStaticShape() && "expected static output shape");
(void)outputType;
int64_t kh = filterType.getDimSize(0);
int64_t kw = filterType.getDimSize(1);
// Filter dimensions are swapped: (kw, kh) instead of (kh, kw).
auto f32Type = builder.getF32Type();
auto swappedFilterType = RankedTensorType::get({kw, kh}, f32Type);
Value input = tensor::EmptyOp::create(builder, loc, inputType.getShape(),
inputType.getElementType());
Value filter =
tensor::EmptyOp::create(builder, loc, swappedFilterType.getShape(),
swappedFilterType.getElementType());
Value output = tensor::EmptyOp::create(builder, loc, outputType.getShape(),
outputType.getElementType());
// Build indexing maps for swapped filter loop dimensions.
// Original Conv2DOp: (d0=oh, d1=ow, d2=kh, d3=kw)
// Swapped: (d0=oh, d1=ow, d2=kw, d3=kh)
AffineExpr d0, d1, d2, d3;
bindDims(ctx, d0, d1, d2, d3);
auto inputMap = AffineMap::get(4, 0, {d0 + d3, d1 + d2}, ctx);
auto filterMap = AffineMap::get(4, 0, {d2, d3}, ctx);
auto outputMap = AffineMap::get(4, 0, {d0, d1}, ctx);
SmallVector<AffineMap> indexingMaps = {inputMap, filterMap, outputMap};
SmallVector<utils::IteratorType> iterTypes = {
utils::IteratorType::parallel, utils::IteratorType::parallel,
utils::IteratorType::reduction, utils::IteratorType::reduction};
return linalg::GenericOp::create(
builder, loc, outputType, ValueRange{input, filter}, ValueRange{output},
indexingMaps, iterTypes,
[&](OpBuilder &b, Location loc, ValueRange args) {
Value mul = arith::MulFOp::create(b, loc, args[0], args[1]);
Value add = arith::AddFOp::create(b, loc, args[2], mul);
linalg::YieldOp::create(b, loc, add);
});
}
TEST_F(InferConvolutionDimsTest, Conv2DPairing) {
// Use non-square kernel to ensure dimension swapping is tested properly.
const int64_t oh = 6, ow = 12, kh = 3, kw = 5;
// Create a module to own all test operations and ensure proper cleanup.
OpBuilder builder(ctx.get());
OwningOpRef<ModuleOp> module = ModuleOp::create(builder.getUnknownLoc());
builder.setInsertionPointToStart(module->getBody());
// Create Conv2DOp where the standard loop order is (oh, ow, kh, kw).
linalg::Conv2DOp conv2DOp = createConv2DOp(builder, oh, ow, kh, kw);
FailureOr<ConvolutionDimensions> origDims = inferConvolutionDims(conv2DOp);
ASSERT_TRUE(succeeded(origDims));
ASSERT_EQ(origDims->outputImage.size(), 2u);
ASSERT_EQ(origDims->filterLoop.size(), 2u);
// Standard pairing: outputImage=[0,1], filterLoop=[2,3]
// d0 <-> d2 (oh <-> kh), d1 <-> d3 (ow <-> kw)
EXPECT_EQ(origDims->outputImage[0], 0u);
EXPECT_EQ(origDims->outputImage[1], 1u);
EXPECT_EQ(origDims->filterLoop[0], 2u);
EXPECT_EQ(origDims->filterLoop[1], 3u);
// Create equivalent generic with swapped filter loop order: (oh, ow, kw, kh)
linalg::GenericOp swappedOp =
createConv2DWithSwappedFilterLoops(builder, conv2DOp);
FailureOr<ConvolutionDimensions> swappedDims =
inferConvolutionDims(swappedOp);
ASSERT_TRUE(succeeded(swappedDims));
ASSERT_EQ(swappedDims->outputImage.size(), 2u);
ASSERT_EQ(swappedDims->filterLoop.size(), 2u);
// outputImage should still be [0, 1] after sorting.
EXPECT_EQ(swappedDims->outputImage[0], 0u);
EXPECT_EQ(swappedDims->outputImage[1], 1u);
// In swapped version:
// Input map: (d0 + d3, d1 + d2) -> d0 <-> d3, d1 <-> d2
// So filterLoop should be [3, 2] to maintain
// outputImage[i] <-> filterLoop[i].
EXPECT_EQ(swappedDims->filterLoop[0], 3u)
<< "outputImage[0]=0 should pair with filterLoop[0]=3 (oh <-> kh)";
EXPECT_EQ(swappedDims->filterLoop[1], 2u)
<< "outputImage[1]=1 should pair with filterLoop[1]=2 (ow <-> kw)";
}
} // namespace