refactorone
Validate Operations / validate-operations (push) Has been cancelled

This commit is contained in:
NiccoloN
2026-05-20 19:06:41 +02:00
parent f56c4159b5
commit a50e77ff38
50 changed files with 3420 additions and 1187 deletions
File diff suppressed because it is too large Load Diff
@@ -167,21 +167,20 @@ bool isTrivialSerialMergeCandidate(SpatCompute compute) {
return user && user.getInputs().size() == 1 && use.getOperandNumber() >= user.getWeights().size();
}
SmallVector<size_t> appendMissingWeightsAndBuildIndexMap(SpatCompute target, ValueRange sourceWeights) {
SmallVector<size_t> appendMissingWeightsAndBuildIndexMap(SmallVectorImpl<Value>& targetWeights, ValueRange sourceWeights) {
DenseMap<Value, SmallVector<size_t, 4>> targetWeightIndices;
for (auto [weightIndex, weight] : llvm::enumerate(target.getWeights()))
for (auto [weightIndex, weight] : llvm::enumerate(targetWeights))
targetWeightIndices[weight].push_back(weightIndex);
DenseMap<Value, size_t> usedSourceWeightOccurrences;
SmallVector<size_t> sourceToTargetIndex;
sourceToTargetIndex.reserve(sourceWeights.size());
auto targetWeights = target.getWeightsMutable();
for (Value weight : sourceWeights) {
size_t occurrence = usedSourceWeightOccurrences[weight]++;
auto& matchingIndices = targetWeightIndices[weight];
if (occurrence >= matchingIndices.size()) {
size_t newIndex = target.getWeights().size();
targetWeights.append(weight);
size_t newIndex = targetWeights.size();
targetWeights.push_back(weight);
matchingIndices.push_back(newIndex);
sourceToTargetIndex.push_back(newIndex);
continue;
@@ -213,37 +212,36 @@ void mergeTriviallyConnectedComputes(func::FuncOp funcOp) {
auto& computeUse = *compute->getUses().begin();
auto child = cast<SpatCompute>(computeUse.getOwner());
auto usedResult = cast<OpResult>(computeUse.get()).getResultNumber();
auto childArgIndex = computeUse.getOperandNumber() - child.getWeights().size();
auto childInputIndex = computeUse.getOperandNumber() - child.getWeights().size();
rewriter.setInsertionPointAfter(compute.getOperation());
auto newCompute = SpatCompute::create(rewriter, loc, child.getResultTypes(), compute.getOperands());
newCompute.getProperties().setOperandSegmentSizes(
{static_cast<int>(compute.getWeights().size()), static_cast<int>(compute.getInputs().size())});
SmallVector<Value> mergedWeights(compute.getWeights().begin(), compute.getWeights().end());
SmallVector<size_t> childWeightToNewIndex = appendMissingWeightsAndBuildIndexMap(mergedWeights, child.getWeights());
SmallVector<Value> mergedInputs(compute.getInputs().begin(), compute.getInputs().end());
auto newCompute = SpatCompute::create(rewriter, loc, child.getResultTypes(), mergedWeights, mergedInputs);
Block* newBody = rewriter.createBlock(&newCompute.getBodyRegion());
for (Value weight : mergedWeights)
newBody->addArgument(weight.getType(), loc);
for (Value input : mergedInputs)
newBody->addArgument(input.getType(), loc);
IRMapping mapper;
SmallVector<size_t> childWeightToNewIndex = appendMissingWeightsAndBuildIndexMap(newCompute, child.getWeights());
for (auto [weightIndex, _] : llvm::enumerate(compute.getWeights()))
mapper.map(compute.getWeightArgument(weightIndex), newCompute.getWeightArgument(weightIndex));
for (auto [inputIndex, _] : llvm::enumerate(compute.getInputs()))
mapper.map(compute.getInputArgument(inputIndex), newCompute.getInputArgument(inputIndex));
for (auto [oldIndex, weight] : llvm::enumerate(child.getWeights()))
mapper.map(weight, *std::next(newCompute.getWeights().begin(), childWeightToNewIndex[oldIndex]));
mapper.map(child.getWeightArgument(oldIndex), newCompute.getWeightArgument(childWeightToNewIndex[oldIndex]));
compute.getBodyRegion().cloneInto(&newCompute.getBodyRegion(), mapper);
auto newTerminator = newCompute.getBody().front().getTerminator();
mapper.map(child.getBody().front().getArgument(childArgIndex), newTerminator->getOperand(usedResult));
newTerminator->erase();
rewriter.setInsertionPointToEnd(newBody);
auto computeYield = cast<spatial::SpatYieldOp>(compute.getBody().front().getTerminator());
for (Operation& op : compute.getBody().front().without_terminator())
rewriter.clone(op, mapper);
mapper.map(child.getInputArgument(childInputIndex), mapper.lookupOrDefault(computeYield.getOperand(usedResult)));
rewriter.setInsertionPoint(&newCompute.getBody().front(), newCompute.getBody().front().end());
auto remapWeightIndex = [&](auto weightedOp) {
auto oldIndex = weightedOp.getWeightIndex();
assert(static_cast<size_t>(oldIndex) < childWeightToNewIndex.size() && "weight index out of range");
weightedOp.setWeightIndex(childWeightToNewIndex[oldIndex]);
};
for (auto& op : child.getBody().front()) {
auto newInst = rewriter.clone(op, mapper);
if (auto weightedMvmOp = dyn_cast<spatial::SpatMVMOp>(newInst))
remapWeightIndex(weightedMvmOp);
if (auto weightedVmmOp = dyn_cast<spatial::SpatVMMOp>(newInst))
remapWeightIndex(weightedVmmOp);
}
rewriter.setInsertionPointToEnd(newBody);
for (auto& op : child.getBody().front())
rewriter.clone(op, mapper);
child.replaceAllUsesWith(newCompute);
toErase.insert(child);
@@ -2,6 +2,7 @@
#include "mlir/Dialect/Func/IR/FuncOps.h"
#include "mlir/Dialect/Tensor/IR/Tensor.h"
#include "mlir/IR/IRMapping.h"
#include "mlir/IR/Matchers.h"
#include "mlir/IR/PatternMatch.h"
#include "llvm/ADT/DenseMap.h"
@@ -61,6 +62,66 @@ std::optional<int32_t> getComputeCoreId(SpatCompute compute) {
static constexpr StringLiteral kRebatchPhaseAttrName = "_pim_rebatch_phase";
static FailureOr<int64_t> getConstantI64Value(Value value) {
APInt constantValue;
if (!matchPattern(value, m_ConstantInt(&constantValue)))
return failure();
return constantValue.getSExtValue();
}
static FailureOr<int32_t> getConstantI32Value(Value value) {
APInt constantValue;
if (!matchPattern(value, m_ConstantInt(&constantValue)))
return failure();
return static_cast<int32_t>(constantValue.getSExtValue());
}
static bool getScalarChannelMetadata(spatial::SpatChannelSendOp op,
uint64_t& channelId,
uint32_t& sourceCoreId,
uint32_t& targetCoreId) {
FailureOr<int64_t> constantChannelId = getConstantI64Value(op.getChannelId());
FailureOr<int32_t> constantSourceCoreId = getConstantI32Value(op.getSourceCoreId());
FailureOr<int32_t> constantTargetCoreId = getConstantI32Value(op.getTargetCoreId());
if (failed(constantChannelId) || failed(constantSourceCoreId) || failed(constantTargetCoreId))
return false;
channelId = static_cast<uint64_t>(*constantChannelId);
sourceCoreId = static_cast<uint32_t>(*constantSourceCoreId);
targetCoreId = static_cast<uint32_t>(*constantTargetCoreId);
return true;
}
static bool getScalarChannelMetadata(spatial::SpatChannelReceiveOp op,
uint64_t& channelId,
uint32_t& sourceCoreId,
uint32_t& targetCoreId) {
FailureOr<int64_t> constantChannelId = getConstantI64Value(op.getChannelId());
FailureOr<int32_t> constantSourceCoreId = getConstantI32Value(op.getSourceCoreId());
FailureOr<int32_t> constantTargetCoreId = getConstantI32Value(op.getTargetCoreId());
if (failed(constantChannelId) || failed(constantSourceCoreId) || failed(constantTargetCoreId))
return false;
channelId = static_cast<uint64_t>(*constantChannelId);
sourceCoreId = static_cast<uint32_t>(*constantSourceCoreId);
targetCoreId = static_cast<uint32_t>(*constantTargetCoreId);
return true;
}
static SmallVector<Value> createIndexConstants(Operation* anchorOp, ArrayRef<int64_t> values, OperationFolder& folder) {
SmallVector<Value> constants;
constants.reserve(values.size());
for (int64_t value : values)
constants.push_back(getOrCreateHostIndexConstant(anchorOp, value, folder));
return constants;
}
static SmallVector<Value> createIndexConstants(Operation* anchorOp, ArrayRef<int32_t> values, OperationFolder& folder) {
SmallVector<Value> constants;
constants.reserve(values.size());
for (int32_t value : values)
constants.push_back(getOrCreateHostIndexConstant(anchorOp, value, folder));
return constants;
}
std::optional<uint64_t> getComputeRebatchPhase(SpatCompute compute) {
if (auto phaseAttr = compute->getAttrOfType<IntegerAttr>(kRebatchPhaseAttrName))
return static_cast<uint64_t>(phaseAttr.getInt());
@@ -206,8 +267,215 @@ bool areEquivalentForRebatch(SpatCompute lhs, SpatCompute rhs) {
return lhsIt == lhsBlock.end() && rhsIt == rhsBlock.end();
}
struct BatchYieldInfo {
Value yieldedValue;
tensor::ParallelInsertSliceOp insertSlice;
};
static bool isHostOnlyBatchResultUser(Operation* user) {
return isa<func::ReturnOp,
spatial::SpatConcatOp,
tensor::ExtractSliceOp,
tensor::CastOp,
tensor::CollapseShapeOp,
tensor::ExpandShapeOp>(user);
}
static FailureOr<DenseMap<BlockArgument, BatchYieldInfo>> collectBatchYieldInfo(SpatComputeBatch batchOp) {
Block& block = batchOp.getBody().front();
auto inParallel = dyn_cast<spatial::SpatInParallelOp>(block.getTerminator());
if (!inParallel)
return failure();
DenseMap<BlockArgument, BatchYieldInfo> batchYieldByOutputArg;
for (Operation& op : inParallel.getRegion().front()) {
auto insertSlice = dyn_cast<tensor::ParallelInsertSliceOp>(&op);
if (!insertSlice)
return failure();
auto outputArg = dyn_cast<BlockArgument>(insertSlice.getDest());
if (!outputArg || outputArg.getOwner() != &block)
return failure();
batchYieldByOutputArg[outputArg] = {insertSlice.getSource(), insertSlice};
}
return batchYieldByOutputArg;
}
static FailureOr<SpatComputeBatch> cloneBatchAsResultless(SpatComputeBatch batchOp, IRRewriter& rewriter) {
auto coreIdsAttr = batchOp->getAttrOfType<DenseI32ArrayAttr>(onnx_mlir::kCoreIdsAttrName);
if (!coreIdsAttr)
return failure();
Block& oldBlock = batchOp.getBody().front();
rewriter.setInsertionPoint(batchOp);
auto newBatch = SpatComputeBatch::create(rewriter,
batchOp.getLoc(),
TypeRange {},
rewriter.getI32IntegerAttr(batchOp.getLaneCount()),
batchOp.getWeights(),
batchOp.getInputs());
newBatch.getProperties().setOperandSegmentSizes(
{static_cast<int>(batchOp.getWeights().size()), static_cast<int>(batchOp.getInputs().size())});
newBatch->setAttr(onnx_mlir::kCoreIdsAttrName, coreIdsAttr);
SmallVector<Type> blockArgTypes;
SmallVector<Location> blockArgLocs;
blockArgTypes.reserve(1 + batchOp.getWeights().size() + batchOp.getInputs().size());
blockArgLocs.reserve(1 + batchOp.getWeights().size() + batchOp.getInputs().size());
blockArgTypes.push_back(batchOp.getLaneArgument().getType());
blockArgLocs.push_back(batchOp.getLaneArgument().getLoc());
for (unsigned weightIndex = 0; weightIndex < batchOp.getWeights().size(); ++weightIndex) {
blockArgTypes.push_back(batchOp.getWeightArgument(weightIndex).getType());
blockArgLocs.push_back(batchOp.getWeightArgument(weightIndex).getLoc());
}
for (unsigned inputIndex = 0; inputIndex < batchOp.getInputs().size(); ++inputIndex) {
blockArgTypes.push_back(batchOp.getInputArgument(inputIndex).getType());
blockArgLocs.push_back(batchOp.getInputArgument(inputIndex).getLoc());
}
Block* newBlock =
rewriter.createBlock(&newBatch.getBody(), newBatch.getBody().end(), TypeRange(blockArgTypes), blockArgLocs);
rewriter.setInsertionPointToStart(newBlock);
IRMapping mapper;
mapper.map(batchOp.getLaneArgument(), newBatch.getLaneArgument());
for (unsigned weightIndex = 0; weightIndex < batchOp.getWeights().size(); ++weightIndex)
mapper.map(batchOp.getWeightArgument(weightIndex), newBatch.getWeightArgument(weightIndex));
for (unsigned inputIndex = 0; inputIndex < batchOp.getInputs().size(); ++inputIndex)
mapper.map(batchOp.getInputArgument(inputIndex), newBatch.getInputArgument(inputIndex));
for (Operation& op : oldBlock.without_terminator()) {
Operation* cloned = rewriter.clone(op, mapper);
for (auto [oldResult, newResult] : llvm::zip(op.getResults(), cloned->getResults()))
mapper.map(oldResult, newResult);
}
return newBatch;
}
static LogicalResult materializeBatchResultCommunication(func::FuncOp funcOp, int64_t& nextChannelId) {
IRRewriter rewriter(funcOp.getContext());
OperationFolder constantFolder(funcOp.getContext());
SmallVector<SpatComputeBatch> batches(funcOp.getOps<SpatComputeBatch>());
for (auto batchOp : batches) {
if (batchOp.getNumResults() == 0)
continue;
auto coreIdsAttr = batchOp->getAttrOfType<DenseI32ArrayAttr>(onnx_mlir::kCoreIdsAttrName);
if (!coreIdsAttr)
return batchOp.emitOpError("missing coreIds while materializing batch result communication");
FailureOr<DenseMap<BlockArgument, BatchYieldInfo>> batchYieldInfo = collectBatchYieldInfo(batchOp);
if (failed(batchYieldInfo))
return batchOp.emitOpError("failed to collect per-result yielded values from compute_batch body");
FailureOr<SpatComputeBatch> newBatch = cloneBatchAsResultless(batchOp, rewriter);
if (failed(newBatch))
return batchOp.emitOpError("failed to clone resultful compute_batch as resultless");
Block& oldBlock = batchOp.getBody().front();
Block& newBlock = newBatch->getBody().front();
IRMapping mapper;
mapper.map(batchOp.getLaneArgument(), newBatch->getLaneArgument());
for (unsigned weightIndex = 0; weightIndex < batchOp.getWeights().size(); ++weightIndex)
mapper.map(batchOp.getWeightArgument(weightIndex), newBatch->getWeightArgument(weightIndex));
for (unsigned inputIndex = 0; inputIndex < batchOp.getInputs().size(); ++inputIndex)
mapper.map(batchOp.getInputArgument(inputIndex), newBatch->getInputArgument(inputIndex));
auto oldIt = oldBlock.begin();
auto newIt = newBlock.begin();
for (; oldIt != oldBlock.end() && newIt != newBlock.end(); ++oldIt, ++newIt)
for (auto [oldResult, newResult] : llvm::zip(oldIt->getResults(), newIt->getResults()))
mapper.map(oldResult, newResult);
SmallVector<int32_t> sourceCoreIds(coreIdsAttr.asArrayRef().begin(), coreIdsAttr.asArrayRef().end());
rewriter.setInsertionPointToEnd(&newBlock);
for (unsigned resultIndex = 0; resultIndex < batchOp.getNumResults(); ++resultIndex) {
BlockArgument outputArg = batchOp.getOutputArgument(resultIndex);
auto yieldInfoIt = batchYieldInfo->find(outputArg);
if (yieldInfoIt == batchYieldInfo->end())
return batchOp.emitOpError(
"missing yielded value for compute_batch result during communication materialization");
Value mappedYieldedValue = mapper.lookup(yieldInfoIt->second.yieldedValue);
DenseMap<int32_t, SmallVector<OpOperand*>> computeUsesByTargetCore;
SmallVector<OpOperand*> hostUses;
for (OpOperand& use : batchOp.getResult(resultIndex).getUses()) {
if (auto computeOp = dyn_cast<SpatCompute>(use.getOwner())) {
auto coreIdAttr = computeOp->getAttrOfType<IntegerAttr>(onnx_mlir::kCoreIdAttrName);
if (!coreIdAttr)
return batchOp.emitOpError("compute user of compute_batch result is missing coreId");
computeUsesByTargetCore[static_cast<int32_t>(coreIdAttr.getInt())].push_back(&use);
continue;
}
if (isHostOnlyBatchResultUser(use.getOwner())) {
hostUses.push_back(&use);
continue;
}
return batchOp.emitOpError("unsupported user of compute_batch result during communication materialization")
<< ": " << use.getOwner()->getName();
}
auto createReceiveForUses = [&](ArrayRef<OpOperand*> uses, ArrayRef<int32_t> targetCoreIds) -> LogicalResult {
if (uses.empty())
return success();
SmallVector<int64_t> channelIds;
channelIds.reserve(sourceCoreIds.size());
for ([[maybe_unused]] int32_t sourceCoreId : sourceCoreIds)
channelIds.push_back(nextChannelId++);
SmallVector<Value> sendChannelIdValues = createIndexConstants(batchOp, channelIds, constantFolder);
SmallVector<Value> sendSourceCoreIdValues = createIndexConstants(batchOp, sourceCoreIds, constantFolder);
SmallVector<Value> sendTargetCoreIdValues = createIndexConstants(batchOp, targetCoreIds, constantFolder);
spatial::SpatChannelSendBatchOp::create(rewriter,
batchOp.getLoc(),
sendChannelIdValues,
sendSourceCoreIdValues,
sendTargetCoreIdValues,
mappedYieldedValue);
OpBuilder::InsertionGuard guard(rewriter);
rewriter.setInsertionPointAfter(newBatch->getOperation());
SmallVector<Value> receiveChannelIdValues = createIndexConstants(batchOp, channelIds, constantFolder);
SmallVector<Value> receiveSourceCoreIdValues = createIndexConstants(batchOp, sourceCoreIds, constantFolder);
SmallVector<Value> receiveTargetCoreIdValues = createIndexConstants(batchOp, targetCoreIds, constantFolder);
auto received = spatial::SpatChannelReceiveTensorOp::create(rewriter,
batchOp.getLoc(),
batchOp.getResult(resultIndex).getType(),
receiveChannelIdValues,
receiveSourceCoreIdValues,
receiveTargetCoreIdValues);
for (OpOperand* use : uses)
use->set(received.getOutput());
rewriter.setInsertionPointToEnd(&newBlock);
return success();
};
for (auto& [targetCoreId, uses] : computeUsesByTargetCore) {
SmallVector<int32_t> targetCoreIds(static_cast<size_t>(batchOp.getLaneCount()), targetCoreId);
if (failed(createReceiveForUses(uses, targetCoreIds)))
return failure();
}
if (!hostUses.empty()) {
SmallVector<int32_t> hostTargetCoreIds(static_cast<size_t>(batchOp.getLaneCount()), 0);
if (failed(createReceiveForUses(hostUses, hostTargetCoreIds)))
return failure();
}
}
rewriter.setInsertionPointToEnd(&newBlock);
spatial::SpatYieldOp::create(rewriter, batchOp.getLoc(), ValueRange {});
rewriter.eraseOp(batchOp);
}
return success();
}
void rebatchEquivalentComputes(func::FuncOp funcOp) {
IRRewriter rewriter(funcOp.getContext());
OperationFolder constantFolder(funcOp.getContext());
SmallVector<SpatCompute> computes(funcOp.getOps<SpatCompute>());
DenseSet<Operation*> consumed;
DenseMap<Operation*, size_t> computeOrder;
@@ -316,8 +584,10 @@ void rebatchEquivalentComputes(func::FuncOp funcOp) {
entries.reserve(group.size());
for (auto [groupIndex, compute] : llvm::enumerate(group)) {
auto groupReceive = cast<spatial::SpatChannelReceiveOp>(&*opIts[groupIndex]);
entries.push_back(
{groupReceive.getChannelId(), groupReceive.getSourceCoreId(), groupReceive.getTargetCoreId()});
BatchReceiveEntry entry;
if (!getScalarChannelMetadata(groupReceive, entry.channelId, entry.sourceCoreId, entry.targetCoreId))
return;
entries.push_back(entry);
++opIts[groupIndex];
}
SmallVector<int64_t> channelIds;
@@ -331,12 +601,15 @@ void rebatchEquivalentComputes(func::FuncOp funcOp) {
sourceCoreIds.push_back(static_cast<int32_t>(entry.sourceCoreId));
targetCoreIds.push_back(static_cast<int32_t>(entry.targetCoreId));
}
SmallVector<Value> channelIdValues = createIndexConstants(receiveOp, channelIds, constantFolder);
SmallVector<Value> sourceCoreIdValues = createIndexConstants(receiveOp, sourceCoreIds, constantFolder);
SmallVector<Value> targetCoreIdValues = createIndexConstants(receiveOp, targetCoreIds, constantFolder);
auto batchReceive = spatial::SpatChannelReceiveBatchOp::create(rewriter,
receiveOp.getLoc(),
receiveOp.getOutput().getType(),
rewriter.getDenseI64ArrayAttr(channelIds),
rewriter.getDenseI32ArrayAttr(sourceCoreIds),
rewriter.getDenseI32ArrayAttr(targetCoreIds));
channelIdValues,
sourceCoreIdValues,
targetCoreIdValues);
mapper.map(receiveOp.getOutput(), batchReceive.getOutput());
continue;
}
@@ -351,7 +624,10 @@ void rebatchEquivalentComputes(func::FuncOp funcOp) {
entries.reserve(group.size());
for (auto [groupIndex, compute] : llvm::enumerate(group)) {
auto groupSend = cast<spatial::SpatChannelSendOp>(&*opIts[groupIndex]);
entries.push_back({groupSend.getChannelId(), groupSend.getSourceCoreId(), groupSend.getTargetCoreId()});
BatchSendEntry entry;
if (!getScalarChannelMetadata(groupSend, entry.channelId, entry.sourceCoreId, entry.targetCoreId))
return;
entries.push_back(entry);
++opIts[groupIndex];
}
SmallVector<int64_t> channelIds;
@@ -365,11 +641,14 @@ void rebatchEquivalentComputes(func::FuncOp funcOp) {
sourceCoreIds.push_back(static_cast<int32_t>(entry.sourceCoreId));
targetCoreIds.push_back(static_cast<int32_t>(entry.targetCoreId));
}
SmallVector<Value> channelIdValues = createIndexConstants(sendOp, channelIds, constantFolder);
SmallVector<Value> sourceCoreIdValues = createIndexConstants(sendOp, sourceCoreIds, constantFolder);
SmallVector<Value> targetCoreIdValues = createIndexConstants(sendOp, targetCoreIds, constantFolder);
spatial::SpatChannelSendBatchOp::create(rewriter,
sendOp.getLoc(),
rewriter.getDenseI64ArrayAttr(channelIds),
rewriter.getDenseI32ArrayAttr(sourceCoreIds),
rewriter.getDenseI32ArrayAttr(targetCoreIds),
channelIdValues,
sourceCoreIdValues,
targetCoreIdValues,
mapper.lookup(sendOp.getInput()));
continue;
}
@@ -452,6 +731,11 @@ LogicalResult runPostMergeCompactionPipeline(func::FuncOp funcOp, int64_t& nextC
ScopedMergePhaseTimer timer("cleanup-dead-packing-ops");
cleanupDeadPackingOps(funcOp);
}
{
ScopedMergePhaseTimer timer("materialize-batch-result-communication");
if (failed(materializeBatchResultCommunication(funcOp, nextChannelId)))
return failure();
}
return success();
}
@@ -3,6 +3,7 @@
#include "mlir/Dialect/Tensor/IR/Tensor.h"
#include "mlir/IR/BuiltinTypes.h"
#include "mlir/IR/IRMapping.h"
#include "mlir/IR/Matchers.h"
#include "mlir/IR/PatternMatch.h"
#include "mlir/IR/Value.h"
#include "mlir/Support/LLVM.h"
@@ -30,7 +31,7 @@ enum class RegularStepKind {
struct RegularStep {
RegularStepKind kind;
int32_t weightIndex = 0;
Value weight;
Value invariantOperand;
Type resultType;
};
@@ -73,15 +74,90 @@ static uint64_t getEndpointKey(uint32_t sourceCoreId, uint32_t targetCoreId) {
return (static_cast<uint64_t>(sourceCoreId) << 32) | static_cast<uint64_t>(targetCoreId);
}
static void appendChannelAttrs(SmallVectorImpl<int64_t>& channelIds,
SmallVectorImpl<int32_t>& sourceCoreIds,
SmallVectorImpl<int32_t>& targetCoreIds,
uint64_t channelId,
uint32_t sourceCoreId,
uint32_t targetCoreId) {
channelIds.push_back(static_cast<int64_t>(channelId));
sourceCoreIds.push_back(static_cast<int32_t>(sourceCoreId));
targetCoreIds.push_back(static_cast<int32_t>(targetCoreId));
static FailureOr<int64_t> getConstantI64Value(Value value) {
APInt constantValue;
if (!matchPattern(value, m_ConstantInt(&constantValue)))
return failure();
return constantValue.getSExtValue();
}
static FailureOr<int32_t> getConstantI32Value(Value value) {
APInt constantValue;
if (!matchPattern(value, m_ConstantInt(&constantValue)))
return failure();
return static_cast<int32_t>(constantValue.getSExtValue());
}
static bool getScalarChannelMetadata(spatial::SpatChannelSendOp op,
uint64_t& channelId,
uint32_t& sourceCoreId,
uint32_t& targetCoreId) {
FailureOr<int64_t> constantChannelId = getConstantI64Value(op.getChannelId());
FailureOr<int32_t> constantSourceCoreId = getConstantI32Value(op.getSourceCoreId());
FailureOr<int32_t> constantTargetCoreId = getConstantI32Value(op.getTargetCoreId());
if (failed(constantChannelId) || failed(constantSourceCoreId) || failed(constantTargetCoreId))
return false;
channelId = static_cast<uint64_t>(*constantChannelId);
sourceCoreId = static_cast<uint32_t>(*constantSourceCoreId);
targetCoreId = static_cast<uint32_t>(*constantTargetCoreId);
return true;
}
static bool getScalarChannelMetadata(spatial::SpatChannelReceiveOp op,
uint64_t& channelId,
uint32_t& sourceCoreId,
uint32_t& targetCoreId) {
FailureOr<int64_t> constantChannelId = getConstantI64Value(op.getChannelId());
FailureOr<int32_t> constantSourceCoreId = getConstantI32Value(op.getSourceCoreId());
FailureOr<int32_t> constantTargetCoreId = getConstantI32Value(op.getTargetCoreId());
if (failed(constantChannelId) || failed(constantSourceCoreId) || failed(constantTargetCoreId))
return false;
channelId = static_cast<uint64_t>(*constantChannelId);
sourceCoreId = static_cast<uint32_t>(*constantSourceCoreId);
targetCoreId = static_cast<uint32_t>(*constantTargetCoreId);
return true;
}
static SmallVector<Value> createIndexConstants(Operation* anchorOp, ArrayRef<int64_t> values, OperationFolder& folder) {
SmallVector<Value> constants;
constants.reserve(values.size());
for (int64_t value : values)
constants.push_back(getOrCreateHostIndexConstant(anchorOp, value, folder));
return constants;
}
static SmallVector<Value> createIndexConstants(Operation* anchorOp, ArrayRef<int32_t> values, OperationFolder& folder) {
SmallVector<Value> constants;
constants.reserve(values.size());
for (int32_t value : values)
constants.push_back(getOrCreateHostIndexConstant(anchorOp, value, folder));
return constants;
}
static SmallVector<Operation*> getScalarChannelMetadataDefs(Operation* channelOp, unsigned metadataOperandCount) {
SmallVector<Operation*> defs;
defs.reserve(metadataOperandCount);
for (unsigned operandIndex = 0; operandIndex < metadataOperandCount; ++operandIndex) {
Operation* def = channelOp->getOperand(operandIndex).getDefiningOp();
auto constantOp = dyn_cast_or_null<arith::ConstantOp>(def);
if (!constantOp || def->getBlock() != channelOp->getBlock())
continue;
defs.push_back(def);
}
llvm::sort(defs, [](Operation* lhs, Operation* rhs) { return lhs->isBeforeInBlock(rhs); });
return defs;
}
static void moveScalarChannelBundleBefore(Operation* channelOp, Operation* insertionPoint) {
for (Operation* metadataDef : getScalarChannelMetadataDefs(channelOp, /*metadataOperandCount=*/3))
metadataDef->moveBefore(insertionPoint);
channelOp->moveBefore(insertionPoint);
}
static void moveScalarChannelBundleBefore(Operation* channelOp, Block* block, Block::iterator insertionPoint) {
for (Operation* metadataDef : getScalarChannelMetadataDefs(channelOp, /*metadataOperandCount=*/3))
metadataDef->moveBefore(block, insertionPoint);
channelOp->moveBefore(block, insertionPoint);
}
static spatial::SpatConcatOp getContiguousConcatUse(ValueRange values, unsigned& startOperandIndex) {
@@ -196,7 +272,7 @@ static Value createPackedTensorForValues(ValueRange values, IRRewriter& rewriter
}
static bool areEquivalentRegularSteps(const RegularStep& lhs, const RegularStep& rhs) {
return lhs.kind == rhs.kind && lhs.weightIndex == rhs.weightIndex && lhs.invariantOperand == rhs.invariantOperand
return lhs.kind == rhs.kind && lhs.weight == rhs.weight && lhs.invariantOperand == rhs.invariantOperand
&& lhs.resultType == rhs.resultType;
}
@@ -227,8 +303,7 @@ static FailureOr<RegularChunk> analyzeRegularChunk(spatial::SpatVMMOp startOp) {
chunk.input = startOp.getInput();
chunk.output = startOp.getOutput();
chunk.ops.push_back(startOp.getOperation());
chunk.steps.push_back(
{RegularStepKind::Wvmm, static_cast<int32_t>(startOp.getWeightIndex()), Value(), startOp.getOutput().getType()});
chunk.steps.push_back({RegularStepKind::Wvmm, startOp.getWeight(), Value(), startOp.getOutput().getType()});
Value currentValue = startOp.getOutput();
while (currentValue.hasOneUse()) {
@@ -241,9 +316,9 @@ static FailureOr<RegularChunk> analyzeRegularChunk(spatial::SpatVMMOp startOp) {
break;
if (vaddOp.getLhs() == currentValue)
chunk.steps.push_back({RegularStepKind::VAddLhs, 0, vaddOp.getRhs(), vaddOp.getOutput().getType()});
chunk.steps.push_back({RegularStepKind::VAddLhs, Value(), vaddOp.getRhs(), vaddOp.getOutput().getType()});
else if (vaddOp.getRhs() == currentValue)
chunk.steps.push_back({RegularStepKind::VAddRhs, 0, vaddOp.getLhs(), vaddOp.getOutput().getType()});
chunk.steps.push_back({RegularStepKind::VAddRhs, Value(), vaddOp.getLhs(), vaddOp.getOutput().getType()});
else
break;
@@ -255,7 +330,8 @@ static FailureOr<RegularChunk> analyzeRegularChunk(spatial::SpatVMMOp startOp) {
return chunk;
}
static RegularCompactionResult compactRegularChunkRun(IRRewriter& rewriter, ArrayRef<RegularChunk> run) {
static RegularCompactionResult
compactRegularChunkRun(IRRewriter& rewriter, ArrayRef<RegularChunk> run, OperationFolder& constantFolder) {
assert(!run.empty() && "expected a non-empty regular chunk run");
const RegularChunk& anchorChunk = run.front();
RegularCompactionResult result;
@@ -275,9 +351,9 @@ static RegularCompactionResult compactRegularChunkRun(IRRewriter& rewriter, Arra
auto packedOutputType = getPackedTensorType(outputType, static_cast<int64_t>(run.size()));
auto packedInit = tensor::EmptyOp::create(
rewriter, anchorChunk.startOp->getLoc(), packedOutputType.getShape(), packedOutputType.getElementType());
auto zero = arith::ConstantIndexOp::create(rewriter, anchorChunk.startOp->getLoc(), 0);
auto upper = arith::ConstantIndexOp::create(rewriter, anchorChunk.startOp->getLoc(), run.size());
auto step = arith::ConstantIndexOp::create(rewriter, anchorChunk.startOp->getLoc(), 1);
auto zero = getOrCreateHostIndexConstant(anchorChunk.startOp, 0, constantFolder);
auto upper = getOrCreateHostIndexConstant(anchorChunk.startOp, static_cast<int64_t>(run.size()), constantFolder);
auto step = getOrCreateHostIndexConstant(anchorChunk.startOp, 1, constantFolder);
auto loop =
scf::ForOp::create(rewriter, anchorChunk.startOp->getLoc(), zero, upper, step, ValueRange {packedInit.getResult()});
@@ -290,8 +366,7 @@ static RegularCompactionResult compactRegularChunkRun(IRRewriter& rewriter, Arra
Value inputRowOffset = iv;
if (inputType.getDimSize(0) != 1) {
auto rowsPerValue =
arith::ConstantIndexOp::create(rewriter, anchorChunk.startOp->getLoc(), inputType.getDimSize(0));
auto rowsPerValue = getOrCreateHostIndexConstant(anchorChunk.startOp, inputType.getDimSize(0), constantFolder);
inputRowOffset = arith::MulIOp::create(rewriter, anchorChunk.startOp->getLoc(), iv, rowsPerValue);
}
@@ -320,8 +395,7 @@ static RegularCompactionResult compactRegularChunkRun(IRRewriter& rewriter, Arra
Value mappedOutput = mapping.lookup(anchorChunk.output);
Value outputRowOffset = iv;
if (outputType.getDimSize(0) != 1) {
auto rowsPerValue =
arith::ConstantIndexOp::create(rewriter, anchorChunk.startOp->getLoc(), outputType.getDimSize(0));
auto rowsPerValue = getOrCreateHostIndexConstant(anchorChunk.startOp, outputType.getDimSize(0), constantFolder);
outputRowOffset = arith::MulIOp::create(rewriter, anchorChunk.startOp->getLoc(), iv, rowsPerValue);
}
@@ -389,35 +463,50 @@ void orderBilateralChannelOps(func::FuncOp funcOp) {
Block& block = compute.getBody().front();
SmallVector<std::pair<spatial::SpatChannelReceiveOp, Operation*>> moves;
DenseMap<uint64_t, Operation*> firstForwardedSendByEndpoint;
Operation* firstForwardedSend = nullptr;
for (Operation& op : block) {
if (auto sendOp = dyn_cast<spatial::SpatChannelSendOp>(&op)) {
if (sendOp.getSourceCoreId() == static_cast<uint32_t>(coreId)
&& isForwardedChannelPayload(sendOp.getInput(), block)) {
uint64_t key = getEndpointKey(sendOp.getSourceCoreId(), sendOp.getTargetCoreId());
uint64_t channelId = 0;
uint32_t sourceCoreId = 0;
uint32_t targetCoreId = 0;
if (getScalarChannelMetadata(sendOp, channelId, sourceCoreId, targetCoreId)
&& sourceCoreId == static_cast<uint32_t>(coreId) && isForwardedChannelPayload(sendOp.getInput(), block)) {
if (!firstForwardedSend)
firstForwardedSend = sendOp.getOperation();
uint64_t key = getEndpointKey(sourceCoreId, targetCoreId);
firstForwardedSendByEndpoint.try_emplace(key, sendOp.getOperation());
}
continue;
}
auto receiveOp = dyn_cast<spatial::SpatChannelReceiveOp>(&op);
if (!receiveOp || receiveOp.getTargetCoreId() != static_cast<uint32_t>(coreId)
|| receiveOp.getSourceCoreId() >= static_cast<uint32_t>(coreId)) {
uint64_t channelId = 0;
uint32_t sourceCoreId = 0;
uint32_t targetCoreId = 0;
if (!receiveOp || !getScalarChannelMetadata(receiveOp, channelId, sourceCoreId, targetCoreId)
|| targetCoreId != static_cast<uint32_t>(coreId) || sourceCoreId >= static_cast<uint32_t>(coreId)) {
continue;
}
uint64_t key = getEndpointKey(static_cast<uint32_t>(coreId), receiveOp.getSourceCoreId());
uint64_t key = getEndpointKey(static_cast<uint32_t>(coreId), sourceCoreId);
auto firstMatchingSend = firstForwardedSendByEndpoint.find(key);
if (firstMatchingSend != firstForwardedSendByEndpoint.end())
moves.push_back({receiveOp, firstMatchingSend->second});
else if (firstForwardedSend && firstForwardedSend->isBeforeInBlock(receiveOp))
moves.push_back({receiveOp, firstForwardedSend});
}
for (auto [receiveOp, insertionPoint] : moves)
receiveOp->moveBefore(insertionPoint);
moveScalarChannelBundleBefore(receiveOp, insertionPoint);
for (auto it = block.begin(); it != block.end();) {
auto receiveOp = dyn_cast<spatial::SpatChannelReceiveOp>(&*it);
if (!receiveOp || receiveOp.getSourceCoreId() >= static_cast<uint32_t>(coreId)) {
uint64_t channelId = 0;
uint32_t sourceCoreId = 0;
uint32_t targetCoreId = 0;
if (!receiveOp || !getScalarChannelMetadata(receiveOp, channelId, sourceCoreId, targetCoreId)
|| sourceCoreId >= static_cast<uint32_t>(coreId)) {
++it;
continue;
}
@@ -425,18 +514,32 @@ void orderBilateralChannelOps(func::FuncOp funcOp) {
Type outputType = receiveOp.getOutput().getType();
auto run = collectConsecutiveRun<spatial::SpatChannelReceiveOp>(
it, block.end(), [&](spatial::SpatChannelReceiveOp current) {
uint64_t currentChannelId = 0;
uint32_t currentSourceCoreId = 0;
uint32_t currentTargetCoreId = 0;
return current.getOutput().getType() == outputType
&& current.getSourceCoreId() < static_cast<uint32_t>(coreId);
&& getScalarChannelMetadata(current, currentChannelId, currentSourceCoreId, currentTargetCoreId)
&& currentSourceCoreId < static_cast<uint32_t>(coreId);
});
if (run.ops.size() > 1) {
SmallVector<spatial::SpatChannelReceiveOp> sorted(run.ops);
llvm::stable_sort(sorted, [](spatial::SpatChannelReceiveOp lhs, spatial::SpatChannelReceiveOp rhs) {
return lhs.getSourceCoreId() > rhs.getSourceCoreId();
uint64_t lhsChannelId = 0;
uint32_t lhsSourceCoreId = 0;
uint32_t lhsTargetCoreId = 0;
uint64_t rhsChannelId = 0;
uint32_t rhsSourceCoreId = 0;
uint32_t rhsTargetCoreId = 0;
bool lhsHasMetadata = getScalarChannelMetadata(lhs, lhsChannelId, lhsSourceCoreId, lhsTargetCoreId);
bool rhsHasMetadata = getScalarChannelMetadata(rhs, rhsChannelId, rhsSourceCoreId, rhsTargetCoreId);
if (!lhsHasMetadata || !rhsHasMetadata)
return false;
return lhsSourceCoreId > rhsSourceCoreId;
});
Block::iterator insertIt = run.end;
for (auto op : sorted)
op->moveBefore(&block, insertIt);
moveScalarChannelBundleBefore(op, &block, insertIt);
}
it = run.end;
@@ -446,6 +549,7 @@ void orderBilateralChannelOps(func::FuncOp funcOp) {
void compactScalarChannelRuns(func::FuncOp funcOp, int64_t& nextChannelId) {
IRRewriter rewriter(funcOp.getContext());
OperationFolder constantFolder(funcOp.getContext());
for (auto compute : funcOp.getOps<spatial::SpatCompute>()) {
Block& block = compute.getBody().front();
@@ -461,7 +565,14 @@ void compactScalarChannelRuns(func::FuncOp funcOp, int64_t& nextChannelId) {
bool hasRepeatedEndpoint = false;
DenseSet<uint64_t> seenEndpoints;
for (auto op : run.ops) {
uint64_t endpointKey = getEndpointKey(op.getSourceCoreId(), op.getTargetCoreId());
uint64_t channelId = 0;
uint32_t sourceCoreId = 0;
uint32_t targetCoreId = 0;
if (!getScalarChannelMetadata(op, channelId, sourceCoreId, targetCoreId)) {
hasRepeatedEndpoint = true;
break;
}
uint64_t endpointKey = getEndpointKey(sourceCoreId, targetCoreId);
if (!seenEndpoints.insert(endpointKey).second) {
hasRepeatedEndpoint = true;
break;
@@ -478,8 +589,20 @@ void compactScalarChannelRuns(func::FuncOp funcOp, int64_t& nextChannelId) {
};
SmallVector<ReceiveEntry> sortedEntries;
sortedEntries.reserve(run.ops.size());
for (auto [originalIndex, op] : llvm::enumerate(run.ops))
sortedEntries.push_back({op, originalIndex, op.getSourceCoreId(), op.getTargetCoreId(), op.getChannelId()});
for (auto [originalIndex, op] : llvm::enumerate(run.ops)) {
uint64_t channelId = 0;
uint32_t sourceCoreId = 0;
uint32_t targetCoreId = 0;
if (!getScalarChannelMetadata(op, channelId, sourceCoreId, targetCoreId)) {
sortedEntries.clear();
break;
}
sortedEntries.push_back({op, originalIndex, sourceCoreId, targetCoreId, channelId});
}
if (sortedEntries.empty()) {
++it;
continue;
}
SmallVector<int64_t> channelIds;
SmallVector<int32_t> sourceCoreIds;
@@ -488,8 +611,9 @@ void compactScalarChannelRuns(func::FuncOp funcOp, int64_t& nextChannelId) {
sourceCoreIds.reserve(sortedEntries.size());
targetCoreIds.reserve(sortedEntries.size());
for (ReceiveEntry& entry : sortedEntries) {
appendChannelAttrs(
channelIds, sourceCoreIds, targetCoreIds, entry.channelId, entry.sourceCoreId, entry.targetCoreId);
channelIds.push_back(static_cast<int64_t>(entry.channelId));
sourceCoreIds.push_back(static_cast<int32_t>(entry.sourceCoreId));
targetCoreIds.push_back(static_cast<int32_t>(entry.targetCoreId));
}
auto rowType = cast<RankedTensorType>(run.ops.front().getOutput().getType());
@@ -506,13 +630,11 @@ void compactScalarChannelRuns(func::FuncOp funcOp, int64_t& nextChannelId) {
: RankedTensorType {};
auto packedType = concatPackedType ? concatPackedType : fallbackPackedType;
rewriter.setInsertionPoint(run.ops.front());
auto compactReceive =
spatial::SpatChannelReceiveTensorOp::create(rewriter,
run.ops.front().getLoc(),
packedType,
rewriter.getDenseI64ArrayAttr(channelIds),
rewriter.getDenseI32ArrayAttr(sourceCoreIds),
rewriter.getDenseI32ArrayAttr(targetCoreIds));
SmallVector<Value> channelIdValues = createIndexConstants(run.ops.front(), channelIds, constantFolder);
SmallVector<Value> sourceCoreIdValues = createIndexConstants(run.ops.front(), sourceCoreIds, constantFolder);
SmallVector<Value> targetCoreIdValues = createIndexConstants(run.ops.front(), targetCoreIds, constantFolder);
auto compactReceive = spatial::SpatChannelReceiveTensorOp::create(
rewriter, run.ops.front().getLoc(), packedType, channelIdValues, sourceCoreIdValues, targetCoreIdValues);
if (concatOp && concatPackedType) {
replaceConcatRunWithPackedValue(concatOp,
concatStartIndex,
@@ -551,8 +673,20 @@ void compactScalarChannelRuns(func::FuncOp funcOp, int64_t& nextChannelId) {
};
SmallVector<SendEntry> sortedEntries;
sortedEntries.reserve(run.ops.size());
for (auto op : run.ops)
sortedEntries.push_back({op, op.getSourceCoreId(), op.getTargetCoreId(), op.getChannelId()});
for (auto op : run.ops) {
uint64_t channelId = 0;
uint32_t sourceCoreId = 0;
uint32_t targetCoreId = 0;
if (!getScalarChannelMetadata(op, channelId, sourceCoreId, targetCoreId)) {
sortedEntries.clear();
break;
}
sortedEntries.push_back({op, sourceCoreId, targetCoreId, channelId});
}
if (sortedEntries.empty()) {
++it;
continue;
}
SmallVector<int64_t> channelIds;
SmallVector<int32_t> sourceCoreIds;
@@ -563,20 +697,20 @@ void compactScalarChannelRuns(func::FuncOp funcOp, int64_t& nextChannelId) {
targetCoreIds.reserve(sortedEntries.size());
inputs.reserve(sortedEntries.size());
for (SendEntry& entry : sortedEntries) {
appendChannelAttrs(
channelIds, sourceCoreIds, targetCoreIds, entry.channelId, entry.sourceCoreId, entry.targetCoreId);
channelIds.push_back(static_cast<int64_t>(entry.channelId));
sourceCoreIds.push_back(static_cast<int32_t>(entry.sourceCoreId));
targetCoreIds.push_back(static_cast<int32_t>(entry.targetCoreId));
inputs.push_back(entry.op.getInput());
}
rewriter.setInsertionPoint(run.ops.front());
Value packedInput = createPackedTensorForValues(ValueRange(inputs), rewriter, run.ops.front().getLoc());
if (packedInput) {
spatial::SpatChannelSendTensorOp::create(rewriter,
run.ops.front().getLoc(),
rewriter.getDenseI64ArrayAttr(channelIds),
rewriter.getDenseI32ArrayAttr(sourceCoreIds),
rewriter.getDenseI32ArrayAttr(targetCoreIds),
packedInput);
SmallVector<Value> channelIdValues = createIndexConstants(run.ops.front(), channelIds, constantFolder);
SmallVector<Value> sourceCoreIdValues = createIndexConstants(run.ops.front(), sourceCoreIds, constantFolder);
SmallVector<Value> targetCoreIdValues = createIndexConstants(run.ops.front(), targetCoreIds, constantFolder);
spatial::SpatChannelSendTensorOp::create(
rewriter, run.ops.front().getLoc(), channelIdValues, sourceCoreIdValues, targetCoreIdValues, packedInput);
for (auto op : run.ops)
rewriter.eraseOp(op);
@@ -606,9 +740,9 @@ void compactBatchChannelRuns(func::FuncOp funcOp) {
});
if (run.ops.size() > 1) {
SmallVector<int64_t> channelIds;
SmallVector<int32_t> sourceCoreIds;
SmallVector<int32_t> targetCoreIds;
SmallVector<Value> channelIds;
SmallVector<Value> sourceCoreIds;
SmallVector<Value> targetCoreIds;
for (auto op : run.ops) {
llvm::append_range(channelIds, op.getChannelIds());
llvm::append_range(sourceCoreIds, op.getSourceCoreIds());
@@ -629,13 +763,8 @@ void compactBatchChannelRuns(func::FuncOp funcOp) {
: RankedTensorType {};
auto packedType = concatPackedType ? concatPackedType : fallbackPackedType;
rewriter.setInsertionPoint(run.ops.front());
auto compactReceive =
spatial::SpatChannelReceiveTensorBatchOp::create(rewriter,
run.ops.front().getLoc(),
packedType,
rewriter.getDenseI64ArrayAttr(channelIds),
rewriter.getDenseI32ArrayAttr(sourceCoreIds),
rewriter.getDenseI32ArrayAttr(targetCoreIds));
auto compactReceive = spatial::SpatChannelReceiveTensorBatchOp::create(
rewriter, run.ops.front().getLoc(), packedType, channelIds, sourceCoreIds, targetCoreIds);
if (concatOp && concatPackedType) {
replaceConcatRunWithPackedValue(
concatOp, concatStartIndex, static_cast<unsigned>(outputs.size()), compactReceive.getOutput(), rewriter);
@@ -663,9 +792,9 @@ void compactBatchChannelRuns(func::FuncOp funcOp) {
});
if (run.ops.size() > 1) {
SmallVector<int64_t> channelIds;
SmallVector<int32_t> sourceCoreIds;
SmallVector<int32_t> targetCoreIds;
SmallVector<Value> channelIds;
SmallVector<Value> sourceCoreIds;
SmallVector<Value> targetCoreIds;
SmallVector<Value> inputs;
inputs.reserve(run.ops.size());
for (auto op : run.ops) {
@@ -678,12 +807,8 @@ void compactBatchChannelRuns(func::FuncOp funcOp) {
rewriter.setInsertionPoint(run.ops.front());
Value packedInput = createPackedTensorForValues(ValueRange(inputs), rewriter, run.ops.front().getLoc());
if (packedInput) {
spatial::SpatChannelSendTensorBatchOp::create(rewriter,
run.ops.front().getLoc(),
rewriter.getDenseI64ArrayAttr(channelIds),
rewriter.getDenseI32ArrayAttr(sourceCoreIds),
rewriter.getDenseI32ArrayAttr(targetCoreIds),
packedInput);
spatial::SpatChannelSendTensorBatchOp::create(
rewriter, run.ops.front().getLoc(), channelIds, sourceCoreIds, targetCoreIds, packedInput);
for (auto op : run.ops)
rewriter.eraseOp(op);
@@ -700,6 +825,7 @@ void compactBatchChannelRuns(func::FuncOp funcOp) {
void compactRegularOpRuns(func::FuncOp funcOp) {
IRRewriter rewriter(funcOp.getContext());
OperationFolder constantFolder(funcOp.getContext());
auto compactInBlock = [&](Block& block) {
for (auto it = block.begin(); it != block.end();) {
@@ -740,7 +866,7 @@ void compactRegularOpRuns(func::FuncOp funcOp) {
for (const RegularChunk& chunk : run)
originalOpCount += chunk.ops.size();
RegularCompactionResult result = compactRegularChunkRun(rewriter, run);
RegularCompactionResult result = compactRegularChunkRun(rewriter, run, constantFolder);
if (result.changed) {
assert(originalOpCount > anchorChunk->ops.size() && "successful regular compaction must consume the run");
if (!result.resumeAfter) {
@@ -763,6 +889,7 @@ void compactRegularOpRuns(func::FuncOp funcOp) {
void compactRowWiseWvmmRuns(func::FuncOp funcOp) {
IRRewriter rewriter(funcOp.getContext());
OperationFolder constantFolder(funcOp.getContext());
for (auto compute : funcOp.getOps<spatial::SpatCompute>()) {
Block& block = compute.getBody().front();
@@ -784,7 +911,7 @@ void compactRowWiseWvmmRuns(func::FuncOp funcOp) {
int64_t expectedRow = static_cast<int64_t>(rowResult.getResultNumber());
auto run = collectConsecutiveRun<spatial::SpatVMMOp>(it, block.end(), [&](spatial::SpatVMMOp current) {
if (current.getWeightIndex() != wvmmOp.getWeightIndex()
if (current.getWeight() != wvmmOp.getWeight()
|| current.getInput().getDefiningOp<spatial::SpatExtractRowsOp>() != extractRowsOp
|| current.getInput().getType() != wvmmOp.getInput().getType()
|| current.getOutput().getType() != wvmmOp.getOutput().getType())
@@ -851,9 +978,9 @@ void compactRowWiseWvmmRuns(func::FuncOp funcOp) {
auto packedType = RankedTensorType::get({runLength, outputCols}, outputType.getElementType());
rewriter.setInsertionPoint(run.ops.front());
auto zero = arith::ConstantIndexOp::create(rewriter, run.ops.front().getLoc(), 0);
auto upper = arith::ConstantIndexOp::create(rewriter, run.ops.front().getLoc(), runLength);
auto step = arith::ConstantIndexOp::create(rewriter, run.ops.front().getLoc(), 1);
auto zero = getOrCreateHostIndexConstant(run.ops.front(), 0, constantFolder);
auto upper = getOrCreateHostIndexConstant(run.ops.front(), runLength, constantFolder);
auto step = getOrCreateHostIndexConstant(run.ops.front(), 1, constantFolder);
auto packedInit =
tensor::EmptyOp::create(rewriter, run.ops.front().getLoc(), packedType.getShape(), packedType.getElementType());
auto loop =
@@ -868,7 +995,7 @@ void compactRowWiseWvmmRuns(func::FuncOp funcOp) {
Value sourceRow = iv;
if (firstRow != 0) {
auto firstRowValue = arith::ConstantIndexOp::create(rewriter, run.ops.front().getLoc(), firstRow);
auto firstRowValue = getOrCreateHostIndexConstant(run.ops.front(), firstRow, constantFolder);
sourceRow = arith::AddIOp::create(rewriter, run.ops.front().getLoc(), iv, firstRowValue);
}
@@ -883,7 +1010,7 @@ void compactRowWiseWvmmRuns(func::FuncOp funcOp) {
extractSizes,
extractStrides);
auto loopWvmm = spatial::SpatVMMOp::create(
rewriter, run.ops.front().getLoc(), outputType, wvmmOp.getWeightIndex(), extractedRow.getResult());
rewriter, run.ops.front().getLoc(), outputType, wvmmOp.getWeight(), extractedRow.getResult());
SmallVector<OpFoldResult> insertOffsets = {iv, rewriter.getIndexAttr(0)};
SmallVector<OpFoldResult> insertSizes = {rewriter.getIndexAttr(1), rewriter.getIndexAttr(outputCols)};
@@ -23,31 +23,31 @@ using namespace mlir;
namespace {
Weight getComputeBodyWeight(Region &body) {
Weight getComputeBodyWeight(Region& body) {
constexpr Weight kOperationWeight = 100;
Weight numOperations = 0;
for (auto &block : body)
for ([[maybe_unused]] auto &op : block)
for (auto& block : body)
for ([[maybe_unused]] auto& op : block)
numOperations = checkedAdd(numOperations, static_cast<Weight>(1));
return checkedMultiply(numOperations, kOperationWeight);
}
CrossbarUsage getComputeBodyCrossbarUsage(Region &body) {
CrossbarUsage getComputeBodyCrossbarUsage(Region& body) {
CrossbarUsage crossbarUsage = 0;
for (auto &block : body)
for (auto &op : block)
for (auto& block : body)
for (auto& op : block)
if (isa<SpatVMMOp>(op))
crossbarUsage = checkedAdd(crossbarUsage, static_cast<CrossbarUsage>(1));
return crossbarUsage;
}
bool isUsedAsWeightOnly(Operation *producerOp) {
bool isUsedAsWeightOnly(Operation* producerOp) {
if (producerOp->getNumResults() == 0)
return false;
for (Value result : producerOp->getResults()) {
if (result.use_empty())
return false;
for (Operation *user : result.getUsers()) {
for (Operation* user : result.getUsers()) {
if (auto compute = dyn_cast<SpatCompute>(user)) {
if (!llvm::is_contained(compute.getWeights(), result))
return false;
@@ -66,7 +66,7 @@ bool isUsedAsWeightOnly(Operation *producerOp) {
std::vector<ComputeGraphEdge> aggregateEdges(llvm::ArrayRef<ComputeGraphEdge> edges) {
llvm::DenseMap<std::pair<size_t, size_t>, Weight> edgeWeights;
for (const ComputeGraphEdge &edge : edges) {
for (const ComputeGraphEdge& edge : edges) {
if (edge.source == edge.target)
continue;
auto inserted = edgeWeights.try_emplace({edge.source, edge.target}, edge.transferCost);
@@ -76,9 +76,9 @@ std::vector<ComputeGraphEdge> aggregateEdges(llvm::ArrayRef<ComputeGraphEdge> ed
std::vector<ComputeGraphEdge> aggregatedEdges;
aggregatedEdges.reserve(edgeWeights.size());
for (const auto &[key, weight] : edgeWeights)
for (const auto& [key, weight] : edgeWeights)
aggregatedEdges.push_back({key.first, key.second, weight});
llvm::sort(aggregatedEdges, [](const ComputeGraphEdge &lhs, const ComputeGraphEdge &rhs) {
llvm::sort(aggregatedEdges, [](const ComputeGraphEdge& lhs, const ComputeGraphEdge& rhs) {
if (lhs.source != rhs.source)
return lhs.source < rhs.source;
return lhs.target < rhs.target;
@@ -88,33 +88,33 @@ std::vector<ComputeGraphEdge> aggregateEdges(llvm::ArrayRef<ComputeGraphEdge> ed
} // namespace
Weight getComputeInstanceWeight(const ComputeInstance &instance) {
Weight getComputeInstanceWeight(const ComputeInstance& instance) {
if (auto spatCompute = dyn_cast<SpatCompute>(instance.op))
return getSpatComputeWeight(spatCompute);
auto batch = cast<SpatComputeBatch>(instance.op);
return checkedMultiply(getComputeBodyWeight(batch.getBody()), static_cast<Weight>(instance.laneCount));
}
CrossbarUsage getComputeInstanceCrossbarUsage(const ComputeInstance &instance) {
CrossbarUsage getComputeInstanceCrossbarUsage(const ComputeInstance& instance) {
if (auto spatCompute = dyn_cast<SpatCompute>(instance.op))
return getSpatComputeCrossbarUsage(spatCompute);
auto batch = cast<SpatComputeBatch>(instance.op);
return checkedMultiply(getComputeBodyCrossbarUsage(batch.getBody()),
static_cast<CrossbarUsage>(instance.laneCount));
return checkedMultiply(getComputeBodyCrossbarUsage(batch.getBody()), static_cast<CrossbarUsage>(instance.laneCount));
}
ComputeGraph buildComputeGraph(Operation *entryOp) {
ComputeGraph buildComputeGraph(Operation* entryOp) {
ComputeGraph graph;
for (Region &region : entryOp->getRegions()) {
for (Block &block : region) {
for (Operation &op : block) {
for (Region& region : entryOp->getRegions()) {
for (Block& block : region) {
for (Operation& op : block) {
if (auto spatCompute = dyn_cast<SpatCompute>(&op)) {
if (isUsedAsWeightOnly(spatCompute.getOperation()))
continue;
ComputeInstance instance {spatCompute.getOperation(), 0, 1};
size_t index = graph.nodes.size();
graph.nodes.push_back({instance, getComputeInstanceWeight(instance), getComputeInstanceCrossbarUsage(instance), index});
graph.nodes.push_back(
{instance, getComputeInstanceWeight(instance), getComputeInstanceCrossbarUsage(instance), index});
graph.instanceToIndex[instance] = index;
continue;
}
@@ -135,9 +135,21 @@ ComputeGraph buildComputeGraph(Operation *entryOp) {
}
llvm::SmallVector<ComputeGraphEdge, 16> rawEdges;
for (const auto &[targetIndex, node] : llvm::enumerate(graph.nodes)) {
for (const auto& [targetIndex, node] : llvm::enumerate(graph.nodes)) {
for (Value input : getComputeInstanceInputs(node.instance)) {
auto producerInstance = getComputeProducerInstance(input);
if (auto producerBatch = dyn_cast_or_null<SpatComputeBatch>(input.getDefiningOp());
producerBatch && producerBatch.getNumResults() != 0 && !isa<SpatComputeBatch>(node.instance.op)) {
for (uint32_t lane = 0; lane < static_cast<uint32_t>(producerBatch.getLaneCount()); ++lane) {
auto producerIt = graph.instanceToIndex.find(getBatchChunkForLane(producerBatch, lane));
if (producerIt == graph.instanceToIndex.end())
continue;
rawEdges.push_back(
{producerIt->second, targetIndex, static_cast<Weight>(getSizeInBytes(cast<ShapedType>(input.getType())))});
}
continue;
}
auto producerInstance = getComputeProducerInstance(input, &node.instance);
if (!producerInstance)
continue;
auto producerIt = graph.instanceToIndex.find(*producerInstance);
@@ -152,7 +164,7 @@ ComputeGraph buildComputeGraph(Operation *entryOp) {
graph.edges.append(aggregatedEdges.begin(), aggregatedEdges.end());
graph.successors.assign(graph.nodes.size(), {});
graph.predecessors.assign(graph.nodes.size(), {});
for (const ComputeGraphEdge &edge : graph.edges) {
for (const ComputeGraphEdge& edge : graph.edges) {
graph.successors[edge.source].push_back({edge.target, edge.transferCost});
graph.predecessors[edge.target].push_back({edge.source, edge.transferCost});
}
@@ -160,7 +172,7 @@ ComputeGraph buildComputeGraph(Operation *entryOp) {
return graph;
}
bool verifyAcyclic(const ComputeGraph &graph) {
bool verifyAcyclic(const ComputeGraph& graph) {
std::vector<size_t> remainingParents(graph.nodes.size(), 0);
std::queue<size_t> readyNodes;
for (size_t node = 0; node < graph.nodes.size(); ++node) {
@@ -174,7 +186,7 @@ bool verifyAcyclic(const ComputeGraph &graph) {
size_t node = readyNodes.front();
readyNodes.pop();
++visited;
for (const auto &[child, weight] : graph.successors[node]) {
for (const auto& [child, weight] : graph.successors[node]) {
(void) weight;
assert(remainingParents[child] > 0 && "remaining parent count underflow");
if (--remainingParents[child] == 0)
@@ -1,6 +1,8 @@
#include "mlir/Dialect/Arith/IR/Arith.h"
#include "mlir/Dialect/Tensor/IR/Tensor.h"
#include <limits>
#include <optional>
#include "ComputeInstanceUtils.hpp"
#include "src/Accelerators/PIM/Compiler/PimCompilerOptions.hpp"
@@ -18,48 +20,91 @@ size_t getSchedulingCpuBudget() {
size_t getBatchChunkTargetCount(int32_t laneCount) {
assert(laneCount > 0 && "laneCount must be positive");
return std::min(static_cast<size_t>(laneCount), std::max<size_t>(1, getSchedulingCpuBudget()));
return static_cast<size_t>(laneCount);
}
ComputeInstance getBatchChunkForIndex(SpatComputeBatch batch, size_t chunkIndex) {
size_t totalLanes = batch.getLaneCount();
size_t chunkCount = getBatchChunkTargetCount(batch.getLaneCount());
size_t baseChunkSize = totalLanes / chunkCount;
size_t largeChunkCount = totalLanes % chunkCount;
size_t laneStart = chunkIndex * baseChunkSize + std::min(chunkIndex, largeChunkCount);
size_t laneCount = baseChunkSize + (chunkIndex < largeChunkCount ? 1 : 0);
return {batch.getOperation(), static_cast<uint32_t>(laneStart), static_cast<uint32_t>(laneCount)};
assert(chunkIndex < static_cast<size_t>(batch.getLaneCount()) && "chunkIndex out of range");
return {batch.getOperation(), static_cast<uint32_t>(chunkIndex), 1};
}
ComputeInstance getBatchChunkForLane(SpatComputeBatch batch, uint32_t lane) {
size_t totalLanes = batch.getLaneCount();
size_t chunkCount = getBatchChunkTargetCount(batch.getLaneCount());
size_t baseChunkSize = totalLanes / chunkCount;
size_t largeChunkCount = totalLanes % chunkCount;
size_t largeChunkSpan = largeChunkCount * (baseChunkSize + 1);
size_t chunkIndex = 0;
if (static_cast<size_t>(lane) < largeChunkSpan)
chunkIndex = static_cast<size_t>(lane) / (baseChunkSize + 1);
else
chunkIndex = largeChunkCount + (static_cast<size_t>(lane) - largeChunkSpan) / baseChunkSize;
return getBatchChunkForIndex(batch, chunkIndex);
assert(lane < static_cast<uint32_t>(batch.getLaneCount()) && "lane out of range");
return {batch.getOperation(), lane, 1};
}
std::optional<ProducerValueRef> getProducerValueRef(Value value) {
Operation *op = value.getDefiningOp();
static std::optional<uint32_t> getConstantExtractLane(tensor::ExtractSliceOp extract) {
if (extract.getMixedOffsets().empty())
return std::nullopt;
OpFoldResult offset = extract.getMixedOffsets().front();
if (Attribute attr = llvm::dyn_cast<Attribute>(offset)) {
auto intAttr = dyn_cast<IntegerAttr>(attr);
if (!intAttr || intAttr.getInt() < 0)
return std::nullopt;
return static_cast<uint32_t>(intAttr.getInt());
}
Value offsetValue = llvm::cast<Value>(offset);
if (auto constantIndex = offsetValue.getDefiningOp<arith::ConstantIndexOp>()) {
if (constantIndex.value() < 0)
return std::nullopt;
return static_cast<uint32_t>(constantIndex.value());
}
return std::nullopt;
}
static std::optional<ProducerValueRef> getResultfulBatchProducerValueRef(SpatComputeBatch batch,
const ComputeInstance* consumerInstance) {
if (!consumerInstance)
return std::nullopt;
if (!isa<SpatComputeBatch>(consumerInstance->op))
return std::nullopt;
if (consumerInstance->laneStart + consumerInstance->laneCount > static_cast<uint32_t>(batch.getLaneCount()))
return std::nullopt;
return ProducerValueRef {
{batch.getOperation(), consumerInstance->laneStart, consumerInstance->laneCount},
0
};
}
std::optional<ProducerValueRef> getProducerValueRef(Value value, const ComputeInstance* consumerInstance) {
Operation* op = value.getDefiningOp();
if (!op)
return std::nullopt;
while (auto extract = dyn_cast<tensor::ExtractSliceOp>(op)) {
Value source = extract.getSource();
auto batch = dyn_cast_or_null<SpatComputeBatch>(source.getDefiningOp());
if (batch && batch.getNumResults() != 0) {
if (std::optional<uint32_t> lane = getConstantExtractLane(extract)) {
if (*lane >= static_cast<uint32_t>(batch.getLaneCount()))
return std::nullopt;
return ProducerValueRef {
{batch.getOperation(), *lane, 1},
0
};
}
return getResultfulBatchProducerValueRef(batch, consumerInstance);
}
value = source;
op = value.getDefiningOp();
if (!op)
return std::nullopt;
}
if (auto compute = dyn_cast<SpatCompute>(op)) {
return ProducerValueRef {
ComputeInstance {compute.getOperation(), 0, 1},
static_cast<size_t>(cast<OpResult>(value).getResultNumber())
static_cast<size_t>(cast<OpResult>(value).getResultNumber())
};
}
if (auto batch = dyn_cast<SpatComputeBatch>(op)) {
if (batch.getNumResults() != 0)
return getResultfulBatchProducerValueRef(batch, consumerInstance);
uint32_t lane = cast<OpResult>(value).getResultNumber();
ComputeInstance instance = getBatchChunkForLane(batch, lane);
size_t resultIndex = lane - instance.laneStart;
@@ -69,42 +114,60 @@ std::optional<ProducerValueRef> getProducerValueRef(Value value) {
return std::nullopt;
}
std::optional<ComputeInstance> getComputeProducerInstance(Value value) {
if (std::optional<ProducerValueRef> producer = getProducerValueRef(value))
std::optional<ComputeInstance> getComputeProducerInstance(Value value, const ComputeInstance* consumerInstance) {
if (std::optional<ProducerValueRef> producer = getProducerValueRef(value, consumerInstance))
return producer->instance;
return std::nullopt;
}
llvm::SmallVector<Value, 4> getComputeInstanceInputs(const ComputeInstance &instance) {
llvm::SmallVector<Value, 4> getComputeInstanceInputs(const ComputeInstance& instance) {
if (auto compute = dyn_cast<SpatCompute>(instance.op))
return llvm::SmallVector<Value, 4>(compute.getInputs().begin(), compute.getInputs().end());
auto batch = cast<SpatComputeBatch>(instance.op);
if (batch.getNumResults() != 0)
return llvm::SmallVector<Value, 4>(batch.getInputs().begin(), batch.getInputs().end());
assert(batch.getInputs().size() % static_cast<size_t>(batch.getLaneCount()) == 0
&& "resultless compute_batch inputs must be evenly partitioned by lane");
size_t inputsPerLane = batch.getInputs().size() / static_cast<size_t>(batch.getLaneCount());
llvm::SmallVector<Value, 4> inputs;
inputs.reserve(instance.laneCount);
for (uint32_t lane = instance.laneStart; lane < instance.laneStart + instance.laneCount; ++lane)
if (!batch.getInputs().empty())
inputs.push_back(batch.getInputs()[lane]);
inputs.reserve(instance.laneCount * inputsPerLane);
for (uint32_t lane = instance.laneStart; lane < instance.laneStart + instance.laneCount; ++lane) {
size_t firstInput = static_cast<size_t>(lane) * inputsPerLane;
inputs.append(batch.getInputs().begin() + firstInput, batch.getInputs().begin() + firstInput + inputsPerLane);
}
return inputs;
}
llvm::SmallVector<Value, 4> getComputeInstanceWeights(const ComputeInstance &instance) {
llvm::SmallVector<Value, 4> getComputeInstanceWeights(const ComputeInstance& instance) {
if (auto compute = dyn_cast<SpatCompute>(instance.op))
return llvm::SmallVector<Value, 4>(compute.getWeights().begin(), compute.getWeights().end());
auto batch = cast<SpatComputeBatch>(instance.op);
if (batch.getNumResults() != 0)
return llvm::SmallVector<Value, 4>(batch.getWeights().begin(), batch.getWeights().end());
assert(batch.getWeights().size() % static_cast<size_t>(batch.getLaneCount()) == 0
&& "resultless compute_batch weights must be evenly partitioned by lane");
size_t weightsPerLane = batch.getWeights().size() / static_cast<size_t>(batch.getLaneCount());
llvm::SmallVector<Value, 4> weights;
weights.reserve(instance.laneCount);
for (uint32_t lane = instance.laneStart; lane < instance.laneStart + instance.laneCount; ++lane)
weights.push_back(batch.getWeights()[lane]);
weights.reserve(instance.laneCount * weightsPerLane);
for (uint32_t lane = instance.laneStart; lane < instance.laneStart + instance.laneCount; ++lane) {
size_t firstWeight = static_cast<size_t>(lane) * weightsPerLane;
weights.append(batch.getWeights().begin() + firstWeight, batch.getWeights().begin() + firstWeight + weightsPerLane);
}
return weights;
}
llvm::SmallVector<Value, 4> getComputeInstanceOutputValues(const ComputeInstance &instance) {
llvm::SmallVector<Value, 4> getComputeInstanceOutputValues(const ComputeInstance& instance) {
if (auto compute = dyn_cast<SpatCompute>(instance.op))
return llvm::SmallVector<Value, 4>(compute.getResults().begin(), compute.getResults().end());
auto batch = cast<SpatComputeBatch>(instance.op);
if (batch.getNumResults() != 0)
return llvm::SmallVector<Value, 4>(batch.getResults().begin(), batch.getResults().end());
llvm::SmallVector<Value, 4> outputs;
outputs.reserve(instance.laneCount);
for (uint32_t lane = instance.laneStart; lane < instance.laneStart + instance.laneCount; ++lane)
@@ -113,14 +176,14 @@ llvm::SmallVector<Value, 4> getComputeInstanceOutputValues(const ComputeInstance
return outputs;
}
llvm::SmallVector<Type, 4> getComputeInstanceOutputTypes(const ComputeInstance &instance) {
llvm::SmallVector<Type, 4> getComputeInstanceOutputTypes(const ComputeInstance& instance) {
llvm::SmallVector<Type, 4> outputTypes;
for (Value output : getComputeInstanceOutputValues(instance))
outputTypes.push_back(output.getType());
return outputTypes;
}
Block &getComputeInstanceTemplateBlock(const ComputeInstance &instance) {
Block& getComputeInstanceTemplateBlock(const ComputeInstance& instance) {
if (auto compute = dyn_cast<SpatCompute>(instance.op))
return compute.getBody().front();
return cast<SpatComputeBatch>(instance.op).getBody().front();
@@ -26,8 +26,10 @@ size_t getBatchChunkTargetCount(int32_t laneCount);
ComputeInstance getBatchChunkForIndex(SpatComputeBatch batch, size_t chunkIndex);
ComputeInstance getBatchChunkForLane(SpatComputeBatch batch, uint32_t lane);
std::optional<ProducerValueRef> getProducerValueRef(mlir::Value value);
std::optional<ComputeInstance> getComputeProducerInstance(mlir::Value value);
std::optional<ProducerValueRef> getProducerValueRef(mlir::Value value,
const ComputeInstance *consumerInstance = nullptr);
std::optional<ComputeInstance> getComputeProducerInstance(mlir::Value value,
const ComputeInstance *consumerInstance = nullptr);
llvm::SmallVector<mlir::Value, 4> getComputeInstanceInputs(const ComputeInstance &instance);
llvm::SmallVector<mlir::Value, 4> getComputeInstanceWeights(const ComputeInstance &instance);