#include "mlir/Dialect/Affine/IR/AffineOps.h" #include "mlir/Dialect/Arith/IR/Arith.h" #include "mlir/Dialect/Func/IR/FuncOps.h" #include "mlir/Dialect/Linalg/IR/Linalg.h" #include "mlir/Dialect/SCF/IR/SCF.h" #include "mlir/Dialect/Tensor/IR/Tensor.h" #include "mlir/Pass/Pass.h" #include "mlir/Transforms/GreedyPatternRewriteDriver.h" #include "mlir/Transforms/DialectConversion.h" #include "Conversion/ONNXToSpatial/ONNXToSpatialVerifier.hpp" #include "mlir/Transforms/Passes.h" #include "src/Accelerators/PIM/Common/PimCommon.hpp" #include "src/Accelerators/PIM/Common/Support/DebugDump.hpp" #include "src/Accelerators/PIM/Conversion/ONNXToSpatial/Common/BiasAddUtils.hpp" #include "src/Accelerators/PIM/Conversion/ONNXToSpatial/Common/Common.hpp" #include "src/Accelerators/PIM/Conversion/ONNXToSpatial/Common/MatrixProductLowering.hpp" #include "src/Accelerators/PIM/Conversion/ONNXToSpatial/Common/RowStripLayoutUtils.hpp" #include "src/Accelerators/PIM/Conversion/ONNXToSpatial/Patterns.hpp" #include "src/Accelerators/PIM/Conversion/ONNXToSpatial/PlanLowering.hpp" #include "src/Accelerators/PIM/Dialect/Spatial/SpatialOps.hpp" #include "src/Accelerators/PIM/Dialect/Spatial/Transforms/MergeComputeNodes/SpatialDataflowCsvExporter.hpp" #include "src/Accelerators/PIM/Pass/PIMPasses.h" using namespace mlir; namespace onnx_mlir { namespace { static FailureOr getRowStripValue(Value value) { return getRowStripPhysicalValue(value); } static FailureOr publishRowStripValue(Operation* planOp, Value storage, PatternRewriter& rewriter) { auto logicalType = dyn_cast(planOp->getResult(0).getType()); if (!logicalType) return planOp->emitOpError("requires ranked logical output type"), failure(); FailureOr value = describeRowStripPhysicalValue(storage, logicalType); if (failed(value)) return planOp->emitOpError("lowering produced invalid row-strip physical storage"), failure(); FailureOr blueprint = createRowStripStorageBlueprint( storage, logicalType, rewriter, planOp->getLoc()); if (failed(blueprint)) return planOp->emitOpError("failed to create row-strip storage Blueprint"), failure(); rewriter.replaceOp(planOp, *blueprint); return *blueprint; } static bool isRowStripSelected(Operation* op) { auto selected = spatial::getSelectedPhysicalLayout(op); return selected && *selected == spatial::PhysicalLayout::NHWCRowStrip; } static bool isDenseSelected(Operation* op) { auto selected = spatial::getSelectedPhysicalLayout(op); return selected && *selected == spatial::PhysicalLayout::DenseNCHW; } static spatial::PhysicalLayout getKnownPhysicalLayout(Value value) { if (auto materialize = value.getDefiningOp()) return materialize.getTargetPhysicalLayout(); if (auto blueprint = value.getDefiningOp()) return blueprint.getPhysicalLayout(); if (Operation* producer = value.getDefiningOp()) { if (auto selected = spatial::getSelectedPhysicalLayout(producer)) return *selected; } return spatial::PhysicalLayout::DenseNCHW; } static LogicalResult verifySelectedLayouts( func::FuncOp funcOp, const spatial::SpatialTargetInfo& target) { LogicalResult result = success(); funcOp.walk([&](Operation* op) { auto capability = dyn_cast(op); if (!capability) return; auto selected = spatial::getSelectedPhysicalLayout(op); if (!selected) { op->emitOpError("requires a selected physical layout from SpatialLayoutPlanning"); result = failure(); return; } if (*selected != spatial::PhysicalLayout::DenseNCHW && *selected != spatial::PhysicalLayout::NHWCRowStrip) { op->emitOpError("has an unsupported selected physical layout"); result = failure(); return; } SmallVector operandLayouts; operandLayouts.reserve(op->getNumOperands()); for (Value operand : op->getOperands()) operandLayouts.push_back(getKnownPhysicalLayout(operand)); auto alternatives = capability.getLayoutAlternatives(target, operandLayouts); if (llvm::none_of(alternatives, [&](const spatial::LayoutAlternative& alternative) { return alternative.resultLayout == *selected && alternative.operandLayouts == operandLayouts; })) { op->emitOpError("selected physical layout is not lowerable for its explicit operand layouts"); result = failure(); } }); return result; } static FailureOr lowerRowStripRelu(const RowStripPhysicalValue& input, spatial::SpatReluPlanOp planOp, PatternRewriter& rewriter) { return applyRowStripRelu(input, rewriter, planOp.getLoc()); } static FailureOr lowerRowStripSilu(const RowStripPhysicalValue& input, spatial::SpatSiluPlanOp planOp, PatternRewriter& rewriter) { return applyRowStripSilu(input, rewriter, planOp.getLoc()); } static FailureOr lowerRowStripBiasAdd(const RowStripPhysicalValue& input, spatial::SpatBiasAddPlanOp planOp, PatternRewriter& rewriter) { return applyRowStripBiasAdd(input, planOp.getBias(), rewriter, planOp.getLoc()); } static FailureOr lowerRowStripAdd(const RowStripPhysicalValue& lhs, const RowStripPhysicalValue& rhs, spatial::SpatAddPlanOp planOp, PatternRewriter& rewriter) { return applyRowStripAdd(lhs, rhs, rewriter, planOp.getLoc()); } static FailureOr lowerRowStripConcat(ArrayRef inputs, spatial::SpatConcatPlanOp planOp, PatternRewriter& rewriter) { auto outputType = dyn_cast(planOp.getOutput().getType()); if (!outputType) return failure(); return applyRowStripConcat(inputs, outputType, rewriter, planOp.getLoc()); } static FailureOr materializeRowStripToDense(const RowStripPhysicalValue& rowStripValue, Location loc, PatternRewriter& rewriter) { if (rowStripValue.logicalType.getRank() != 4 || !rowStripValue.logicalType.hasStaticShape()) return failure(); return createRowStripAssemblyBlueprint(rowStripValue, rewriter, loc); } static FailureOr materializeDenseToRowStrip( Value input, RankedTensorType logicalType, Location loc, PatternRewriter& rewriter) { if (!logicalType || !logicalType.hasStaticShape() || logicalType.getRank() != 4 || logicalType.getDimSize(0) != 1) return failure(); auto nhwcType = RankedTensorType::get( {1, logicalType.getDimSize(2), logicalType.getDimSize(3), logicalType.getDimSize(1)}, logicalType.getElementType(), logicalType.getEncoding()); auto rowsType = RankedTensorType::get( {logicalType.getDimSize(2) * logicalType.getDimSize(3), logicalType.getDimSize(1)}, logicalType.getElementType(), logicalType.getEncoding()); auto rowsCompute = createSpatCompute<1>( rewriter, loc, rowsType, {}, input, [&](Value denseInput) { Value nhwc = createLinalgTranspose( denseInput, nhwcType, {0, 2, 3, 1}, rewriter, loc); Value rows = tensor::CollapseShapeOp::create( rewriter, loc, rowsType, nhwc, SmallVector {{0, 1, 2}, {3}}); spatial::SpatYieldOp::create(rewriter, loc, rows); }); Value rows = rowsCompute->getResult(0); FailureOr storage = createRowStripStorageFromRows(rows, logicalType, rewriter, loc); if (failed(storage)) return failure(); return createRowStripStorageBlueprint(*storage, logicalType, rewriter, loc); } static FailureOr lowerDenseBatchBiasAdd(Value input, Value bias, RankedTensorType resultType, PatternRewriter& rewriter, Location loc) { auto producer = input.getDefiningOp(); auto inputType = dyn_cast(input.getType()); auto biasType = dyn_cast(bias.getType()); if (!producer || !inputType || !biasType || !inputType.hasStaticShape() || !biasType.hasStaticShape() || !resultType.hasStaticShape() || inputType.getDimSize(0) != producer.getLaneCount() || biasType.getDimSize(0) != producer.getLaneCount() || resultType.getDimSize(0) != producer.getLaneCount()) return failure(); auto inputFragmentType = spatial::getGraphBatchFragmentType(inputType, producer.getLaneCount()); auto outputFragmentType = spatial::getGraphBatchFragmentType(resultType, producer.getLaneCount()); if (failed(inputFragmentType) || failed(outputFragmentType) || inputFragmentType->getRank() != biasType.getRank() || inputFragmentType->getDimSize(0) != 1 || inputFragmentType->getShape().drop_front() != biasType.getShape().drop_front() || inputFragmentType->getRank() != outputFragmentType->getRank() + 1) return failure(); for (auto [inputDim, outputDim] : llvm::zip(inputFragmentType->getShape().drop_front(), outputFragmentType->getShape())) if (outputDim > inputDim) return failure(); auto batch = createSpatComputeBatch(rewriter, loc, TypeRange {resultType}, producer.getLaneCount(), {}, ValueRange {input, bias}, [&](detail::SpatComputeBatchBodyArgs args) -> LogicalResult { FailureOr fragment = extractGraphBatchPhysicalFragment(rewriter, loc, args.inputs[0], args.lane, *inputFragmentType); if (failed(fragment)) return failure(); MixedSliceGeometry biasSlice; for (int64_t dim : inputFragmentType->getShape()) { biasSlice.offsets.push_back(biasSlice.offsets.empty() ? OpFoldResult(args.lane) : rewriter.getIndexAttr(0)); biasSlice.sizes.push_back(rewriter.getIndexAttr(dim)); biasSlice.strides.push_back(rewriter.getIndexAttr(1)); } Value biasFragment = extractMixedSliceOrIdentity(rewriter, loc, args.inputs[1], *inputFragmentType, biasSlice); if (!biasFragment) return failure(); Value added = spatial::SpatVAddOp::create(rewriter, loc, *inputFragmentType, *fragment, biasFragment); MixedSliceGeometry outputSlice; outputSlice.offsets.assign(inputFragmentType->getRank(), rewriter.getIndexAttr(0)); outputSlice.sizes.push_back(rewriter.getIndexAttr(1)); outputSlice.strides.assign(inputFragmentType->getRank(), rewriter.getIndexAttr(1)); for (int64_t dim : outputFragmentType->getShape()) outputSlice.sizes.push_back(rewriter.getIndexAttr(dim)); Value output = extractMixedSliceOrIdentity(rewriter, loc, added, *outputFragmentType, outputSlice); if (!output) return failure(); publishGraphBatchPhysicalFragment(rewriter, loc, output, args.outputs.front(), args.lane); return success(); }); if (failed(batch)) return failure(); return batch->getResult(0); } struct LowerDenseReluPlan final : OpRewritePattern { using OpRewritePattern::OpRewritePattern; LogicalResult matchAndRewrite(spatial::SpatReluPlanOp planOp, PatternRewriter& rewriter) const override { auto selected = spatial::getSelectedPhysicalLayout(planOp.getOperation()); if (!selected || *selected != spatial::PhysicalLayout::DenseNCHW) return failure(); auto computeOp = createSpatCompute<1>( rewriter, planOp.getLoc(), planOp.getOutput().getType(), {}, planOp.getInput(), [&](Value x) { auto relu = spatial::SpatReluOp::create(rewriter, planOp.getLoc(), planOp.getOutput().getType(), x); spatial::SpatYieldOp::create(rewriter, planOp.getLoc(), relu.getResult()); }); rewriter.replaceOp(planOp, computeOp.getResults()); return success(); } }; struct LowerDenseSiluPlan final : OpRewritePattern { using OpRewritePattern::OpRewritePattern; LogicalResult matchAndRewrite(spatial::SpatSiluPlanOp planOp, PatternRewriter& rewriter) const override { auto selected = spatial::getSelectedPhysicalLayout(planOp.getOperation()); if (!selected || *selected != spatial::PhysicalLayout::DenseNCHW) return failure(); auto computeOp = createSpatCompute<1>( rewriter, planOp.getLoc(), planOp.getOutput().getType(), {}, planOp.getInput(), [&](Value x) { Value sigmoid = spatial::SpatSigmoidOp::create( rewriter, planOp.getLoc(), planOp.getOutput().getType(), x).getResult(); Value silu = spatial::SpatVMulOp::create( rewriter, planOp.getLoc(), planOp.getOutput().getType(), x, sigmoid).getResult(); spatial::SpatYieldOp::create(rewriter, planOp.getLoc(), silu); }); rewriter.replaceOp(planOp, computeOp.getResults()); return success(); } }; struct LowerDenseResizePlan final : OpRewritePattern { explicit LowerDenseResizePlan(MLIRContext* ctx, const spatial::SpatialTargetInfo& target) : OpRewritePattern(ctx), target(target) {} LogicalResult matchAndRewrite(spatial::SpatResizeNearestPlanOp planOp, PatternRewriter& rewriter) const override { if (!isDenseSelected(planOp.getOperation())) return failure(); FailureOr lowered = lowerSelectedResizeNearestPlan(planOp, std::nullopt, target, rewriter); if (failed(lowered)) return planOp.emitOpError("failed to lower selected dense nearest Resize plan"); rewriter.replaceOp(planOp, *lowered); return success(); } const spatial::SpatialTargetInfo& target; }; struct LowerDenseBiasAddPlan final : OpRewritePattern { using OpRewritePattern::OpRewritePattern; LogicalResult matchAndRewrite(spatial::SpatBiasAddPlanOp planOp, PatternRewriter& rewriter) const override { if (!isDenseSelected(planOp.getOperation())) return failure(); auto resultType = dyn_cast(planOp.getOutput().getType()); if (!resultType) return planOp.emitOpError("requires ranked output type"); FailureOr denseBias = materializeDenseBiasAddTensor( planOp.getBias(), resultType, rewriter, planOp.getLoc()); if (failed(denseBias)) return planOp.emitOpError("failed to materialize dense Conv-style bias"); if (planOp.getInput().getDefiningOp()) { FailureOr lowered = lowerDenseBatchBiasAdd( planOp.getInput(), *denseBias, resultType, rewriter, planOp.getLoc()); if (succeeded(lowered)) { rewriter.replaceOp(planOp, *lowered); return success(); } } auto computeOp = createSpatCompute<2>( rewriter, planOp.getLoc(), planOp.getOutput().getType(), {}, ValueRange {planOp.getInput(), *denseBias}, [&](Value x, Value y) { auto added = spatial::SpatVAddOp::create( rewriter, planOp.getLoc(), planOp.getOutput().getType(), x, y); spatial::SpatYieldOp::create(rewriter, planOp.getLoc(), added.getResult()); }); rewriter.replaceOp(planOp, computeOp.getResults()); return success(); } }; struct LowerDenseAddPlan final : OpRewritePattern { using OpRewritePattern::OpRewritePattern; LogicalResult matchAndRewrite(spatial::SpatAddPlanOp planOp, PatternRewriter& rewriter) const override { if (!isDenseSelected(planOp.getOperation())) return failure(); auto compute = createSpatCompute<2>( rewriter, planOp.getLoc(), planOp.getOutput().getType(), {}, ValueRange {planOp.getLhs(), planOp.getRhs()}, [&](Value lhsValue, Value rhsValue) { Value added = spatial::SpatVAddOp::create( rewriter, planOp.getLoc(), planOp.getOutput().getType(), lhsValue, rhsValue); spatial::SpatYieldOp::create(rewriter, planOp.getLoc(), added); }); rewriter.replaceOp(planOp, compute.getResults()); return success(); } }; struct LowerDenseConcatPlan final : OpRewritePattern { using OpRewritePattern::OpRewritePattern; LogicalResult matchAndRewrite(spatial::SpatConcatPlanOp planOp, PatternRewriter& rewriter) const override { if (!isDenseSelected(planOp.getOperation())) return failure(); auto compute = createSpatCompute( rewriter, planOp.getLoc(), TypeRange {planOp.getOutput().getType()}, {}, planOp.getInputs(), [&](ValueRange values) { Value concatenated = spatial::SpatConcatOp::create( rewriter, planOp.getLoc(), planOp.getOutput().getType(), rewriter.getI64IntegerAttr(planOp.getAxis()), values); spatial::SpatYieldOp::create(rewriter, planOp.getLoc(), concatenated); }); rewriter.replaceOp(planOp, compute.getResults()); return success(); } }; static LogicalResult lowerAddPlan(spatial::SpatAddPlanOp planOp, PatternRewriter& rewriter) { FailureOr lhs = getRowStripValue(planOp.getLhs()); FailureOr rhs = getRowStripValue(planOp.getRhs()); if (isRowStripSelected(planOp.getOperation()) && failed(lhs)) { if (getKnownPhysicalLayout(planOp.getLhs()) == spatial::PhysicalLayout::NHWCRowStrip) return failure(); return planOp.emitOpError("selected row-strip Add plan requires row-strip inputs"); } if (isRowStripSelected(planOp.getOperation()) && failed(rhs)) { if (getKnownPhysicalLayout(planOp.getRhs()) == spatial::PhysicalLayout::NHWCRowStrip) return failure(); return planOp.emitOpError("selected row-strip Add plan requires row-strip inputs"); } if (isRowStripSelected(planOp.getOperation())) { rewriter.setInsertionPoint(planOp); FailureOr lowered = lowerRowStripAdd(*lhs, *rhs, planOp, rewriter); if (failed(lowered)) return planOp.emitOpError("failed to lower selected row-strip Spatial add plan"); if (failed(publishRowStripValue(planOp, *lowered, rewriter))) return failure(); return success(); } return planOp.emitOpError("dense Add plan was not lowered by the selected-plan patterns"); } static LogicalResult lowerConcatPlan(spatial::SpatConcatPlanOp planOp, PatternRewriter& rewriter) { SmallVector inputs; for (Value input : planOp.getInputs()) { FailureOr physical = getRowStripValue(input); if (failed(physical)) { inputs.clear(); break; } inputs.push_back(*physical); } if (isRowStripSelected(planOp.getOperation()) && inputs.size() != planOp.getInputs().size()) { if (llvm::any_of(planOp.getInputs(), [](Value input) { return getKnownPhysicalLayout(input) == spatial::PhysicalLayout::NHWCRowStrip; })) return failure(); return planOp.emitOpError("selected row-strip Concat plan requires row-strip inputs"); } if (isRowStripSelected(planOp.getOperation())) { rewriter.setInsertionPoint(planOp); FailureOr lowered = lowerRowStripConcat(inputs, planOp, rewriter); if (failed(lowered)) return planOp.emitOpError("failed to lower selected row-strip Spatial concat plan"); if (failed(publishRowStripValue(planOp, *lowered, rewriter))) return failure(); return success(); } return planOp.emitOpError("dense Concat plan was not lowered by the selected-plan patterns"); } struct LowerSelectedConvPlan final : OpRewritePattern { explicit LowerSelectedConvPlan(MLIRContext* ctx, const spatial::SpatialTargetInfo& target) : OpRewritePattern(ctx), target(target) {} LogicalResult matchAndRewrite(spatial::SpatConv2DPlanOp planOp, PatternRewriter& rewriter) const override { if (isDenseSelected(planOp.getOperation())) { FailureOr lowered = lowerSelectedConv2DPlan( planOp, std::nullopt, /*emitRowStripLayout=*/false, target, rewriter); if (failed(lowered)) return planOp.emitOpError("failed to lower selected dense Spatial Conv plan"); rewriter.replaceOp(planOp, *lowered); return success(); } if (!isRowStripSelected(planOp.getOperation())) return failure(); FailureOr rowStripInput = getRowStripValue(planOp.getInput()); if (failed(rowStripInput) && getKnownPhysicalLayout(planOp.getInput()) == spatial::PhysicalLayout::NHWCRowStrip) return failure(); std::optional physicalInput; if (succeeded(rowStripInput)) physicalInput = rowStripInput->storage; FailureOr lowered = lowerSelectedConv2DPlan( planOp, physicalInput, /*emitRowStripLayout=*/true, target, rewriter); if (failed(lowered)) return planOp.emitOpError("failed to lower selected row-strip Spatial Conv plan"); if (failed(publishRowStripValue(planOp, *lowered, rewriter))) return failure(); return success(); } const spatial::SpatialTargetInfo& target; }; struct LowerRowStripReluPlan final : OpRewritePattern { using OpRewritePattern::OpRewritePattern; LogicalResult matchAndRewrite(spatial::SpatReluPlanOp planOp, PatternRewriter& rewriter) const override { if (!isRowStripSelected(planOp.getOperation())) return failure(); FailureOr input = getRowStripValue(planOp.getInput()); if (failed(input)) { if (getKnownPhysicalLayout(planOp.getInput()) == spatial::PhysicalLayout::NHWCRowStrip) return failure(); return planOp.emitOpError("selected row-strip ReLU plan requires a row-strip input"); } FailureOr lowered = lowerRowStripRelu(*input, planOp, rewriter); if (failed(lowered)) return planOp.emitOpError("failed to lower selected row-strip Spatial ReLU plan"); if (failed(publishRowStripValue(planOp, *lowered, rewriter))) return failure(); return success(); } }; struct LowerRowStripSiluPlan final : OpRewritePattern { using OpRewritePattern::OpRewritePattern; LogicalResult matchAndRewrite(spatial::SpatSiluPlanOp planOp, PatternRewriter& rewriter) const override { if (!isRowStripSelected(planOp.getOperation())) return failure(); FailureOr input = getRowStripValue(planOp.getInput()); if (failed(input)) { if (getKnownPhysicalLayout(planOp.getInput()) == spatial::PhysicalLayout::NHWCRowStrip) return failure(); return planOp.emitOpError("selected row-strip SiLU plan requires a row-strip input"); } FailureOr lowered = lowerRowStripSilu(*input, planOp, rewriter); if (failed(lowered)) return planOp.emitOpError("failed to lower selected row-strip Spatial SiLU plan"); if (failed(publishRowStripValue(planOp, *lowered, rewriter))) return failure(); return success(); } }; struct LowerRowStripResizePlan final : OpRewritePattern { explicit LowerRowStripResizePlan(MLIRContext* ctx, const spatial::SpatialTargetInfo& target) : OpRewritePattern(ctx), target(target) {} LogicalResult matchAndRewrite(spatial::SpatResizeNearestPlanOp planOp, PatternRewriter& rewriter) const override { if (!isRowStripSelected(planOp.getOperation())) return failure(); FailureOr input = getRowStripValue(planOp.getInput()); if (failed(input)) { if (getKnownPhysicalLayout(planOp.getInput()) == spatial::PhysicalLayout::NHWCRowStrip) return failure(); return planOp.emitOpError("selected row-strip Resize plan requires a row-strip input"); } FailureOr lowered = lowerSelectedResizeNearestPlan(planOp, input->storage, target, rewriter); if (failed(lowered)) return planOp.emitOpError("failed to lower selected row-strip Resize plan"); if (failed(publishRowStripValue(planOp, *lowered, rewriter))) return failure(); return success(); } const spatial::SpatialTargetInfo& target; }; struct LowerDenseMaxPoolPlan final : OpRewritePattern { explicit LowerDenseMaxPoolPlan(MLIRContext* ctx, const spatial::SpatialTargetInfo& target) : OpRewritePattern(ctx), target(target) {} LogicalResult matchAndRewrite(spatial::SpatMaxPool2DPlanOp planOp, PatternRewriter& rewriter) const override { if (!isDenseSelected(planOp.getOperation())) return failure(); FailureOr lowered = lowerDenseMaxPool2DPlan(planOp, target, rewriter); if (failed(lowered)) return planOp.emitOpError("failed to lower selected dense Spatial MaxPool plan"); rewriter.replaceOp(planOp, *lowered); return success(); } const spatial::SpatialTargetInfo& target; }; struct LowerRowStripMaxPoolPlan final : OpRewritePattern { explicit LowerRowStripMaxPoolPlan(MLIRContext* ctx, const spatial::SpatialTargetInfo& target) : OpRewritePattern(ctx), target(target) {} LogicalResult matchAndRewrite(spatial::SpatMaxPool2DPlanOp planOp, PatternRewriter& rewriter) const override { if (!isRowStripSelected(planOp.getOperation())) return failure(); FailureOr input = getRowStripValue(planOp.getInput()); if (failed(input) && getKnownPhysicalLayout(planOp.getInput()) == spatial::PhysicalLayout::NHWCRowStrip) return failure(); std::optional physicalInput; if (succeeded(input)) physicalInput = input->storage; FailureOr lowered = lowerSelectedMaxPool2DPlan(planOp, physicalInput, target, rewriter); if (failed(lowered)) return planOp.emitOpError("failed to lower selected row-strip Spatial MaxPool plan"); if (failed(publishRowStripValue(planOp, *lowered, rewriter))) return failure(); return success(); } const spatial::SpatialTargetInfo& target; }; struct LowerRowStripGlobalAveragePoolPlan final : OpRewritePattern { explicit LowerRowStripGlobalAveragePoolPlan(MLIRContext* ctx, const spatial::SpatialTargetInfo& target) : OpRewritePattern(ctx), target(target) {} LogicalResult matchAndRewrite(spatial::SpatGlobalAveragePoolPlanOp planOp, PatternRewriter& rewriter) const override { if (!isRowStripSelected(planOp.getOperation())) return failure(); FailureOr input = getRowStripValue(planOp.getInput()); if (failed(input) && getKnownPhysicalLayout(planOp.getInput()) == spatial::PhysicalLayout::NHWCRowStrip) return failure(); std::optional physicalInput; if (succeeded(input)) physicalInput = input->storage; FailureOr lowered = lowerSelectedGlobalAveragePoolPlan(planOp, physicalInput, target, rewriter); if (failed(lowered)) return planOp.emitOpError("failed to lower selected row-strip Spatial global AveragePool plan"); if (failed(publishRowStripValue(planOp, *lowered, rewriter))) return failure(); return success(); } const spatial::SpatialTargetInfo& target; }; struct LowerDenseGlobalAveragePoolPlan final : OpRewritePattern { explicit LowerDenseGlobalAveragePoolPlan(MLIRContext* ctx, const spatial::SpatialTargetInfo& target) : OpRewritePattern(ctx), target(target) {} LogicalResult matchAndRewrite(spatial::SpatGlobalAveragePoolPlanOp planOp, PatternRewriter& rewriter) const override { if (!isDenseSelected(planOp.getOperation())) return failure(); FailureOr lowered = lowerDenseGlobalAveragePoolPlan(planOp, target, rewriter); if (failed(lowered)) return planOp.emitOpError("failed to lower selected dense Spatial global AveragePool plan"); rewriter.replaceOp(planOp, *lowered); return success(); } const spatial::SpatialTargetInfo& target; }; struct LowerRowStripBiasAddPlan final : OpRewritePattern { using OpRewritePattern::OpRewritePattern; LogicalResult matchAndRewrite(spatial::SpatBiasAddPlanOp planOp, PatternRewriter& rewriter) const override { if (!isRowStripSelected(planOp.getOperation())) return failure(); FailureOr input = getRowStripValue(planOp.getInput()); if (failed(input)) { if (getKnownPhysicalLayout(planOp.getInput()) == spatial::PhysicalLayout::NHWCRowStrip) return failure(); return planOp.emitOpError("selected row-strip bias_add plan requires a row-strip input"); } FailureOr lowered = lowerRowStripBiasAdd(*input, planOp, rewriter); if (failed(lowered)) return planOp.emitOpError("failed to lower selected row-strip Spatial bias_add plan"); if (failed(publishRowStripValue(planOp, *lowered, rewriter))) return failure(); return success(); } }; struct LowerRowStripAddPlan final : OpRewritePattern { using OpRewritePattern::OpRewritePattern; LogicalResult matchAndRewrite(spatial::SpatAddPlanOp planOp, PatternRewriter& rewriter) const override { if (!isRowStripSelected(planOp.getOperation())) return failure(); return lowerAddPlan(planOp, rewriter); } }; struct LowerRowStripConcatPlan final : OpRewritePattern { using OpRewritePattern::OpRewritePattern; LogicalResult matchAndRewrite(spatial::SpatConcatPlanOp planOp, PatternRewriter& rewriter) const override { if (!isRowStripSelected(planOp.getOperation())) return failure(); return lowerConcatPlan(planOp, rewriter); } }; struct LowerMaterializeLayout final : OpRewritePattern { using OpRewritePattern::OpRewritePattern; LogicalResult matchAndRewrite(spatial::SpatMaterializeLayoutOp materializeOp, PatternRewriter& rewriter) const override { auto source = materializeOp.getSourcePhysicalLayout(); auto target = materializeOp.getTargetPhysicalLayout(); if (source == spatial::PhysicalLayout::DenseNCHW && target == spatial::PhysicalLayout::DenseNCHW) { rewriter.replaceOp(materializeOp, materializeOp.getInput()); return success(); } if (source == spatial::PhysicalLayout::DenseNCHW && target == spatial::PhysicalLayout::NHWCRowStrip) { auto logicalType = dyn_cast(materializeOp.getInput().getType()); if (!logicalType) return materializeOp.emitOpError("requires a ranked dense input"), failure(); FailureOr rowStrip = materializeDenseToRowStrip( materializeOp.getInput(), logicalType, materializeOp.getLoc(), rewriter); if (failed(rowStrip)) return materializeOp.emitOpError( "failed to materialize dense NCHW storage to row-strip layout"), failure(); rewriter.replaceOp(materializeOp, *rowStrip); return success(); } if (source != spatial::PhysicalLayout::NHWCRowStrip || target != spatial::PhysicalLayout::DenseNCHW) return materializeOp.emitOpError( "unsupported Spatial layout materialization direction"), failure(); auto inputType = dyn_cast(materializeOp.getInput().getType()); if (!inputType) return materializeOp.emitOpError("requires a ranked row-strip input"), failure(); FailureOr rowStripValue = getRowStripValue(materializeOp.getInput()); if (failed(rowStripValue)) return materializeOp.emitOpError( "requires an explicitly defining row-strip physical value"), failure(); FailureOr dense = materializeRowStripToDense( *rowStripValue, materializeOp.getLoc(), rewriter); if (failed(dense)) return materializeOp.emitOpError( "failed to materialize row-strip storage to dense NCHW"), failure(); rewriter.replaceOp(materializeOp, *dense); return success(); } }; struct LowerRowStripFlatten final : OpRewritePattern { explicit LowerRowStripFlatten(MLIRContext* context, const spatial::SpatialTargetInfo& target) : OpRewritePattern(context), target(target) {} LogicalResult matchAndRewrite(spatial::SpatGraphCompute flattenOp, PatternRewriter& rewriter) const override { if (flattenOp.getInputs().size() != 1) return failure(); FailureOr input = getRowStripValue(flattenOp.getInputs().front()); if (failed(input) || failed(canLowerFlattenFromRowStrip(flattenOp, target))) return failure(); if (failed(lowerFlattenFromRowStrip(*input, flattenOp, target, rewriter))) return flattenOp.emitOpError( "failed to preserve row-strip layout through Flatten"), failure(); return success(); } const spatial::SpatialTargetInfo& target; }; struct LowerSpatialPlansPass final : PassWrapper> { MLIR_DEFINE_EXPLICIT_INTERNAL_INLINE_TYPE_ID(LowerSpatialPlansPass) StringRef getArgument() const override { return "lower-spatial-plans"; } StringRef getDescription() const override { return "Lower selected Spatial planning ops to low-level Spatial IR."; } LowerSpatialPlansPass() = default; explicit LowerSpatialPlansPass(const spatial::SpatialTargetInfo& target) : target(target), hasTarget(true) {} void runOnOperation() override { ModuleOp moduleOp = getOperation(); if (!hasTarget) { moduleOp.emitError("Spatial plan lowering requires an injected SpatialTargetInfo"); signalPassFailure(); return; } MLIRContext* ctx = moduleOp.getContext(); auto entryFunc = getPimEntryFunc(moduleOp); if (failed(entryFunc)) { moduleOp.emitError("failed to locate the PIM entry function during LowerSpatialPlans"); signalPassFailure(); return; } func::FuncOp funcOp = *entryFunc; PatternRewriter rewriter(ctx); auto verifyLogicalPhase = [&](StringRef stage) -> bool { if (succeeded(verifyLogicalSpatialGraphInvariants(*entryFunc))) return true; moduleOp.emitError() << "logical Spatial graph verification failed " << stage; signalPassFailure(); return false; }; if (!verifyLogicalPhase("at the start of LowerSpatialPlans")) return; if (failed(verifySelectedLayouts(funcOp, target))) { moduleOp.emitError("selected Spatial layout verification failed"); signalPassFailure(); return; } RewritePatternSet selectedPlanPatterns(ctx); selectedPlanPatterns.add(ctx); selectedPlanPatterns.add(ctx, target); if (failed(applyPatternsGreedily(funcOp, std::move(selectedPlanPatterns)))) { moduleOp.emitError("failed to lower selected Spatial plans"); signalPassFailure(); return; } RewritePatternSet layoutPatterns(ctx); layoutPatterns.add(ctx); layoutPatterns.add(ctx, target); ConversionTarget layoutTarget(*ctx); layoutTarget.addLegalDialect(); layoutTarget.addIllegalDialect(); layoutTarget.addIllegalOp(); layoutTarget.addDynamicallyLegalOp( [&](spatial::SpatGraphCompute computeOp) { if (computeOp.getInputs().size() != 1) return true; FailureOr input = getRowStripValue(computeOp.getInputs().front()); return failed(input) || failed(canLowerFlattenFromRowStrip(computeOp, target)); }); FrozenRewritePatternSet frozenLayoutPatterns(std::move(layoutPatterns)); if (failed(applyFullConversion(funcOp, layoutTarget, frozenLayoutPatterns))) { moduleOp.emitError("failed to lower explicit Spatial layout materialization"); signalPassFailure(); return; } if (!verifyLogicalPhase("after selected-plan conversion")) return; SmallVector deadPhysicalViews; funcOp.walk([&](spatial::SpatBlueprintOp blueprint) { if (spatial::isPhysicalView(blueprint.getMode()) && blueprint.use_empty()) deadPhysicalViews.push_back(blueprint); }); for (spatial::SpatBlueprintOp blueprint : deadPhysicalViews) rewriter.eraseOp(blueprint); bool hasIllegalOps = false; moduleOp.walk([&](Operation* op) { if (isa(op)) return; if (auto blueprint = dyn_cast(op)) { if (spatial::isFragmentAssembly(blueprint.getMode())) return; op->emitOpError("planning blueprint must not remain after LowerSpatialPlans"); hasIllegalOps = true; } else if (isa(op) || op->getDialect()->getNamespace() == "onnx") { op->emitOpError("operation must not remain after LowerSpatialPlans"); hasIllegalOps = true; } }); PassManager canonicalizationPM(ctx); canonicalizationPM.addPass(createCanonicalizerPass()); if (failed(canonicalizationPM.run(moduleOp))) moduleOp.emitWarning("failed to run LowerSpatialPlansPass canonicalization; continuing"); if (hasIllegalOps) { signalPassFailure(); } else { dumpModule(moduleOp, "spatial1_graph"); spatial::SpatialDataflowExportStage exportMode = spatial::getSpatialDataflowExportStage(); if (spatial::shouldExportSpatialDataflowStage(exportMode, spatial::SpatialDataflowExportStage::Spatial1) && failed(spatial::exportSpatialDataflowCsvGraph(funcOp, "spatial1_graph"))) { signalPassFailure(); return; } } if (!verifyLogicalPhase("at the end of LowerSpatialPlans")) return; } spatial::SpatialTargetInfo target; bool hasTarget = false; }; } // namespace std::unique_ptr createLowerSpatialPlansPass() { return std::make_unique(); } std::unique_ptr createLowerSpatialPlansPass(const spatial::SpatialTargetInfo& target) { return std::make_unique(target); } } // namespace onnx_mlir