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
237 lines
8.6 KiB
C++
237 lines
8.6 KiB
C++
//===- Simplify.cpp - Shard Simplify ----------------------------*- C++ -*-===//
|
|
//
|
|
// 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/Shard/Transforms/Simplify.h"
|
|
#include "TransformsDetail.h"
|
|
#include "mlir/Dialect/Arith/IR/Arith.h"
|
|
#include "mlir/Dialect/Shard/IR/ShardDialect.h"
|
|
#include "mlir/Dialect/Shard/IR/ShardOps.h"
|
|
#include "mlir/Dialect/Shard/Transforms/Passes.h"
|
|
#include "mlir/IR/BuiltinTypeInterfaces.h"
|
|
#include "mlir/IR/PatternMatch.h"
|
|
#include "mlir/IR/SymbolTable.h"
|
|
#include "mlir/Transforms/GreedyPatternRewriteDriver.h"
|
|
#include "llvm/ADT/STLExtras.h"
|
|
#include "llvm/ADT/SmallVector.h"
|
|
#include <numeric>
|
|
#include <type_traits>
|
|
|
|
namespace mlir {
|
|
namespace shard {
|
|
|
|
#define GEN_PASS_DEF_SHARDSIMPLIFY
|
|
#include "mlir/Dialect/Shard/Transforms/Passes.h.inc"
|
|
|
|
namespace {
|
|
|
|
template <typename LhsOp, typename RhsOp>
|
|
static bool haveSameGridAndGridAxes(LhsOp lhsOp, RhsOp rhsOp) {
|
|
return lhsOp.getGrid() == rhsOp.getGrid() &&
|
|
lhsOp.getGridAxes() == rhsOp.getGridAxes();
|
|
}
|
|
|
|
static bool isAllGatherAllSliceFoldable(AllGatherOp gatherOp,
|
|
AllSliceOp sliceOp) {
|
|
return haveSameGridAndGridAxes(gatherOp, sliceOp) &&
|
|
gatherOp.getGatherAxis() == sliceOp.getSliceAxis();
|
|
}
|
|
|
|
template <typename OuterOp, typename InnerOp>
|
|
static LogicalResult foldAllGatherAllSlice(OuterOp outerOp, InnerOp innerOp,
|
|
PatternRewriter &rewriter) {
|
|
if (!innerOp)
|
|
return failure();
|
|
|
|
AllGatherOp gatherOp;
|
|
AllSliceOp sliceOp;
|
|
if constexpr (std::is_same_v<OuterOp, AllGatherOp>) {
|
|
gatherOp = outerOp;
|
|
sliceOp = innerOp;
|
|
} else {
|
|
gatherOp = innerOp;
|
|
sliceOp = outerOp;
|
|
}
|
|
|
|
if (!isAllGatherAllSliceFoldable(gatherOp, sliceOp))
|
|
return failure();
|
|
|
|
rewriter.replaceOp(outerOp, innerOp.getInput());
|
|
return success();
|
|
}
|
|
|
|
// This folding can not be done with an operation's fold method or
|
|
// DialectFoldInterface, because it needs a SymbolTableCollection to cache the
|
|
// symbol tables.
|
|
// We can't use DialectFoldInterface since the cache may be invalidated by some
|
|
// pass changing the referenced GridOp ops.
|
|
struct GridShapeFolder
|
|
: OpRewritePatternWithSymbolTableCollection<GridShapeOp> {
|
|
using OpRewritePatternWithSymbolTableCollection::
|
|
OpRewritePatternWithSymbolTableCollection;
|
|
LogicalResult matchAndRewrite(GridShapeOp op,
|
|
PatternRewriter &rewriter) const override {
|
|
ImplicitLocOpBuilder builder(op->getLoc(), rewriter);
|
|
GridOp grid = symbolTableCollection.lookupNearestSymbolFrom<shard::GridOp>(
|
|
op.getOperation(), op.getGridAttr());
|
|
if (!grid) {
|
|
return failure();
|
|
}
|
|
ArrayRef<GridAxis> opGridAxes = op.getAxes();
|
|
SmallVector<GridAxis> opAxesIota;
|
|
if (opGridAxes.empty()) {
|
|
opAxesIota.resize(grid.getRank());
|
|
std::iota(opAxesIota.begin(), opAxesIota.end(), 0);
|
|
opGridAxes = opAxesIota;
|
|
}
|
|
if (llvm::all_of(opGridAxes, [&grid](GridAxis axis) {
|
|
return ShapedType::isDynamic(grid.getShape()[axis]);
|
|
})) {
|
|
// All grid dimensions are dynamic. Nothing to fold.
|
|
return failure();
|
|
}
|
|
|
|
SmallVector<Value> newResults(op->getResults().size());
|
|
SmallVector<GridAxis> newShapeOpGridAxes;
|
|
SmallVector<size_t> newToOldResultsIndexMap;
|
|
|
|
for (size_t i = 0; i < opGridAxes.size(); ++i) {
|
|
auto gridAxisSize = grid.getShape()[opGridAxes[i]];
|
|
if (ShapedType::isDynamic(gridAxisSize)) {
|
|
newToOldResultsIndexMap.push_back(i);
|
|
newShapeOpGridAxes.push_back(opGridAxes[i]);
|
|
} else {
|
|
// Fold static grid axes.
|
|
newResults[i] = arith::ConstantOp::create(
|
|
builder, builder.getIndexAttr(gridAxisSize));
|
|
}
|
|
}
|
|
|
|
// Leave only the dynamic grid axes to be queried.
|
|
if (!newShapeOpGridAxes.empty()) {
|
|
GridShapeOp newShapeOp =
|
|
GridShapeOp::create(builder, grid.getSymName(), newShapeOpGridAxes);
|
|
for (size_t i = 0; i < newShapeOp->getResults().size(); ++i) {
|
|
newResults[newToOldResultsIndexMap[i]] = newShapeOp->getResults()[i];
|
|
}
|
|
}
|
|
rewriter.replaceOp(op, newResults);
|
|
|
|
return success();
|
|
}
|
|
};
|
|
|
|
// Simplify AllSliceOp(AllReduceOp) -> ReduceScatterOp when both ops share the
|
|
// same grid and grid_axes.
|
|
//
|
|
// AllReduceOp performs an element-wise reduction across all devices in the
|
|
// group, and AllSliceOp then slices (scatters) the result along a tensor
|
|
// dimension. This is exactly what ReduceScatterOp does in a single collective.
|
|
//
|
|
// With a ring algorithm over N ranks and M elements:
|
|
// AllReduce: 2*(N-1) steps of M/N each => ~2M total data transferred
|
|
// AllSlice: local slice, no communication
|
|
// ReduceScatter: (N-1) steps of M/N each => ~M total data transferred
|
|
// So this fusion roughly halves the communication volume.
|
|
//
|
|
// Memory-wise, AllReduce produces a full-sized M-element result that the
|
|
// subsequent AllSlice must keep alive until the slice is taken. ReduceScatter
|
|
// only materializes the M/N-element local slice, reducing peak memory by
|
|
// a factor of N.
|
|
struct AllReduceAllSliceSimplification : OpRewritePattern<AllSliceOp> {
|
|
using OpRewritePattern::OpRewritePattern;
|
|
|
|
LogicalResult matchAndRewrite(AllSliceOp sliceOp,
|
|
PatternRewriter &rewriter) const override {
|
|
// Check if the input to AllSliceOp is produced by an AllReduceOp.
|
|
auto reduceOp = sliceOp.getInput().getDefiningOp<AllReduceOp>();
|
|
if (!reduceOp || !reduceOp->hasOneUse())
|
|
return failure();
|
|
|
|
// Both ops must operate on the same grid and grid axes.
|
|
if (!haveSameGridAndGridAxes(reduceOp, sliceOp))
|
|
return failure();
|
|
|
|
// Replace with a single ReduceScatterOp.
|
|
rewriter.replaceOpWithNewOp<ReduceScatterOp>(
|
|
sliceOp, sliceOp.getResult().getType(), sliceOp.getGridAttr(),
|
|
sliceOp.getGridAxesAttr(), reduceOp.getInput(),
|
|
reduceOp.getReductionAttr(), sliceOp.getSliceAxisAttr());
|
|
|
|
return success();
|
|
}
|
|
};
|
|
|
|
// Simplify all_slice(all_gather(x)) and all_gather(all_slice(x)) to x when
|
|
// both ops share grid, grid_axes, and axis.
|
|
template <typename OuterOp, typename InnerOp>
|
|
struct AllGatherAllSliceSimplification : OpRewritePattern<OuterOp> {
|
|
using OpRewritePattern<OuterOp>::OpRewritePattern;
|
|
|
|
LogicalResult matchAndRewrite(OuterOp outerOp,
|
|
PatternRewriter &rewriter) const override {
|
|
auto innerOp = outerOp.getInput().template getDefiningOp<InnerOp>();
|
|
return foldAllGatherAllSlice(outerOp, innerOp, rewriter);
|
|
}
|
|
};
|
|
|
|
} // namespace
|
|
|
|
void populateSimplifyPatterns(RewritePatternSet &patterns,
|
|
SymbolTableCollection &symbolTableCollection) {
|
|
populateAllReduceEndomorphismSimplifyPatterns<arith::AddFOp>(
|
|
patterns, ReductionKind::Sum);
|
|
populateAllReduceEndomorphismSimplifyPatterns<arith::AddIOp>(
|
|
patterns, ReductionKind::Sum);
|
|
|
|
populateAllReduceEndomorphismSimplifyPatterns<arith::MinimumFOp>(
|
|
patterns, ReductionKind::Min);
|
|
populateAllReduceEndomorphismSimplifyPatterns<arith::MinSIOp>(
|
|
patterns, ReductionKind::Min);
|
|
populateAllReduceEndomorphismSimplifyPatterns<arith::MinUIOp>(
|
|
patterns, ReductionKind::Min);
|
|
|
|
populateAllReduceEndomorphismSimplifyPatterns<arith::MaximumFOp>(
|
|
patterns, ReductionKind::Max);
|
|
populateAllReduceEndomorphismSimplifyPatterns<arith::MaxSIOp>(
|
|
patterns, ReductionKind::Max);
|
|
populateAllReduceEndomorphismSimplifyPatterns<arith::MaxUIOp>(
|
|
patterns, ReductionKind::Max);
|
|
|
|
patterns.add<AllReduceAllSliceSimplification,
|
|
AllGatherAllSliceSimplification<AllSliceOp, AllGatherOp>,
|
|
AllGatherAllSliceSimplification<AllGatherOp, AllSliceOp>>(
|
|
patterns.getContext());
|
|
|
|
// TODO: add simplify patterns for all-gather and other collectives.
|
|
|
|
populateFoldingPatterns(patterns, symbolTableCollection);
|
|
}
|
|
|
|
void populateFoldingPatterns(RewritePatternSet &patterns,
|
|
SymbolTableCollection &symbolTableCollection) {
|
|
patterns.add<GridShapeFolder>(symbolTableCollection, patterns.getContext());
|
|
}
|
|
|
|
namespace {
|
|
|
|
struct ShardSimplifyPass : public impl::ShardSimplifyBase<ShardSimplifyPass> {
|
|
|
|
void runOnOperation() override {
|
|
RewritePatternSet patterns(&getContext());
|
|
SymbolTableCollection symbolTableCollection;
|
|
populateSimplifyPatterns(patterns, symbolTableCollection);
|
|
if (failed(applyPatternsGreedily(getOperation(), std::move(patterns))))
|
|
signalPassFailure();
|
|
}
|
|
};
|
|
|
|
} // namespace
|
|
|
|
} // namespace shard
|
|
} // namespace mlir
|