Now something work but not trust us (phase 1 + phase 2 of merge)
Validate Operations / validate-operations (push) Has been cancelled
Validate Operations / validate-operations (push) Has been cancelled
This commit is contained in:
@@ -141,7 +141,8 @@ collectTopLevelFragmentAssemblyCopies(OpResult result, RankedTensorType packedRe
|
|||||||
std::optional<StringRef> mode = blueprint.getMode();
|
std::optional<StringRef> mode = blueprint.getMode();
|
||||||
std::optional<ArrayRef<int64_t>> operandIndicesAttr = blueprint.getFragmentOperandIndices();
|
std::optional<ArrayRef<int64_t>> operandIndicesAttr = blueprint.getFragmentOperandIndices();
|
||||||
std::optional<ArrayRef<int64_t>> sourceOffsetsAttr = blueprint.getFragmentSourceOffsets();
|
std::optional<ArrayRef<int64_t>> sourceOffsetsAttr = blueprint.getFragmentSourceOffsets();
|
||||||
if (!mode || *mode != "fragment_assembly" || !operandIndicesAttr || !sourceOffsetsAttr)
|
std::optional<ArrayRef<int64_t>> sourceSlotsAttr = blueprint.getFragmentSourceSlots();
|
||||||
|
if (!mode || *mode != "fragment_assembly" || !operandIndicesAttr || !sourceOffsetsAttr || !sourceSlotsAttr)
|
||||||
return failure();
|
return failure();
|
||||||
if (!blueprint.getOutput().hasOneUse() || !isa<func::ReturnOp>(*blueprint.getOutput().getUsers().begin()))
|
if (!blueprint.getOutput().hasOneUse() || !isa<func::ReturnOp>(*blueprint.getOutput().getUsers().begin()))
|
||||||
return failure();
|
return failure();
|
||||||
@@ -153,6 +154,9 @@ collectTopLevelFragmentAssemblyCopies(OpResult result, RankedTensorType packedRe
|
|||||||
|
|
||||||
ArrayRef<int64_t> operandIndices = *operandIndicesAttr;
|
ArrayRef<int64_t> operandIndices = *operandIndicesAttr;
|
||||||
ArrayRef<int64_t> sourceOffsets = *sourceOffsetsAttr;
|
ArrayRef<int64_t> sourceOffsets = *sourceOffsetsAttr;
|
||||||
|
ArrayRef<int64_t> sourceSlots = *sourceSlotsAttr;
|
||||||
|
if (sourceSlots.size() != operandIndices.size())
|
||||||
|
return failure();
|
||||||
ArrayRef<int64_t> flatOffsets = blueprint.getFragmentOffsets();
|
ArrayRef<int64_t> flatOffsets = blueprint.getFragmentOffsets();
|
||||||
ArrayRef<int64_t> flatSizes = blueprint.getFragmentSizes();
|
ArrayRef<int64_t> flatSizes = blueprint.getFragmentSizes();
|
||||||
ArrayRef<int64_t> flatStrides = *stridesAttr;
|
ArrayRef<int64_t> flatStrides = *stridesAttr;
|
||||||
@@ -174,7 +178,8 @@ collectTopLevelFragmentAssemblyCopies(OpResult result, RankedTensorType packedRe
|
|||||||
if (operandIndices[fragmentIndex] != static_cast<int64_t>(use.getOperandNumber()))
|
if (operandIndices[fragmentIndex] != static_cast<int64_t>(use.getOperandNumber()))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
int64_t sourceElementOffset = sourceOffsets[fragmentIndex];
|
int64_t sourceElementOffset =
|
||||||
|
sourceSlots[fragmentIndex] * payloadElementCount + sourceOffsets[fragmentIndex];
|
||||||
int64_t lane = sourceElementOffset / payloadElementCount;
|
int64_t lane = sourceElementOffset / payloadElementCount;
|
||||||
if (lane < 0 || lane >= static_cast<int64_t>(laneCount))
|
if (lane < 0 || lane >= static_cast<int64_t>(laneCount))
|
||||||
return failure();
|
return failure();
|
||||||
|
|||||||
@@ -204,15 +204,32 @@ analyzeCopyRewrite(Value target, Value source, Value targetOffset, Value sourceO
|
|||||||
if (!targetType || !sourceType || size <= 0)
|
if (!targetType || !sourceType || size <= 0)
|
||||||
return failure();
|
return failure();
|
||||||
|
|
||||||
auto logicalCopyShape = inferLogicalCopyShape(targetType, sourceType, size);
|
|
||||||
if (failed(logicalCopyShape))
|
|
||||||
return failure();
|
|
||||||
|
|
||||||
auto targetPlan = analyzeCopyEndpoint(target, targetOffset, targetType);
|
auto targetPlan = analyzeCopyEndpoint(target, targetOffset, targetType);
|
||||||
auto sourcePlan = analyzeCopyEndpoint(source, sourceOffset, sourceType);
|
auto sourcePlan = analyzeCopyEndpoint(source, sourceOffset, sourceType);
|
||||||
if (failed(targetPlan) || failed(sourcePlan))
|
if (failed(targetPlan) || failed(sourcePlan))
|
||||||
return failure();
|
return failure();
|
||||||
|
|
||||||
|
auto targetBytes = getShapedByteSize(targetType);
|
||||||
|
auto sourceBytes = getShapedByteSize(sourceType);
|
||||||
|
if (targetType.getElementType() == sourceType.getElementType() && succeeded(targetBytes) && succeeded(sourceBytes)
|
||||||
|
&& *targetBytes == size && *sourceBytes == size) {
|
||||||
|
auto targetSuffixRank = getContiguousSuffixRank(targetType, targetType.getShape());
|
||||||
|
auto sourceSuffixRank = getContiguousSuffixRank(sourceType, sourceType.getShape());
|
||||||
|
if (succeeded(targetSuffixRank) && succeeded(sourceSuffixRank)
|
||||||
|
&& *targetSuffixRank == targetType.getRank() && *sourceSuffixRank == sourceType.getRank()) {
|
||||||
|
CopyRewritePlan plan;
|
||||||
|
plan.kind = CopyRewritePlan::Kind::Direct;
|
||||||
|
plan.target = *targetPlan;
|
||||||
|
plan.source = *sourcePlan;
|
||||||
|
plan.directBytes = size;
|
||||||
|
return plan;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto logicalCopyShape = inferLogicalCopyShape(targetType, sourceType, size);
|
||||||
|
if (failed(logicalCopyShape))
|
||||||
|
return failure();
|
||||||
|
|
||||||
auto targetSuffixRank = getContiguousSuffixRank(targetType, *logicalCopyShape);
|
auto targetSuffixRank = getContiguousSuffixRank(targetType, *logicalCopyShape);
|
||||||
auto sourceSuffixRank = getContiguousSuffixRank(sourceType, *logicalCopyShape);
|
auto sourceSuffixRank = getContiguousSuffixRank(sourceType, *logicalCopyShape);
|
||||||
if (failed(targetSuffixRank) || failed(sourceSuffixRank))
|
if (failed(targetSuffixRank) || failed(sourceSuffixRank))
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ add_pim_library(SpatialOps
|
|||||||
Transforms/MergeComputeNodes/Scheduling/ComputeGraph.cpp
|
Transforms/MergeComputeNodes/Scheduling/ComputeGraph.cpp
|
||||||
Transforms/MergeComputeNodes/Scheduling/ComputeInstanceUtils.cpp
|
Transforms/MergeComputeNodes/Scheduling/ComputeInstanceUtils.cpp
|
||||||
Transforms/MergeComputeNodes/DeferredCommunicationPlanning.cpp
|
Transforms/MergeComputeNodes/DeferredCommunicationPlanning.cpp
|
||||||
|
Transforms/MergeComputeNodes/DeferredProjectionAnalysis.cpp
|
||||||
Transforms/MergeComputeNodes/DeferredCommunicationDeadlock.cpp
|
Transforms/MergeComputeNodes/DeferredCommunicationDeadlock.cpp
|
||||||
Transforms/MergeComputeNodes/DeferredCommunicationRealization.cpp
|
Transforms/MergeComputeNodes/DeferredCommunicationRealization.cpp
|
||||||
Transforms/MergeComputeNodes/MergeComputeNodesPass.cpp
|
Transforms/MergeComputeNodes/MergeComputeNodesPass.cpp
|
||||||
|
|||||||
+221
-9
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
#include "mlir/Dialect/Affine/IR/AffineOps.h"
|
#include "mlir/Dialect/Affine/IR/AffineOps.h"
|
||||||
#include "mlir/Dialect/Arith/IR/Arith.h"
|
#include "mlir/Dialect/Arith/IR/Arith.h"
|
||||||
|
#include "mlir/Dialect/SCF/IR/SCF.h"
|
||||||
#include "mlir/Dialect/Tensor/IR/Tensor.h"
|
#include "mlir/Dialect/Tensor/IR/Tensor.h"
|
||||||
#include "mlir/IR/IRMapping.h"
|
#include "mlir/IR/IRMapping.h"
|
||||||
|
|
||||||
@@ -24,6 +25,93 @@ static FailureOr<Value> getOriginalProducerValue(const ProducerValueRef &produce
|
|||||||
return outputs[producer.resultIndex];
|
return outputs[producer.resultIndex];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static SmallVector<Value> getBlueprintFragments(SpatBlueprintOp blueprint) {
|
||||||
|
SmallVector<Value> fragments {blueprint.getInput()};
|
||||||
|
llvm::append_range(fragments, blueprint.getFragments());
|
||||||
|
return fragments;
|
||||||
|
}
|
||||||
|
|
||||||
|
static FailureOr<Value> buildBlueprintReconstruction(
|
||||||
|
OpBuilder &builder, Location loc, SpatBlueprintOp blueprint,
|
||||||
|
ValueRange sourceBlockArgs) {
|
||||||
|
auto resultType = dyn_cast<RankedTensorType>(blueprint.getOutput().getType());
|
||||||
|
auto operandIndices = blueprint.getFragmentOperandIndices();
|
||||||
|
auto sourceSlots = blueprint.getFragmentSourceSlots();
|
||||||
|
auto sourceOffsets = blueprint.getFragmentSourceOffsets();
|
||||||
|
auto strides = blueprint.getFragmentStrides();
|
||||||
|
if (!resultType || !resultType.hasStaticShape() || !operandIndices ||
|
||||||
|
!sourceSlots || !sourceOffsets || !strides)
|
||||||
|
return blueprint.emitOpError("phase 1 requires complete static fragment assembly metadata"), failure();
|
||||||
|
int64_t rank = resultType.getRank();
|
||||||
|
ArrayRef<int64_t> offsets = blueprint.getFragmentOffsets();
|
||||||
|
ArrayRef<int64_t> sizes = blueprint.getFragmentSizes();
|
||||||
|
if (offsets.size() != sizes.size() || offsets.size() != strides->size() ||
|
||||||
|
offsets.size() != operandIndices->size() * rank ||
|
||||||
|
sourceSlots->size() != operandIndices->size() ||
|
||||||
|
sourceOffsets->size() != operandIndices->size())
|
||||||
|
return blueprint.emitOpError("phase 1 fragment assembly metadata has inconsistent sizes"), failure();
|
||||||
|
|
||||||
|
Value result = tensor::EmptyOp::create(builder, loc, resultType.getShape(),
|
||||||
|
resultType.getElementType());
|
||||||
|
for (auto [fragmentIndex, operandIndex] : llvm::enumerate(*operandIndices)) {
|
||||||
|
if (operandIndex < 0 || operandIndex >= static_cast<int64_t>(sourceBlockArgs.size()))
|
||||||
|
return blueprint.emitOpError("phase 1 fragment assembly operand index is out of range"), failure();
|
||||||
|
auto physicalType = dyn_cast<RankedTensorType>(sourceBlockArgs[operandIndex].getType());
|
||||||
|
if (!physicalType || !physicalType.hasStaticShape() || physicalType.getRank() != rank + 1)
|
||||||
|
return blueprint.emitOpError("phase 1 fragment assembly source is not a physical fragment batch"), failure();
|
||||||
|
SmallVector<int64_t> fragmentShape(physicalType.getShape().drop_front());
|
||||||
|
int64_t linearOffset = (*sourceOffsets)[fragmentIndex];
|
||||||
|
SmallVector<int64_t> sourceCoordinates(rank);
|
||||||
|
for (int64_t dim = rank - 1; dim >= 0; --dim) {
|
||||||
|
sourceCoordinates[dim] = linearOffset % fragmentShape[dim];
|
||||||
|
linearOffset /= fragmentShape[dim];
|
||||||
|
}
|
||||||
|
if (linearOffset != 0)
|
||||||
|
return blueprint.emitOpError("phase 1 fragment source offset is out of range"), failure();
|
||||||
|
|
||||||
|
SmallVector<OpFoldResult> sliceOffsets, sliceSizes, sliceStrides;
|
||||||
|
sliceOffsets.push_back(builder.getIndexAttr((*sourceSlots)[fragmentIndex]));
|
||||||
|
sliceSizes.push_back(builder.getIndexAttr(1));
|
||||||
|
sliceStrides.push_back(builder.getIndexAttr(1));
|
||||||
|
SmallVector<int64_t> selectedShape {1};
|
||||||
|
for (int64_t dim = 0; dim < rank; ++dim) {
|
||||||
|
int64_t index = fragmentIndex * rank + dim;
|
||||||
|
int64_t size = sizes[index];
|
||||||
|
if ((*strides)[index] != 1 || sourceCoordinates[dim] < 0 || size <= 0 ||
|
||||||
|
sourceCoordinates[dim] + size > fragmentShape[dim])
|
||||||
|
return blueprint.emitOpError("phase 1 fragment geometry is unsupported"), failure();
|
||||||
|
sliceOffsets.push_back(builder.getIndexAttr(sourceCoordinates[dim]));
|
||||||
|
sliceSizes.push_back(builder.getIndexAttr(size));
|
||||||
|
sliceStrides.push_back(builder.getIndexAttr(1));
|
||||||
|
selectedShape.push_back(size);
|
||||||
|
}
|
||||||
|
auto selectedType = RankedTensorType::get(selectedShape, resultType.getElementType());
|
||||||
|
Value selected = tensor::ExtractSliceOp::create(
|
||||||
|
builder, loc, selectedType, sourceBlockArgs[operandIndex], sliceOffsets,
|
||||||
|
sliceSizes, sliceStrides);
|
||||||
|
SmallVector<int64_t> fragmentResultShape(selectedShape.begin() + 1,
|
||||||
|
selectedShape.end());
|
||||||
|
auto fragmentType = RankedTensorType::get(fragmentResultShape,
|
||||||
|
resultType.getElementType());
|
||||||
|
SmallVector<ReassociationIndices> reassociation {{0, 1}};
|
||||||
|
for (int64_t dim = 1; dim < rank; ++dim)
|
||||||
|
reassociation.push_back({dim + 1});
|
||||||
|
Value fragment = tensor::CollapseShapeOp::create(
|
||||||
|
builder, loc, fragmentType, selected, reassociation);
|
||||||
|
SmallVector<OpFoldResult> targetOffsets, targetSizes, targetStrides;
|
||||||
|
for (int64_t dim = 0; dim < rank; ++dim) {
|
||||||
|
int64_t index = fragmentIndex * rank + dim;
|
||||||
|
targetOffsets.push_back(builder.getIndexAttr(offsets[index]));
|
||||||
|
targetSizes.push_back(builder.getIndexAttr(sizes[index]));
|
||||||
|
targetStrides.push_back(builder.getIndexAttr((*strides)[index]));
|
||||||
|
}
|
||||||
|
result = tensor::InsertSliceOp::create(builder, loc, fragment, result,
|
||||||
|
targetOffsets, targetSizes,
|
||||||
|
targetStrides);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
static bool isSupportedDeferredShapingOp(Operation *op) {
|
static bool isSupportedDeferredShapingOp(Operation *op) {
|
||||||
return isa<tensor::ExtractSliceOp, tensor::InsertSliceOp, tensor::CollapseShapeOp,
|
return isa<tensor::ExtractSliceOp, tensor::InsertSliceOp, tensor::CollapseShapeOp,
|
||||||
tensor::ExpandShapeOp, tensor::CastOp, tensor::EmptyOp, tensor::ExtractOp,
|
tensor::ExpandShapeOp, tensor::CastOp, tensor::EmptyOp, tensor::ExtractOp,
|
||||||
@@ -99,7 +187,7 @@ static bool isEligible(Value value, Block &body, const DeferredInputPlan &plan,
|
|||||||
|
|
||||||
static FailureOr<Value> clonePayloadRoot(Value root, Block &body, const DeferredInputPlan &plan,
|
static FailureOr<Value> clonePayloadRoot(Value root, Block &body, const DeferredInputPlan &plan,
|
||||||
OpBuilder &builder, SpatDeferredCommunicationOp transfer,
|
OpBuilder &builder, SpatDeferredCommunicationOp transfer,
|
||||||
Value selectedSource) {
|
Value selectedSource, Value boundGraphLane) {
|
||||||
IRMapping mapping;
|
IRMapping mapping;
|
||||||
mapping.map(plan.graphInput, selectedSource);
|
mapping.map(plan.graphInput, selectedSource);
|
||||||
std::function<FailureOr<Value>(Value)> cloneScheduledLane = [&](Value value) -> FailureOr<Value> {
|
std::function<FailureOr<Value>(Value)> cloneScheduledLane = [&](Value value) -> FailureOr<Value> {
|
||||||
@@ -118,7 +206,7 @@ static FailureOr<Value> clonePayloadRoot(Value root, Block &body, const Deferred
|
|||||||
std::function<FailureOr<Value>(Value)> clone = [&](Value value) -> FailureOr<Value> {
|
std::function<FailureOr<Value>(Value)> clone = [&](Value value) -> FailureOr<Value> {
|
||||||
if (mapping.contains(value)) return mapping.lookup(value);
|
if (mapping.contains(value)) return mapping.lookup(value);
|
||||||
if (value == plan.graphLane) {
|
if (value == plan.graphLane) {
|
||||||
auto mappedLane = cloneScheduledLane(plan.scheduledGraphLane);
|
auto mappedLane = cloneScheduledLane(boundGraphLane ? boundGraphLane : plan.scheduledGraphLane);
|
||||||
if (failed(mappedLane)) return failure();
|
if (failed(mappedLane)) return failure();
|
||||||
mapping.map(value, *mappedLane);
|
mapping.map(value, *mappedLane);
|
||||||
return *mappedLane;
|
return *mappedLane;
|
||||||
@@ -136,6 +224,62 @@ static FailureOr<Value> clonePayloadRoot(Value root, Block &body, const Deferred
|
|||||||
return clone(root);
|
return clone(root);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool dependsOnGraphLane(Value value, Value graphLane, Block &body,
|
||||||
|
llvm::SmallPtrSetImpl<Operation *> &seen) {
|
||||||
|
if (value == graphLane)
|
||||||
|
return true;
|
||||||
|
Operation *op = value.getDefiningOp();
|
||||||
|
if (!op || !isTopLevelShaping(op, body) || !seen.insert(op).second)
|
||||||
|
return false;
|
||||||
|
return llvm::any_of(op->getOperands(), [&](Value operand) {
|
||||||
|
return dependsOnGraphLane(operand, graphLane, body, seen);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static FailureOr<Value> buildPayloadAggregate(OpBuilder &builder, Location loc,
|
||||||
|
ArrayRef<Value> payloads) {
|
||||||
|
auto payloadType = dyn_cast<RankedTensorType>(payloads.front().getType());
|
||||||
|
if (!payloadType || !payloadType.hasStaticShape())
|
||||||
|
return failure();
|
||||||
|
SmallVector<int64_t> shape {static_cast<int64_t>(payloads.size())};
|
||||||
|
llvm::append_range(shape, payloadType.getShape());
|
||||||
|
auto aggregateType = RankedTensorType::get(shape, payloadType.getElementType());
|
||||||
|
auto empty = createEmptyTensorForType(builder, loc, aggregateType);
|
||||||
|
if (failed(empty)) return failure();
|
||||||
|
Value aggregate = *empty;
|
||||||
|
SmallVector<OpFoldResult> sizes, strides(shape.size(), builder.getIndexAttr(1));
|
||||||
|
sizes.push_back(builder.getIndexAttr(1));
|
||||||
|
for (int64_t dim : payloadType.getShape()) sizes.push_back(builder.getIndexAttr(dim));
|
||||||
|
SmallVector<ReassociationIndices> reassociation {{0, 1}};
|
||||||
|
for (int64_t dim = 1; dim < payloadType.getRank(); ++dim) reassociation.push_back({dim + 1});
|
||||||
|
SmallVector<int64_t> expandedShape {1}; llvm::append_range(expandedShape, payloadType.getShape());
|
||||||
|
auto expandedType = RankedTensorType::get(expandedShape, payloadType.getElementType());
|
||||||
|
for (auto [index, payload] : llvm::enumerate(payloads)) {
|
||||||
|
Value expanded = tensor::ExpandShapeOp::create(builder, loc, expandedType, payload, reassociation).getResult();
|
||||||
|
SmallVector<OpFoldResult> offsets(shape.size(), builder.getIndexAttr(0));
|
||||||
|
offsets[0] = builder.getIndexAttr(index);
|
||||||
|
aggregate = tensor::InsertSliceOp::create(builder, loc, expanded, aggregate, offsets, sizes, strides).getResult();
|
||||||
|
}
|
||||||
|
return aggregate;
|
||||||
|
}
|
||||||
|
|
||||||
|
static FailureOr<Value> selectPayloadAggregate(OpBuilder &builder, Location loc, Value aggregate,
|
||||||
|
Value localLane) {
|
||||||
|
auto aggregateType = cast<RankedTensorType>(aggregate.getType());
|
||||||
|
SmallVector<int64_t> payloadShape(aggregateType.getShape().begin() + 1, aggregateType.getShape().end());
|
||||||
|
auto payloadType = RankedTensorType::get(payloadShape, aggregateType.getElementType());
|
||||||
|
SmallVector<OpFoldResult> offsets(aggregateType.getRank(), builder.getIndexAttr(0)); offsets[0] = localLane;
|
||||||
|
SmallVector<OpFoldResult> sizes, strides(aggregateType.getRank(), builder.getIndexAttr(1));
|
||||||
|
sizes.push_back(builder.getIndexAttr(1));
|
||||||
|
for (int64_t dim : payloadShape) sizes.push_back(builder.getIndexAttr(dim));
|
||||||
|
SmallVector<int64_t> unitShape {1}; llvm::append_range(unitShape, payloadShape);
|
||||||
|
Value unit = tensor::ExtractSliceOp::create(builder, loc,
|
||||||
|
RankedTensorType::get(unitShape, aggregateType.getElementType()), aggregate, offsets, sizes, strides).getResult();
|
||||||
|
SmallVector<ReassociationIndices> reassociation {{0, 1}};
|
||||||
|
for (int64_t dim = 1; dim < payloadType.getRank(); ++dim) reassociation.push_back({dim + 1});
|
||||||
|
return tensor::CollapseShapeOp::create(builder, loc, payloadType, unit, reassociation).getResult();
|
||||||
|
}
|
||||||
|
|
||||||
static void collectClosure(Value value, Block &body, const DeferredInputPlan &plan,
|
static void collectClosure(Value value, Block &body, const DeferredInputPlan &plan,
|
||||||
llvm::SmallPtrSetImpl<Operation *> &ops) {
|
llvm::SmallPtrSetImpl<Operation *> &ops) {
|
||||||
Operation *op = value.getDefiningOp();
|
Operation *op = value.getDefiningOp();
|
||||||
@@ -146,12 +290,27 @@ static void collectClosure(Value value, Block &body, const DeferredInputPlan &pl
|
|||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
|
bool isDeferredFragmentAssemblyInput(
|
||||||
|
Value input, const ComputeInstance &consumerInstance) {
|
||||||
|
auto blueprint = input.getDefiningOp<SpatBlueprintOp>();
|
||||||
|
if (!blueprint || blueprint.getMode() != "fragment_assembly")
|
||||||
|
return false;
|
||||||
|
return llvm::all_of(getBlueprintFragments(blueprint), [&](Value fragment) {
|
||||||
|
return getProducerValueRef(fragment, &consumerInstance).has_value();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
LogicalResult prepareSingleCpuInput(OpBuilder &, Location loc, Value input, BlockArgument graphInput,
|
LogicalResult prepareSingleCpuInput(OpBuilder &, Location loc, Value input, BlockArgument graphInput,
|
||||||
const ComputeInstance &consumerInstance, const MergeScheduleResult &,
|
const ComputeInstance &consumerInstance, const MergeScheduleResult &,
|
||||||
ValueRange scheduledInputs, Block &block, unsigned firstInputArgument,
|
ValueRange scheduledInputs, Block &block, unsigned firstInputArgument,
|
||||||
ArrayRef<ProducerValueKey> carriedKeys, Value graphLane, Value scheduledGraphLane,
|
ArrayRef<ProducerValueKey> carriedKeys, Value graphLane, Value scheduledGraphLane,
|
||||||
DeferredInputPlan &plan) {
|
DeferredInputPlan &plan) {
|
||||||
plan = {graphInput, {}, {}, {}, graphLane, scheduledGraphLane, {}};
|
plan = {graphInput, {}, {}, {}, graphLane, scheduledGraphLane, {}, {}, {}, {}, 1, nullptr};
|
||||||
|
if (isDeferredFragmentAssemblyInput(input, consumerInstance)) {
|
||||||
|
plan.blueprint = input.getDefiningOp<SpatBlueprintOp>();
|
||||||
|
plan.originalSources = getBlueprintFragments(plan.blueprint);
|
||||||
|
return success();
|
||||||
|
}
|
||||||
auto producer = getProducerValueRef(input, &consumerInstance);
|
auto producer = getProducerValueRef(input, &consumerInstance);
|
||||||
if (!producer) { plan.availableValue = getBlockOperand(block, scheduledInputs, input, firstInputArgument); return success(); }
|
if (!producer) { plan.availableValue = getBlockOperand(block, scheduledInputs, input, firstInputArgument); return success(); }
|
||||||
ProducerValueKey key {producer->instance, producer->resultIndex};
|
ProducerValueKey key {producer->instance, producer->resultIndex};
|
||||||
@@ -171,8 +330,13 @@ LogicalResult prepareMultiCpuTupleInput(OpBuilder &, Location loc, Value input,
|
|||||||
const MergeScheduleResult &, ValueRange scheduledInputs, Block &block,
|
const MergeScheduleResult &, ValueRange scheduledInputs, Block &block,
|
||||||
unsigned firstInputArgument, Value graphLane, Value scheduledGraphLane, Value scheduledLane,
|
unsigned firstInputArgument, Value graphLane, Value scheduledGraphLane, Value scheduledLane,
|
||||||
DeferredInputPlan &plan) {
|
DeferredInputPlan &plan) {
|
||||||
plan = {graphInput, {}, {}, {}, graphLane, scheduledGraphLane, scheduledLane};
|
|
||||||
const ComputeInstance &representative = tuple.instances.front();
|
const ComputeInstance &representative = tuple.instances.front();
|
||||||
|
plan = {graphInput, {}, {}, {}, graphLane, scheduledGraphLane, scheduledLane, {}, {}, {}, 1, nullptr};
|
||||||
|
if (isDeferredFragmentAssemblyInput(input, representative)) {
|
||||||
|
plan.blueprint = input.getDefiningOp<SpatBlueprintOp>();
|
||||||
|
plan.originalSources = getBlueprintFragments(plan.blueprint);
|
||||||
|
return success();
|
||||||
|
}
|
||||||
auto producer = getProducerValueRef(input, &representative);
|
auto producer = getProducerValueRef(input, &representative);
|
||||||
if (!producer) { plan.availableValue = getBlockOperand(block, scheduledInputs, input, firstInputArgument); return success(); }
|
if (!producer) { plan.availableValue = getBlockOperand(block, scheduledInputs, input, firstInputArgument); return success(); }
|
||||||
auto inputs = getComputeInstanceInputs(representative);
|
auto inputs = getComputeInstanceInputs(representative);
|
||||||
@@ -222,19 +386,67 @@ LogicalResult materializeDeferredPayloadDemands(OpBuilder &builder, Location loc
|
|||||||
llvm::sort(roots, [](Value a, Value b) { return a.getAsOpaquePointer() < b.getAsOpaquePointer(); });
|
llvm::sort(roots, [](Value a, Value b) { return a.getAsOpaquePointer() < b.getAsOpaquePointer(); });
|
||||||
roots.erase(std::unique(roots.begin(), roots.end()), roots.end());
|
roots.erase(std::unique(roots.begin(), roots.end()), roots.end());
|
||||||
for (Value root : roots) {
|
for (Value root : roots) {
|
||||||
|
llvm::SmallPtrSet<Operation *, 16> laneDependencies;
|
||||||
|
bool scalarize = plan.scalarizedGraphLaneBase
|
||||||
|
&& dependsOnGraphLane(root, plan.graphLane, body, laneDependencies);
|
||||||
|
OpBuilder::InsertPoint restore = builder.saveInsertionPoint();
|
||||||
|
Operation *loop = nullptr;
|
||||||
|
if (scalarize) {
|
||||||
|
loop = builder.getInsertionBlock()->getParentOp();
|
||||||
|
if (loop && !isa<scf::ForOp>(loop))
|
||||||
|
loop = loop->getParentOfType<scf::ForOp>();
|
||||||
|
if (loop)
|
||||||
|
builder.setInsertionPoint(loop);
|
||||||
|
else if (plan.scalarizedHoistBlock)
|
||||||
|
builder.setInsertionPointToEnd(plan.scalarizedHoistBlock);
|
||||||
|
else
|
||||||
|
return emitError(loc) << "phase 1 scalarized deferred payload is missing a hoist point";
|
||||||
|
}
|
||||||
|
SmallVector<Value> payloads;
|
||||||
|
unsigned count = scalarize ? plan.scalarizedLaneCount : 1;
|
||||||
|
for (unsigned offset = 0; offset < count; ++offset) {
|
||||||
auto transfer = SpatDeferredCommunicationOp::create(builder, loc, root.getType(), plan.originalSources);
|
auto transfer = SpatDeferredCommunicationOp::create(builder, loc, root.getType(), plan.originalSources);
|
||||||
Block *deferred = builder.createBlock(&transfer.getBody(), transfer.getBody().end(),
|
Block *deferred = builder.createBlock(&transfer.getBody(), transfer.getBody().end(),
|
||||||
TypeRange {transfer.getSources().getTypes()}, SmallVector<Location>(transfer.getSources().size(), loc));
|
TypeRange {transfer.getSources().getTypes()}, SmallVector<Location>(transfer.getSources().size(), loc));
|
||||||
builder.setInsertionPointToStart(deferred);
|
builder.setInsertionPointToStart(deferred);
|
||||||
auto selected = buildSelectedDeferredSource(builder, loc, transfer, plan.scheduledLane,
|
auto selected = plan.blueprint
|
||||||
deferred->getArguments(), plan.sourceOperandForScheduledLane);
|
? buildBlueprintReconstruction(builder, loc, plan.blueprint,
|
||||||
|
deferred->getArguments())
|
||||||
|
: buildSelectedDeferredSource(builder, loc, transfer,
|
||||||
|
plan.scheduledLane,
|
||||||
|
deferred->getArguments(),
|
||||||
|
plan.sourceOperandForScheduledLane);
|
||||||
if (failed(selected)) return failure();
|
if (failed(selected)) return failure();
|
||||||
auto payload = clonePayloadRoot(root, body, plan, builder, transfer, *selected);
|
Value boundGraphLane;
|
||||||
|
if (scalarize) {
|
||||||
|
Value offsetValue = arith::ConstantIndexOp::create(builder, loc, offset);
|
||||||
|
boundGraphLane = offset ? arith::AddIOp::create(builder, loc, plan.scalarizedGraphLaneBase, offsetValue).getResult()
|
||||||
|
: plan.scalarizedGraphLaneBase;
|
||||||
|
}
|
||||||
|
auto payload = clonePayloadRoot(root, body, plan, builder, transfer, *selected, boundGraphLane);
|
||||||
if (failed(payload)) return failure();
|
if (failed(payload)) return failure();
|
||||||
SpatYieldOp::create(builder, loc, *payload);
|
SpatYieldOp::create(builder, loc, *payload);
|
||||||
mapper.map(root, transfer.getOutput());
|
payloads.push_back(transfer.getOutput());
|
||||||
collectClosure(root, body, plan, absorbed);
|
|
||||||
builder.setInsertionPointAfter(transfer);
|
builder.setInsertionPointAfter(transfer);
|
||||||
|
}
|
||||||
|
if (scalarize) {
|
||||||
|
builder.restoreInsertionPoint(restore);
|
||||||
|
if (payloads.size() == 1) {
|
||||||
|
mapper.map(root, payloads.front());
|
||||||
|
} else {
|
||||||
|
if (loop) builder.setInsertionPoint(loop);
|
||||||
|
else builder.setInsertionPointToEnd(plan.scalarizedHoistBlock);
|
||||||
|
auto aggregate = buildPayloadAggregate(builder, loc, payloads);
|
||||||
|
if (failed(aggregate)) return failure();
|
||||||
|
builder.restoreInsertionPoint(restore);
|
||||||
|
auto selected = selectPayloadAggregate(builder, loc, *aggregate, plan.scalarizedLocalLane);
|
||||||
|
if (failed(selected)) return failure();
|
||||||
|
mapper.map(root, *selected);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
mapper.map(root, payloads.front());
|
||||||
|
}
|
||||||
|
collectClosure(root, body, plan, absorbed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (Operation *op : absorbed) {
|
for (Operation *op : absorbed) {
|
||||||
|
|||||||
@@ -15,8 +15,16 @@ struct DeferredInputPlan {
|
|||||||
Value graphLane;
|
Value graphLane;
|
||||||
Value scheduledGraphLane;
|
Value scheduledGraphLane;
|
||||||
Value scheduledLane;
|
Value scheduledLane;
|
||||||
|
SpatBlueprintOp blueprint;
|
||||||
|
Value scalarizedLocalLane;
|
||||||
|
Value scalarizedGraphLaneBase;
|
||||||
|
int64_t scalarizedLaneCount = 1;
|
||||||
|
Block *scalarizedHoistBlock = nullptr;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
bool isDeferredFragmentAssemblyInput(Value input,
|
||||||
|
const ComputeInstance &consumerInstance);
|
||||||
|
|
||||||
LogicalResult prepareSingleCpuInput(OpBuilder &builder, Location loc, Value input,
|
LogicalResult prepareSingleCpuInput(OpBuilder &builder, Location loc, Value input,
|
||||||
BlockArgument graphInput,
|
BlockArgument graphInput,
|
||||||
const ComputeInstance &consumerInstance,
|
const ComputeInstance &consumerInstance,
|
||||||
|
|||||||
+567
-697
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,287 @@
|
|||||||
|
#include "DeferredProjectionAnalysis.hpp"
|
||||||
|
|
||||||
|
#include "mlir/Dialect/Affine/IR/AffineOps.h"
|
||||||
|
#include "mlir/Dialect/Arith/IR/Arith.h"
|
||||||
|
#include "mlir/IR/Matchers.h"
|
||||||
|
#include "src/Accelerators/PIM/Common/IR/AffineUtils.hpp"
|
||||||
|
|
||||||
|
#include <limits>
|
||||||
|
|
||||||
|
namespace onnx_mlir::spatial {
|
||||||
|
using namespace mlir;
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
static FailureOr<int64_t> evaluate(Value value, const StaticIndexEnvironment &environment,
|
||||||
|
llvm::SmallDenseSet<Value, 16> &visiting) {
|
||||||
|
if (auto it = environment.bindings.find(value); it != environment.bindings.end())
|
||||||
|
return it->second;
|
||||||
|
if (!visiting.insert(value).second)
|
||||||
|
return failure();
|
||||||
|
if (auto constant = value.getDefiningOp<arith::ConstantOp>())
|
||||||
|
if (auto integer = dyn_cast<IntegerAttr>(constant.getValue()))
|
||||||
|
{ visiting.erase(value); return integer.getInt(); }
|
||||||
|
if (auto cast = value.getDefiningOp<arith::IndexCastOp>()) {
|
||||||
|
auto result = evaluate(cast.getIn(), environment, visiting); visiting.erase(value); return result;
|
||||||
|
}
|
||||||
|
auto binary = [&](auto op, auto fn) -> FailureOr<int64_t> {
|
||||||
|
auto lhs = evaluate(op.getLhs(), environment, visiting);
|
||||||
|
auto rhs = evaluate(op.getRhs(), environment, visiting);
|
||||||
|
if (failed(lhs) || failed(rhs)) return failure();
|
||||||
|
return fn(*lhs, *rhs);
|
||||||
|
};
|
||||||
|
if (auto op = value.getDefiningOp<arith::AddIOp>()) { auto result = binary(op, [](int64_t a, int64_t b) -> FailureOr<int64_t> { int64_t r; if (llvm::AddOverflow(a, b, r)) return failure(); return r; }); visiting.erase(value); return result; }
|
||||||
|
if (auto op = value.getDefiningOp<arith::SubIOp>()) { auto result = binary(op, [](int64_t a, int64_t b) -> FailureOr<int64_t> { int64_t r; if (llvm::SubOverflow(a, b, r)) return failure(); return r; }); visiting.erase(value); return result; }
|
||||||
|
if (auto op = value.getDefiningOp<arith::MulIOp>()) { auto result = binary(op, [](int64_t a, int64_t b) -> FailureOr<int64_t> { int64_t r; if (llvm::MulOverflow(a, b, r)) return failure(); return r; }); visiting.erase(value); return result; }
|
||||||
|
if (auto apply = value.getDefiningOp<affine::AffineApplyOp>()) { auto result = evaluateAffineApply(apply, [&](Value operand) { return evaluate(operand, environment, visiting); }); visiting.erase(value); return result; }
|
||||||
|
if (auto extract = value.getDefiningOp<tensor::ExtractOp>()) {
|
||||||
|
auto constant = extract.getTensor().getDefiningOp<arith::ConstantOp>();
|
||||||
|
auto elements = constant ? dyn_cast<DenseIntElementsAttr>(constant.getValue()) : DenseIntElementsAttr();
|
||||||
|
auto type = elements ? dyn_cast<RankedTensorType>(elements.getType()) : RankedTensorType();
|
||||||
|
if (!elements || !type || extract.getIndices().size() != static_cast<size_t>(type.getRank())) return failure();
|
||||||
|
int64_t linear = 0;
|
||||||
|
for (auto [index, dim] : llvm::zip(extract.getIndices(), type.getShape())) {
|
||||||
|
auto i = evaluate(index, environment, visiting);
|
||||||
|
if (failed(i) || *i < 0 || *i >= dim) return failure();
|
||||||
|
linear = linear * dim + *i;
|
||||||
|
}
|
||||||
|
visiting.erase(value); return elements.getValues<APInt>()[linear].getSExtValue();
|
||||||
|
}
|
||||||
|
visiting.erase(value);
|
||||||
|
return failure();
|
||||||
|
}
|
||||||
|
|
||||||
|
static FailureOr<std::optional<unsigned>> sourceArgument(Value value, SpatDeferredCommunicationOp deferred,
|
||||||
|
const StaticIndexEnvironment &environment) {
|
||||||
|
while (auto cast = value.getDefiningOp<tensor::CastOp>()) value = cast.getSource();
|
||||||
|
if (auto argument = dyn_cast<BlockArgument>(value);
|
||||||
|
argument && argument.getOwner() == &deferred.getBody().front()
|
||||||
|
&& argument.getArgNumber() < deferred.getSources().size())
|
||||||
|
return std::optional<unsigned>(argument.getArgNumber());
|
||||||
|
// Phase 1's selector ends in collapse(extract_slice(stacked, table[lane])).
|
||||||
|
auto collapse = value.getDefiningOp<tensor::CollapseShapeOp>();
|
||||||
|
if (!collapse) return std::optional<unsigned>();
|
||||||
|
value = collapse.getSrc();
|
||||||
|
auto slice = value.getDefiningOp<tensor::ExtractSliceOp>();
|
||||||
|
if (!slice) return std::optional<unsigned>();
|
||||||
|
auto sourceType = dyn_cast<RankedTensorType>(slice.getSourceType());
|
||||||
|
if (!sourceType || slice.getMixedOffsets().size() != static_cast<size_t>(sourceType.getRank()))
|
||||||
|
return std::optional<unsigned>();
|
||||||
|
for (unsigned dim = 1; dim < sourceType.getRank(); ++dim) {
|
||||||
|
auto offset = evaluateDeferredIndex(slice.getMixedOffsets()[dim], environment);
|
||||||
|
auto size = evaluateDeferredIndex(slice.getMixedSizes()[dim], environment);
|
||||||
|
auto stride = evaluateDeferredIndex(slice.getMixedStrides()[dim], environment);
|
||||||
|
if (failed(offset) || failed(size) || failed(stride) || *offset != 0 || *size != sourceType.getDimSize(dim) || *stride != 1)
|
||||||
|
return std::optional<unsigned>();
|
||||||
|
}
|
||||||
|
auto leadingSize = evaluateDeferredIndex(slice.getMixedSizes().front(), environment);
|
||||||
|
auto leadingStride = evaluateDeferredIndex(slice.getMixedStrides().front(), environment);
|
||||||
|
if (failed(leadingSize) || failed(leadingStride) || *leadingSize != 1 || *leadingStride != 1)
|
||||||
|
return std::optional<unsigned>();
|
||||||
|
auto selected = evaluateDeferredIndex(slice.getMixedOffsets().front(), environment);
|
||||||
|
if (failed(selected)) return failure();
|
||||||
|
Value stacked = slice.getSource();
|
||||||
|
while (auto cast = stacked.getDefiningOp<tensor::CastOp>()) stacked = cast.getSource();
|
||||||
|
while (auto insert = stacked.getDefiningOp<tensor::InsertSliceOp>()) stacked = insert.getDest();
|
||||||
|
// The stack is a chain. Find the insertion at the selected leading offset.
|
||||||
|
for (Value cursor = slice.getSource(); auto insert = cursor.getDefiningOp<tensor::InsertSliceOp>(); cursor = insert.getDest()) {
|
||||||
|
auto offset = evaluateDeferredIndex(insert.getMixedOffsets().front(), environment);
|
||||||
|
if (succeeded(offset) && *offset == *selected) {
|
||||||
|
Value source = insert.getSource();
|
||||||
|
if (auto expand = source.getDefiningOp<tensor::ExpandShapeOp>()) source = expand.getSrc();
|
||||||
|
if (auto arg = dyn_cast<BlockArgument>(source); arg && arg.getOwner() == &deferred.getBody().front())
|
||||||
|
return std::optional<unsigned>(arg.getArgNumber());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return std::optional<unsigned>();
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool isResidual(Operation *op) {
|
||||||
|
return isa<tensor::CollapseShapeOp, tensor::ExpandShapeOp, tensor::CastOp,
|
||||||
|
tensor::ExtractSliceOp, tensor::InsertSliceOp, tensor::ConcatOp,
|
||||||
|
tensor::EmptyOp, tensor::ExtractOp, arith::ConstantOp,
|
||||||
|
arith::IndexCastOp, arith::AddIOp, arith::SubIOp, arith::MulIOp,
|
||||||
|
affine::AffineApplyOp>(op);
|
||||||
|
}
|
||||||
|
|
||||||
|
static SpatGraphComputeBatch graphBatchOwner(Value value) {
|
||||||
|
if (auto result = dyn_cast<OpResult>(value))
|
||||||
|
return dyn_cast<SpatGraphComputeBatch>(result.getOwner());
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
static Value getEnclosingScheduledLane(SpatDeferredCommunicationOp deferred,
|
||||||
|
SpatScheduledComputeBatch scheduled) {
|
||||||
|
Block *block = deferred->getBlock();
|
||||||
|
while (block && block->getParentOp() != scheduled) {
|
||||||
|
Operation *parent = block->getParentOp();
|
||||||
|
block = parent ? parent->getBlock() : nullptr;
|
||||||
|
}
|
||||||
|
return block && !block->empty() ? block->getArgument(0) : Value();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
FailureOr<int64_t> evaluateDeferredIndex(Value value, const StaticIndexEnvironment &environment) {
|
||||||
|
llvm::SmallDenseSet<Value, 16> visiting;
|
||||||
|
return evaluate(value, environment, visiting);
|
||||||
|
}
|
||||||
|
FailureOr<int64_t> evaluateDeferredIndex(OpFoldResult value, const StaticIndexEnvironment &environment) {
|
||||||
|
if (auto attr = dyn_cast<Attribute>(value))
|
||||||
|
if (auto integer = dyn_cast<IntegerAttr>(attr)) return integer.getInt();
|
||||||
|
if (auto dynamic = dyn_cast<Value>(value)) return evaluateDeferredIndex(dynamic, environment);
|
||||||
|
return failure();
|
||||||
|
}
|
||||||
|
|
||||||
|
FailureOr<std::optional<ResolvedDeferredSource>> tryResolveDeferredSource(Value value, SpatDeferredCommunicationOp deferred,
|
||||||
|
const StaticIndexEnvironment &environment) {
|
||||||
|
auto index = sourceArgument(value, deferred, environment);
|
||||||
|
if (failed(index))
|
||||||
|
return failure();
|
||||||
|
if (!*index)
|
||||||
|
return std::optional<ResolvedDeferredSource>();
|
||||||
|
return std::optional<ResolvedDeferredSource>(ResolvedDeferredSource {**index, deferred.getSources()[**index]});
|
||||||
|
}
|
||||||
|
|
||||||
|
FailureOr<ResolvedDeferredSource> requireResolvedDeferredSource(Value value, SpatDeferredCommunicationOp deferred,
|
||||||
|
const StaticIndexEnvironment &environment) {
|
||||||
|
auto source = tryResolveDeferredSource(value, deferred, environment);
|
||||||
|
if (failed(source) || !*source)
|
||||||
|
return deferred.emitOpError("cannot statically resolve deferred source selection"), failure();
|
||||||
|
return **source;
|
||||||
|
}
|
||||||
|
|
||||||
|
FailureOr<SpecializedDeferredProgram> analyzeDeferredProgram(SpatDeferredCommunicationOp deferred,
|
||||||
|
std::optional<unsigned> targetScheduledLane) {
|
||||||
|
Block &body = deferred.getBody().front();
|
||||||
|
auto yield = dyn_cast<SpatYieldOp>(body.getTerminator());
|
||||||
|
if (!yield || yield.getOutputs().size() != 1)
|
||||||
|
return deferred.emitOpError("requires exactly one deferred yielded value"), failure();
|
||||||
|
StaticIndexEnvironment environment;
|
||||||
|
if (auto scheduled = deferred->getParentOfType<SpatScheduledComputeBatch>()) {
|
||||||
|
if (!targetScheduledLane) return deferred.emitOpError("scheduled-batch deferred program requires lane specialization"), failure();
|
||||||
|
Value scheduledLane = getEnclosingScheduledLane(deferred, scheduled);
|
||||||
|
if (!scheduledLane)
|
||||||
|
return deferred.emitOpError("cannot locate the enclosing scheduled lane"), failure();
|
||||||
|
environment.bindings[scheduledLane] = *targetScheduledLane;
|
||||||
|
} else if (targetScheduledLane) return deferred.emitOpError("scalar deferred program cannot have a target lane"), failure();
|
||||||
|
SpecializedDeferredProgram program;
|
||||||
|
program.deferred = deferred;
|
||||||
|
program.targetScheduledLane = targetScheduledLane;
|
||||||
|
if (auto scheduled = deferred->getParentOfType<SpatScheduledComputeBatch>())
|
||||||
|
program.scheduledLane = getEnclosingScheduledLane(deferred, scheduled);
|
||||||
|
program.yieldedValue = yield.getOutputs().front();
|
||||||
|
llvm::SmallDenseSet<Value, 32> visited;
|
||||||
|
std::function<LogicalResult(Value)> visit = [&](Value value) -> LogicalResult {
|
||||||
|
if (!visited.insert(value).second) return success();
|
||||||
|
// A graph-batch projection is semantically different from the canonical
|
||||||
|
// source-selector scaffold even though both contain extract/collapse ops.
|
||||||
|
if (auto slice = value.getDefiningOp<tensor::ExtractSliceOp>()) {
|
||||||
|
auto source = tryResolveDeferredSource(slice.getSource(), deferred, environment);
|
||||||
|
if (failed(source)) return failure();
|
||||||
|
if (*source && graphBatchOwner((*source)->selectedValue)) {
|
||||||
|
auto type = dyn_cast<RankedTensorType>((*source)->selectedValue.getType());
|
||||||
|
auto result = dyn_cast<RankedTensorType>(slice.getResult().getType());
|
||||||
|
if (!type || !result || type.getRank() == 0) return deferred.emitOpError("graph projection requires ranked tensors");
|
||||||
|
auto offset = evaluateDeferredIndex(slice.getMixedOffsets().front(), environment);
|
||||||
|
if (failed(offset)) return deferred.emitOpError("graph projection leading offset is not statically resolvable");
|
||||||
|
auto size = evaluateDeferredIndex(slice.getMixedSizes().front(), environment);
|
||||||
|
if (failed(size)) return deferred.emitOpError("graph projection leading size is not statically resolvable");
|
||||||
|
auto stride = evaluateDeferredIndex(slice.getMixedStrides().front(), environment);
|
||||||
|
if (failed(stride)) return deferred.emitOpError("graph projection leading stride is not statically resolvable");
|
||||||
|
if (*offset < 0) return deferred.emitOpError("graph projection leading offset is negative");
|
||||||
|
if (*size <= 0) return deferred.emitOpError("graph projection leading size must be positive");
|
||||||
|
if (*stride <= 0) return deferred.emitOpError("graph projection leading stride must be positive");
|
||||||
|
DeferredProjectionLeaf leaf;
|
||||||
|
leaf.kind = DeferredLeafKind::GraphBatchProjection; leaf.sourceOperandIndex = (*source)->sourceOperandIndex;
|
||||||
|
leaf.replacementRoot = value; leaf.leadingProjection = slice; leaf.reconstructedType = result;
|
||||||
|
for (int64_t i = 0; i < *size; ++i) {
|
||||||
|
int64_t slot;
|
||||||
|
if (llvm::MulOverflow(i, *stride, slot) || llvm::AddOverflow(*offset, slot, slot)
|
||||||
|
|| slot >= type.getDimSize(0))
|
||||||
|
return deferred.emitOpError("graph projection selects a physical slot outside the batch");
|
||||||
|
leaf.physicalSlots.push_back(slot);
|
||||||
|
}
|
||||||
|
for (unsigned i = 1; i < type.getRank(); ++i) {
|
||||||
|
auto innerOffset = evaluateDeferredIndex(slice.getMixedOffsets()[i], environment);
|
||||||
|
auto innerSize = evaluateDeferredIndex(slice.getMixedSizes()[i], environment);
|
||||||
|
auto innerStride = evaluateDeferredIndex(slice.getMixedStrides()[i], environment);
|
||||||
|
if (failed(innerOffset) || failed(innerSize) || failed(innerStride))
|
||||||
|
return deferred.emitOpError("graph projection has unresolved inner geometry");
|
||||||
|
leaf.innerGeometry.offsets.push_back(*innerOffset); leaf.innerGeometry.sizes.push_back(*innerSize); leaf.innerGeometry.strides.push_back(*innerStride);
|
||||||
|
}
|
||||||
|
program.leaves.push_back(std::move(leaf)); return success();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto source = tryResolveDeferredSource(value, deferred, environment);
|
||||||
|
if (failed(source)) return failure();
|
||||||
|
if (*source) {
|
||||||
|
auto type = dyn_cast<RankedTensorType>((*source)->selectedValue.getType());
|
||||||
|
if (!type) return deferred.emitOpError("deferred source is not a ranked tensor");
|
||||||
|
DeferredProjectionLeaf leaf;
|
||||||
|
leaf.sourceOperandIndex = (*source)->sourceOperandIndex;
|
||||||
|
leaf.replacementRoot = value;
|
||||||
|
leaf.reconstructedType = type;
|
||||||
|
if (graphBatchOwner((*source)->selectedValue)) {
|
||||||
|
leaf.kind = DeferredLeafKind::GraphBatchIdentity;
|
||||||
|
for (int64_t slot = 0; slot < type.getDimSize(0); ++slot) leaf.physicalSlots.push_back(slot);
|
||||||
|
} else leaf.kind = DeferredLeafKind::ScalarSource;
|
||||||
|
program.leaves.push_back(std::move(leaf));
|
||||||
|
return success();
|
||||||
|
}
|
||||||
|
Operation *op = value.getDefiningOp();
|
||||||
|
if (!op || op->getBlock() != &body || !isResidual(op))
|
||||||
|
return deferred.emitOpError("deferred residual contains an unsupported operation");
|
||||||
|
for (Value operand : op->getOperands()) if (failed(visit(operand))) return failure();
|
||||||
|
program.residualOps.push_back(op); return success();
|
||||||
|
};
|
||||||
|
if (failed(visit(program.yieldedValue))) return failure();
|
||||||
|
return std::move(program);
|
||||||
|
}
|
||||||
|
|
||||||
|
FailureOr<const GraphBatchPublicationMap *> getGraphBatchPublicationMap(
|
||||||
|
SpatGraphComputeBatch graphBatch, unsigned resultIndex, GraphBatchPublicationCache &cache) {
|
||||||
|
GraphBatchPublicationKey key {graphBatch.getOperation(), resultIndex};
|
||||||
|
if (auto it = cache.find(key); it != cache.end()) return &it->second;
|
||||||
|
auto resultType = dyn_cast<RankedTensorType>(graphBatch.getResult(resultIndex).getType());
|
||||||
|
auto output = graphBatch.getOutputArgument(resultIndex);
|
||||||
|
auto lane = graphBatch.getLaneArgument();
|
||||||
|
if (!resultType || !output || !lane || resultType.getRank() == 0)
|
||||||
|
return graphBatch.emitOpError("graph batch publication is malformed"), failure();
|
||||||
|
tensor::ParallelInsertSliceOp publication;
|
||||||
|
auto parallel = dyn_cast<SpatInParallelOp>(graphBatch.getBody().front().getTerminator());
|
||||||
|
if (!parallel) return graphBatch.emitOpError("graph batch lacks publication region"), failure();
|
||||||
|
for (Operation &op : parallel.getRegion().front()) if (auto insert = dyn_cast<tensor::ParallelInsertSliceOp>(op); insert && insert.getDest() == *output) {
|
||||||
|
if (publication) return graphBatch.emitOpError("graph result has multiple publications"), failure();
|
||||||
|
publication = insert;
|
||||||
|
}
|
||||||
|
if (!publication) return graphBatch.emitOpError("graph result lacks publication"), failure();
|
||||||
|
auto fragment = dyn_cast<RankedTensorType>(publication.getSource().getType());
|
||||||
|
if (!fragment || resultType.getRank() != fragment.getRank() + 1) return graphBatch.emitOpError("graph publication fragment type is invalid"), failure();
|
||||||
|
GraphBatchPublicationMap map;
|
||||||
|
map.physicalResultType = resultType;
|
||||||
|
map.publicationFragmentType = fragment;
|
||||||
|
map.graphLaneToPhysicalSlot.resize(graphBatch.getLaneCount(), -1);
|
||||||
|
map.physicalSlotToGraphLane.resize(resultType.getDimSize(0), -1);
|
||||||
|
for (int64_t index = 0; index < graphBatch.getLaneCount(); ++index) {
|
||||||
|
StaticIndexEnvironment environment; environment.bindings[*lane] = index;
|
||||||
|
auto slot = evaluateDeferredIndex(publication.getMixedOffsets().front(), environment);
|
||||||
|
auto size = evaluateDeferredIndex(publication.getMixedSizes().front(), environment);
|
||||||
|
auto stride = evaluateDeferredIndex(publication.getMixedStrides().front(), environment);
|
||||||
|
if (failed(slot) || failed(size) || failed(stride) || *size != 1 || *stride != 1 || *slot < 0 || *slot >= resultType.getDimSize(0))
|
||||||
|
return graphBatch.emitOpError("graph publication leading geometry is invalid"), failure();
|
||||||
|
for (unsigned dim = 1; dim < resultType.getRank(); ++dim) {
|
||||||
|
auto offset = evaluateDeferredIndex(publication.getMixedOffsets()[dim], environment);
|
||||||
|
auto extent = evaluateDeferredIndex(publication.getMixedSizes()[dim], environment);
|
||||||
|
auto step = evaluateDeferredIndex(publication.getMixedStrides()[dim], environment);
|
||||||
|
if (failed(offset) || failed(extent) || failed(step) || *offset != 0 || *extent != fragment.getDimSize(dim - 1) || *step != 1)
|
||||||
|
return graphBatch.emitOpError("graph publication inner geometry is invalid"), failure();
|
||||||
|
}
|
||||||
|
if (map.physicalSlotToGraphLane[*slot] != -1) return graphBatch.emitOpError("graph publication has duplicate physical slot"), failure();
|
||||||
|
map.graphLaneToPhysicalSlot[index] = *slot; map.physicalSlotToGraphLane[*slot] = index;
|
||||||
|
}
|
||||||
|
if (llvm::is_contained(map.physicalSlotToGraphLane, -1)) return graphBatch.emitOpError("graph publication has missing physical slot"), failure();
|
||||||
|
return &cache.try_emplace(key, std::move(map)).first->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace onnx_mlir::spatial
|
||||||
@@ -0,0 +1,102 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "mlir/Dialect/Tensor/IR/Tensor.h"
|
||||||
|
#include "mlir/IR/Operation.h"
|
||||||
|
#include "src/Accelerators/PIM/Dialect/Spatial/SpatialOps.hpp"
|
||||||
|
|
||||||
|
#include "llvm/ADT/DenseMap.h"
|
||||||
|
|
||||||
|
namespace onnx_mlir::spatial {
|
||||||
|
|
||||||
|
struct StaticIndexEnvironment {
|
||||||
|
llvm::DenseMap<mlir::Value, int64_t> bindings;
|
||||||
|
};
|
||||||
|
|
||||||
|
mlir::FailureOr<int64_t> evaluateDeferredIndex(
|
||||||
|
mlir::Value value, const StaticIndexEnvironment &environment);
|
||||||
|
mlir::FailureOr<int64_t> evaluateDeferredIndex(
|
||||||
|
mlir::OpFoldResult value, const StaticIndexEnvironment &environment);
|
||||||
|
|
||||||
|
enum class DeferredLeafKind { ScalarSource, GraphBatchProjection, GraphBatchIdentity };
|
||||||
|
|
||||||
|
struct StaticSliceGeometry {
|
||||||
|
llvm::SmallVector<int64_t> offsets;
|
||||||
|
llvm::SmallVector<int64_t> sizes;
|
||||||
|
llvm::SmallVector<int64_t> strides;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct DeferredProjectionLeaf {
|
||||||
|
DeferredLeafKind kind = DeferredLeafKind::ScalarSource;
|
||||||
|
unsigned sourceOperandIndex = 0;
|
||||||
|
mlir::Value replacementRoot;
|
||||||
|
mlir::tensor::ExtractSliceOp leadingProjection;
|
||||||
|
llvm::SmallVector<int64_t> physicalSlots;
|
||||||
|
StaticSliceGeometry innerGeometry;
|
||||||
|
mlir::RankedTensorType reconstructedType;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct SpecializedDeferredProgram {
|
||||||
|
SpatDeferredCommunicationOp deferred;
|
||||||
|
std::optional<unsigned> targetScheduledLane;
|
||||||
|
mlir::Value scheduledLane;
|
||||||
|
mlir::Value yieldedValue;
|
||||||
|
llvm::SmallVector<DeferredProjectionLeaf, 0> leaves;
|
||||||
|
llvm::SmallVector<mlir::Operation *> residualOps;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ResolvedDeferredSource {
|
||||||
|
unsigned sourceOperandIndex = 0;
|
||||||
|
mlir::Value selectedValue;
|
||||||
|
};
|
||||||
|
|
||||||
|
mlir::FailureOr<std::optional<ResolvedDeferredSource>> tryResolveDeferredSource(
|
||||||
|
mlir::Value value, SpatDeferredCommunicationOp deferred,
|
||||||
|
const StaticIndexEnvironment &environment);
|
||||||
|
mlir::FailureOr<ResolvedDeferredSource> requireResolvedDeferredSource(
|
||||||
|
mlir::Value value, SpatDeferredCommunicationOp deferred,
|
||||||
|
const StaticIndexEnvironment &environment);
|
||||||
|
mlir::FailureOr<SpecializedDeferredProgram> analyzeDeferredProgram(
|
||||||
|
SpatDeferredCommunicationOp deferred,
|
||||||
|
std::optional<unsigned> targetScheduledLane);
|
||||||
|
|
||||||
|
struct GraphBatchPublicationMap {
|
||||||
|
mlir::RankedTensorType physicalResultType;
|
||||||
|
mlir::RankedTensorType publicationFragmentType;
|
||||||
|
llvm::SmallVector<int64_t> graphLaneToPhysicalSlot;
|
||||||
|
llvm::SmallVector<int64_t> physicalSlotToGraphLane;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct GraphBatchPublicationKey {
|
||||||
|
mlir::Operation *graphBatch = nullptr;
|
||||||
|
unsigned resultIndex = 0;
|
||||||
|
bool operator==(const GraphBatchPublicationKey &other) const {
|
||||||
|
return graphBatch == other.graphBatch && resultIndex == other.resultIndex;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
using GraphBatchPublicationCache =
|
||||||
|
llvm::DenseMap<GraphBatchPublicationKey, GraphBatchPublicationMap>;
|
||||||
|
|
||||||
|
mlir::FailureOr<const GraphBatchPublicationMap *> getGraphBatchPublicationMap(
|
||||||
|
SpatGraphComputeBatch graphBatch, unsigned resultIndex,
|
||||||
|
GraphBatchPublicationCache &cache);
|
||||||
|
|
||||||
|
} // namespace onnx_mlir::spatial
|
||||||
|
|
||||||
|
namespace llvm {
|
||||||
|
template <> struct DenseMapInfo<onnx_mlir::spatial::GraphBatchPublicationKey> {
|
||||||
|
static onnx_mlir::spatial::GraphBatchPublicationKey getEmptyKey() {
|
||||||
|
return {DenseMapInfo<mlir::Operation *>::getEmptyKey(), 0};
|
||||||
|
}
|
||||||
|
static onnx_mlir::spatial::GraphBatchPublicationKey getTombstoneKey() {
|
||||||
|
return {DenseMapInfo<mlir::Operation *>::getTombstoneKey(), 0};
|
||||||
|
}
|
||||||
|
static unsigned getHashValue(const onnx_mlir::spatial::GraphBatchPublicationKey &key) {
|
||||||
|
return hash_combine(key.graphBatch, key.resultIndex);
|
||||||
|
}
|
||||||
|
static bool isEqual(const onnx_mlir::spatial::GraphBatchPublicationKey &lhs,
|
||||||
|
const onnx_mlir::spatial::GraphBatchPublicationKey &rhs) {
|
||||||
|
return lhs == rhs;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} // namespace llvm
|
||||||
@@ -9,6 +9,7 @@
|
|||||||
#include "Scheduling/MergeSchedulingAnalysis.hpp"
|
#include "Scheduling/MergeSchedulingAnalysis.hpp"
|
||||||
#include "src/Accelerators/PIM/Conversion/ONNXToSpatial/ONNXToSpatialVerifier.hpp"
|
#include "src/Accelerators/PIM/Conversion/ONNXToSpatial/ONNXToSpatialVerifier.hpp"
|
||||||
#include "src/Accelerators/PIM/Common/PimCommon.hpp"
|
#include "src/Accelerators/PIM/Common/PimCommon.hpp"
|
||||||
|
#include "src/Accelerators/PIM/Common/Support/DebugDump.hpp"
|
||||||
#include "src/Accelerators/PIM/Pass/PIMPasses.h"
|
#include "src/Accelerators/PIM/Pass/PIMPasses.h"
|
||||||
|
|
||||||
using namespace mlir;
|
using namespace mlir;
|
||||||
@@ -44,6 +45,10 @@ struct MergeComputeNodesPass final : PassWrapper<MergeComputeNodesPass, Operatio
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Phase 1 is intentionally dumped before its verifier: malformed deferred
|
||||||
|
// payloads must be diagnosed from the producer-owned body.
|
||||||
|
dumpModule(moduleOp, "spatial2_scheduled_no_comm", /*assumeVerified=*/true);
|
||||||
|
|
||||||
if (failed(verifyMaterializedScheduleMapping(funcOp,
|
if (failed(verifyMaterializedScheduleMapping(funcOp,
|
||||||
schedule,
|
schedule,
|
||||||
materialization->peftClassPlans,
|
materialization->peftClassPlans,
|
||||||
|
|||||||
+30
-15
@@ -299,7 +299,8 @@ static LogicalResult collectPeftClassOperandsAndResults(PeftClassPlan &peftClass
|
|||||||
for (Value weight : getComputeInstanceWeights(instance))
|
for (Value weight : getComputeInstanceWeights(instance))
|
||||||
appendUnique(peftClassPlan.weights, weight);
|
appendUnique(peftClassPlan.weights, weight);
|
||||||
for (Value input : getComputeInstanceInputs(instance))
|
for (Value input : getComputeInstanceInputs(instance))
|
||||||
if (!getProducerValueRef(input, &instance))
|
if (!getProducerValueRef(input, &instance) &&
|
||||||
|
!isDeferredFragmentAssemblyInput(input, instance))
|
||||||
appendUnique(peftClassPlan.inputs, input);
|
appendUnique(peftClassPlan.inputs, input);
|
||||||
}
|
}
|
||||||
return success();
|
return success();
|
||||||
@@ -333,7 +334,8 @@ static LogicalResult collectPeftClassOperandsAndResults(PeftClassPlan &peftClass
|
|||||||
for (Value weight : getComputeInstanceWeights(instance))
|
for (Value weight : getComputeInstanceWeights(instance))
|
||||||
appendUnique(peftClassPlan.weights, weight);
|
appendUnique(peftClassPlan.weights, weight);
|
||||||
for (Value input : getComputeInstanceInputs(instance))
|
for (Value input : getComputeInstanceInputs(instance))
|
||||||
if (!getProducerValueRef(input, &instance))
|
if (!getProducerValueRef(input, &instance) &&
|
||||||
|
!isDeferredFragmentAssemblyInput(input, instance))
|
||||||
appendUnique(peftClassPlan.inputs, input);
|
appendUnique(peftClassPlan.inputs, input);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -387,6 +389,11 @@ static LogicalResult materializeResultfulBatchChunkAsScalar(PatternRewriter &rew
|
|||||||
|
|
||||||
IRMapping mapper;
|
IRMapping mapper;
|
||||||
mapper.map(*batch.getLaneArgument(), originalLane);
|
mapper.map(*batch.getLaneArgument(), originalLane);
|
||||||
|
Value localLane = arith::SubIOp::create(builder,
|
||||||
|
bodyLoc,
|
||||||
|
originalLane,
|
||||||
|
getOrCreateIndexConstant(rewriter, batch.getOperation(), instance.laneStart))
|
||||||
|
.getResult();
|
||||||
for (auto [index, weight] : llvm::enumerate(batch.getWeights()))
|
for (auto [index, weight] : llvm::enumerate(batch.getWeights()))
|
||||||
mapper.map(*batch.getWeightArgument(index), getBlockOperand(block, scheduledWeights, weight));
|
mapper.map(*batch.getWeightArgument(index), getBlockOperand(block, scheduledWeights, weight));
|
||||||
SmallVector<DeferredInputPlan> inputPlans;
|
SmallVector<DeferredInputPlan> inputPlans;
|
||||||
@@ -403,9 +410,13 @@ static LogicalResult materializeResultfulBatchChunkAsScalar(PatternRewriter &rew
|
|||||||
scheduledWeights.size(),
|
scheduledWeights.size(),
|
||||||
ArrayRef<ProducerValueKey> {},
|
ArrayRef<ProducerValueKey> {},
|
||||||
*batch.getLaneArgument(),
|
*batch.getLaneArgument(),
|
||||||
lower,
|
originalLane,
|
||||||
plan)))
|
plan)))
|
||||||
return failure();
|
return failure();
|
||||||
|
plan.scalarizedLocalLane = localLane;
|
||||||
|
plan.scalarizedGraphLaneBase = lower;
|
||||||
|
plan.scalarizedLaneCount = instance.laneCount;
|
||||||
|
plan.scalarizedHoistBlock = █
|
||||||
inputPlans.push_back(std::move(plan));
|
inputPlans.push_back(std::move(plan));
|
||||||
}
|
}
|
||||||
for (auto [index, outputArg] : llvm::enumerate(batch.getOutputs()))
|
for (auto [index, outputArg] : llvm::enumerate(batch.getOutputs()))
|
||||||
@@ -422,11 +433,6 @@ static LogicalResult materializeResultfulBatchChunkAsScalar(PatternRewriter &rew
|
|||||||
auto inParallel = dyn_cast<SpatInParallelOp>(source.getTerminator());
|
auto inParallel = dyn_cast<SpatInParallelOp>(source.getTerminator());
|
||||||
if (!inParallel)
|
if (!inParallel)
|
||||||
return batch.emitOpError("expected spat.in_parallel in resultful spat.graph_compute_batch"), failure();
|
return batch.emitOpError("expected spat.in_parallel in resultful spat.graph_compute_batch"), failure();
|
||||||
Value localLane = arith::SubIOp::create(builder,
|
|
||||||
bodyLoc,
|
|
||||||
originalLane,
|
|
||||||
getOrCreateIndexConstant(rewriter, batch.getOperation(), instance.laneStart))
|
|
||||||
.getResult();
|
|
||||||
DenseMap<BlockArgument, size_t> outputIndexByArg;
|
DenseMap<BlockArgument, size_t> outputIndexByArg;
|
||||||
for (size_t index = 0; index < batch.getNumResults(); ++index)
|
for (size_t index = 0; index < batch.getNumResults(); ++index)
|
||||||
outputIndexByArg[*batch.getOutputArgument(index)] = index;
|
outputIndexByArg[*batch.getOutputArgument(index)] = index;
|
||||||
@@ -624,7 +630,8 @@ static LogicalResult materializeMultiCpuPeftClass(
|
|||||||
if (auto compute = dyn_cast<SpatCompute>(representative.op)) {
|
if (auto compute = dyn_cast<SpatCompute>(representative.op)) {
|
||||||
IRMapping mapper;
|
IRMapping mapper;
|
||||||
for (auto [index, weight] : llvm::enumerate(compute.getWeights()))
|
for (auto [index, weight] : llvm::enumerate(compute.getWeights()))
|
||||||
mapper.map(*compute.getWeightArgument(index), block->getArgument(1 + index));
|
mapper.map(*compute.getWeightArgument(index),
|
||||||
|
getBlockOperand(*block, scheduled.getWeights(), weight, 1));
|
||||||
unsigned firstInputArg = 1 + scheduled.getWeights().size();
|
unsigned firstInputArg = 1 + scheduled.getWeights().size();
|
||||||
SmallVector<DeferredInputPlan> inputPlans;
|
SmallVector<DeferredInputPlan> inputPlans;
|
||||||
for (auto [index, input] : llvm::enumerate(compute.getInputs())) {
|
for (auto [index, input] : llvm::enumerate(compute.getInputs())) {
|
||||||
@@ -686,6 +693,10 @@ static LogicalResult materializeMultiCpuPeftClass(
|
|||||||
Value lower = getOrCreateIndexConstant(rewriter, scheduled.getOperation(), 0);
|
Value lower = getOrCreateIndexConstant(rewriter, scheduled.getOperation(), 0);
|
||||||
Value upper = getOrCreateIndexConstant(rewriter, scheduled.getOperation(), representative.laneCount);
|
Value upper = getOrCreateIndexConstant(rewriter, scheduled.getOperation(), representative.laneCount);
|
||||||
Value step = getOrCreateIndexConstant(rewriter, scheduled.getOperation(), 1);
|
Value step = getOrCreateIndexConstant(rewriter, scheduled.getOperation(), 1);
|
||||||
|
FailureOr<Value> sourceLaneStart =
|
||||||
|
buildSourceLaneStartForScheduledLane(rewriter, batch.getLoc(), scheduledLane, sourceLaneSelector, scheduled.getOperation());
|
||||||
|
if (failed(sourceLaneStart))
|
||||||
|
return failure();
|
||||||
auto loop = buildNormalizedScfFor(
|
auto loop = buildNormalizedScfFor(
|
||||||
rewriter,
|
rewriter,
|
||||||
batch.getLoc(),
|
batch.getLoc(),
|
||||||
@@ -696,10 +707,6 @@ static LogicalResult materializeMultiCpuPeftClass(
|
|||||||
[&](OpBuilder &builder, Location bodyLoc, Value innerLane, ValueRange iterArgs, SmallVectorImpl<Value> &yielded) -> LogicalResult {
|
[&](OpBuilder &builder, Location bodyLoc, Value innerLane, ValueRange iterArgs, SmallVectorImpl<Value> &yielded) -> LogicalResult {
|
||||||
|
|
||||||
IRMapping mapper;
|
IRMapping mapper;
|
||||||
FailureOr<Value> sourceLaneStart =
|
|
||||||
buildSourceLaneStartForScheduledLane(builder, bodyLoc, scheduledLane, sourceLaneSelector, scheduled.getOperation());
|
|
||||||
if (failed(sourceLaneStart))
|
|
||||||
return failure();
|
|
||||||
Value sourceLane =
|
Value sourceLane =
|
||||||
affine::AffineApplyOp::create(builder,
|
affine::AffineApplyOp::create(builder,
|
||||||
bodyLoc,
|
bodyLoc,
|
||||||
@@ -710,7 +717,8 @@ static LogicalResult materializeMultiCpuPeftClass(
|
|||||||
.getResult();
|
.getResult();
|
||||||
mapper.map(*batch.getLaneArgument(), sourceLane);
|
mapper.map(*batch.getLaneArgument(), sourceLane);
|
||||||
for (auto [index, weight] : llvm::enumerate(batch.getWeights()))
|
for (auto [index, weight] : llvm::enumerate(batch.getWeights()))
|
||||||
mapper.map(*batch.getWeightArgument(index), block->getArgument(1 + index));
|
mapper.map(*batch.getWeightArgument(index),
|
||||||
|
getBlockOperand(*block, scheduled.getWeights(), weight, 1));
|
||||||
unsigned firstInputArg = 1 + scheduled.getWeights().size();
|
unsigned firstInputArg = 1 + scheduled.getWeights().size();
|
||||||
SmallVector<DeferredInputPlan> inputPlans;
|
SmallVector<DeferredInputPlan> inputPlans;
|
||||||
for (auto [index, input] : llvm::enumerate(batch.getInputs())) {
|
for (auto [index, input] : llvm::enumerate(batch.getInputs())) {
|
||||||
@@ -730,6 +738,10 @@ static LogicalResult materializeMultiCpuPeftClass(
|
|||||||
scheduledLane,
|
scheduledLane,
|
||||||
plan)))
|
plan)))
|
||||||
return failure();
|
return failure();
|
||||||
|
plan.scalarizedLocalLane = innerLane;
|
||||||
|
plan.scalarizedGraphLaneBase = *sourceLaneStart;
|
||||||
|
plan.scalarizedLaneCount = representative.laneCount;
|
||||||
|
plan.scalarizedHoistBlock = block;
|
||||||
inputPlans.push_back(std::move(plan));
|
inputPlans.push_back(std::move(plan));
|
||||||
}
|
}
|
||||||
for (unsigned index = 0; index < batch.getNumResults(); ++index)
|
for (unsigned index = 0; index < batch.getNumResults(); ++index)
|
||||||
@@ -838,7 +850,10 @@ materializeScheduledCompute(func::FuncOp funcOp,
|
|||||||
llvm::sort(peftClassPlan.cpus);
|
llvm::sort(peftClassPlan.cpus);
|
||||||
for (size_t cpu : peftClassPlan.cpus)
|
for (size_t cpu : peftClassPlan.cpus)
|
||||||
llvm::sort(peftClassPlan.instancesByCpu[cpu], [&](const ComputeInstance &lhs, const ComputeInstance &rhs) {
|
llvm::sort(peftClassPlan.instancesByCpu[cpu], [&](const ComputeInstance &lhs, const ComputeInstance &rhs) {
|
||||||
return schedule.computeToCpuSlotMap.lookup(lhs) < schedule.computeToCpuSlotMap.lookup(rhs);
|
return std::tie(graphIds.find(lhs.op)->second,
|
||||||
|
schedule.computeToCpuSlotMap.find(lhs)->second) <
|
||||||
|
std::tie(graphIds.find(rhs.op)->second,
|
||||||
|
schedule.computeToCpuSlotMap.find(rhs)->second);
|
||||||
});
|
});
|
||||||
if (failed(verifyPeftClassPlan(funcOp.getOperation(), peftClassPlan, schedule)))
|
if (failed(verifyPeftClassPlan(funcOp.getOperation(), peftClassPlan, schedule)))
|
||||||
return failure();
|
return failure();
|
||||||
|
|||||||
@@ -64,8 +64,6 @@ struct PeftMaterializationReportSummary {
|
|||||||
size_t scheduledCompute = 0;
|
size_t scheduledCompute = 0;
|
||||||
size_t scheduledComputeBatch = 0;
|
size_t scheduledComputeBatch = 0;
|
||||||
size_t deferredCommunication = 0;
|
size_t deferredCommunication = 0;
|
||||||
size_t deferredCommunicationScalarMetadata = 0;
|
|
||||||
size_t deferredCommunicationScheduledLaneMetadata = 0;
|
|
||||||
size_t deferredCommunicationMultiSourcePayloads = 0;
|
size_t deferredCommunicationMultiSourcePayloads = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -95,10 +93,6 @@ static PeftMaterializationReportSummary buildPeftMaterializationReportSummary(
|
|||||||
}
|
}
|
||||||
funcOp.walk([&](SpatDeferredCommunicationOp transfer) {
|
funcOp.walk([&](SpatDeferredCommunicationOp transfer) {
|
||||||
summary.deferredCommunication++;
|
summary.deferredCommunication++;
|
||||||
if (auto batchedAttr = transfer->getAttrOfType<BoolAttr>("batched"); batchedAttr && batchedAttr.getValue())
|
|
||||||
summary.deferredCommunicationScheduledLaneMetadata++;
|
|
||||||
else
|
|
||||||
summary.deferredCommunicationScalarMetadata++;
|
|
||||||
if (transfer.getSources().size() > 1)
|
if (transfer.getSources().size() > 1)
|
||||||
summary.deferredCommunicationMultiSourcePayloads++;
|
summary.deferredCommunicationMultiSourcePayloads++;
|
||||||
});
|
});
|
||||||
@@ -200,8 +194,6 @@ static void dumpPeftMaterializationReport(ModuleOp moduleOp,
|
|||||||
os << " scheduled_compute_batch: " << summary.scheduledComputeBatch << "\n";
|
os << " scheduled_compute_batch: " << summary.scheduledComputeBatch << "\n";
|
||||||
os << "Deferred communications:\n";
|
os << "Deferred communications:\n";
|
||||||
os << " total: " << summary.deferredCommunication << "\n";
|
os << " total: " << summary.deferredCommunication << "\n";
|
||||||
os << " scalar metadata: " << summary.deferredCommunicationScalarMetadata << "\n";
|
|
||||||
os << " scheduled-lane metadata: " << summary.deferredCommunicationScheduledLaneMetadata << "\n";
|
|
||||||
os << " multi-source payloads: " << summary.deferredCommunicationMultiSourcePayloads << "\n\n";
|
os << " multi-source payloads: " << summary.deferredCommunicationMultiSourcePayloads << "\n\n";
|
||||||
|
|
||||||
os << "PEFT Classes\n";
|
os << "PEFT Classes\n";
|
||||||
|
|||||||
+20
-1
@@ -1,4 +1,5 @@
|
|||||||
#include "ScheduledComputeVerification.hpp"
|
#include "ScheduledComputeVerification.hpp"
|
||||||
|
#include "DeferredProjectionAnalysis.hpp"
|
||||||
|
|
||||||
#include "mlir/Dialect/Tensor/IR/Tensor.h"
|
#include "mlir/Dialect/Tensor/IR/Tensor.h"
|
||||||
#include "src/Accelerators/PIM/Common/Support/Diagnostics.hpp"
|
#include "src/Accelerators/PIM/Common/Support/Diagnostics.hpp"
|
||||||
@@ -79,6 +80,7 @@ LogicalResult verifyMaterializedScheduleMapping(
|
|||||||
|
|
||||||
LogicalResult verifyDeferredTransferPhase1Invariants(func::FuncOp funcOp) {
|
LogicalResult verifyDeferredTransferPhase1Invariants(func::FuncOp funcOp) {
|
||||||
pim::CappedDiagnosticReporter diagnostics;
|
pim::CappedDiagnosticReporter diagnostics;
|
||||||
|
GraphBatchPublicationCache publicationCache;
|
||||||
funcOp.walk([&](SpatDeferredCommunicationOp transfer) {
|
funcOp.walk([&](SpatDeferredCommunicationOp transfer) {
|
||||||
for (Value source : transfer.getSources()) {
|
for (Value source : transfer.getSources()) {
|
||||||
auto result = dyn_cast<OpResult>(source);
|
auto result = dyn_cast<OpResult>(source);
|
||||||
@@ -92,7 +94,24 @@ LogicalResult verifyDeferredTransferPhase1Invariants(func::FuncOp funcOp) {
|
|||||||
!transfer->getParentOfType<SpatScheduledComputeBatch>())
|
!transfer->getParentOfType<SpatScheduledComputeBatch>())
|
||||||
diagnostics.report(transfer.getOperation(), [&](Operation *illegalOp) {
|
diagnostics.report(transfer.getOperation(), [&](Operation *illegalOp) {
|
||||||
illegalOp->emitOpError("phase-check deferred communication must be inside a scheduled compute");
|
illegalOp->emitOpError("phase-check deferred communication must be inside a scheduled compute");
|
||||||
});
|
});
|
||||||
|
if (auto scheduled = transfer->getParentOfType<SpatScheduledComputeBatch>()) {
|
||||||
|
for (unsigned lane = 0; lane < static_cast<unsigned>(scheduled.getLaneCount()); ++lane) {
|
||||||
|
auto program = analyzeDeferredProgram(transfer, lane);
|
||||||
|
if (failed(program))
|
||||||
|
continue;
|
||||||
|
for (const DeferredProjectionLeaf &leaf : program->leaves) {
|
||||||
|
if (leaf.kind == DeferredLeafKind::ScalarSource)
|
||||||
|
continue;
|
||||||
|
auto source = dyn_cast<OpResult>(transfer.getSources()[leaf.sourceOperandIndex]);
|
||||||
|
auto graph = source ? dyn_cast<SpatGraphComputeBatch>(source.getOwner()) : SpatGraphComputeBatch();
|
||||||
|
if (!graph) continue;
|
||||||
|
if (failed(getGraphBatchPublicationMap(graph, source.getResultNumber(), publicationCache)))
|
||||||
|
diagnostics.report(transfer.getOperation(), [&](Operation *) {});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else
|
||||||
|
(void)analyzeDeferredProgram(transfer, std::nullopt);
|
||||||
});
|
});
|
||||||
|
|
||||||
diagnostics.emitSuppressedSummary(funcOp, "scheduled Spatial deferred communication verification failed");
|
diagnostics.emitSuppressedSummary(funcOp, "scheduled Spatial deferred communication verification failed");
|
||||||
|
|||||||
Reference in New Issue
Block a user