#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/DialectConversion.h" #include "llvm/ADT/DenseMap.h" #include "llvm/ADT/SmallPtrSet.h" #include "Conversion/ONNXToSpatial/ONNXToSpatialVerifier.hpp" #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/RowStripLayoutUtils.hpp" #include "src/Accelerators/PIM/Conversion/ONNXToSpatial/Patterns.hpp" #include "src/Accelerators/PIM/Conversion/ONNXToSpatial/PlanLowering.hpp" #include "src/Accelerators/PIM/Dialect/Spatial/Transforms/MergeComputeNodes/SpatialDataflowCsvExporter.hpp" #include "src/Accelerators/PIM/Dialect/Spatial/SpatialOps.hpp" #include "src/Accelerators/PIM/Pass/PIMPasses.h" #include "src/Dialect/ONNX/ONNXOps.hpp" using namespace mlir; namespace onnx_mlir { namespace { static constexpr StringLiteral kDenseLayout = "dense_nchw"; static constexpr StringLiteral kRowStripLayout = "nchw_row_strip"; static FailureOr getRowStripValue(llvm::DenseMap& rowStripValues, Value value) { auto it = rowStripValues.find(value); if (it == rowStripValues.end()) return failure(); return it->second; } static FailureOr buildRowStripValue(spatial::SpatBlueprintOp blueprint, Value storage) { auto logicalType = dyn_cast(blueprint.getOutput().getType()); if (!logicalType) return blueprint.emitOpError("requires ranked logical output type"), failure(); RowStripPhysicalValue value; value.storage = storage; value.logicalType = logicalType; value.fragmentOffsets.append(blueprint.getFragmentOffsets().begin(), blueprint.getFragmentOffsets().end()); value.fragmentSizes.append(blueprint.getFragmentSizes().begin(), blueprint.getFragmentSizes().end()); if (blueprint.getIndexMap() != kRowStripIndexMap) return blueprint.emitOpError("requires the canonical row-strip index map"), failure(); auto storageType = dyn_cast(storage.getType()); if (!storageType || storageType != getRowStripStorageType(logicalType)) return blueprint.emitOpError("requires physical row-strip fragment storage"), failure(); return value; } static FailureOr lowerRowStripRelu(const RowStripPhysicalValue& input, spatial::SpatReluPlanOp planOp, PatternRewriter& rewriter) { return applyRowStripRelu(input.storage, input.logicalType, rewriter, planOp.getLoc()); } static FailureOr lowerRowStripBiasAdd(const RowStripPhysicalValue& input, spatial::SpatBiasAddPlanOp planOp, PatternRewriter& rewriter) { return applyRowStripBiasAdd(input.storage, input.logicalType, planOp.getBias(), rewriter, planOp.getLoc()); } static FailureOr materializeRowStripToDense(const RowStripPhysicalValue& rowStripValue, Location loc, PatternRewriter& rewriter) { if (rowStripValue.logicalType.getRank() != 4 || !rowStripValue.logicalType.hasStaticShape()) return failure(); auto [expectedOffsets, expectedSizes] = buildRowStripMetadata(rowStripValue.logicalType); if (!llvm::equal(rowStripValue.fragmentOffsets, expectedOffsets) || !llvm::equal(rowStripValue.fragmentSizes, expectedSizes)) return failure(); return materializeRowStripStorageToDense(rowStripValue.storage, rowStripValue.logicalType, rewriter, loc); } 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."; } void runOnOperation() override { ModuleOp moduleOp = getOperation(); 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); llvm::DenseMap rowStripValues; llvm::SmallPtrSet eraseAfterLowering; 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; for (Operation& op : llvm::make_early_inc_range(funcOp.getBody().front())) { if (auto planOp = dyn_cast(&op)) { FailureOr rowStripInput = getRowStripValue(rowStripValues, planOp.getInput()); auto rowStripBlueprint = llvm::find_if(planOp.getResult().getUsers(), [](Operation* user) { auto blueprint = dyn_cast(user); return blueprint && blueprint.getPhysicalLayout() == kRowStripLayout; }); if (rowStripBlueprint != planOp.getResult().getUsers().end()) { rewriter.setInsertionPoint(planOp); FailureOr lowered = lowerSelectedConv2DPlan( planOp, succeeded(rowStripInput) ? std::optional {rowStripInput->storage} : std::nullopt, /*emitRowStripLayout=*/true, rewriter); if (failed(lowered)) { planOp.emitOpError("failed to lower selected row-strip Spatial Conv plan"); signalPassFailure(); return; } auto blueprint = cast(*rowStripBlueprint); FailureOr rowStripValue = buildRowStripValue(blueprint, *lowered); if (failed(rowStripValue)) { signalPassFailure(); return; } rowStripValues[blueprint.getResult()] = *rowStripValue; eraseAfterLowering.insert(planOp); eraseAfterLowering.insert(blueprint); continue; } rewriter.setInsertionPoint(planOp); FailureOr lowered = lowerSelectedConv2DPlan(planOp, std::nullopt, /*emitRowStripLayout=*/false, rewriter); if (failed(lowered)) { planOp.emitOpError("failed to lower selected Spatial Conv plan"); signalPassFailure(); return; } rewriter.replaceOp(planOp, *lowered); continue; } if (auto planOp = dyn_cast(&op)) { if (succeeded(getRowStripValue(rowStripValues, planOp.getInput()))) { auto outputBlueprint = llvm::find_if(planOp.getResult().getUsers(), [](Operation* user) { auto blueprint = dyn_cast(user); return blueprint && blueprint.getPhysicalLayout() == kRowStripLayout; }); if (outputBlueprint == planOp.getResult().getUsers().end()) { planOp.emitOpError("row-strip Relu plan requires a row-strip blueprint result"); signalPassFailure(); return; } FailureOr input = getRowStripValue(rowStripValues, planOp.getInput()); rewriter.setInsertionPoint(planOp); FailureOr lowered = lowerRowStripRelu(*input, planOp, rewriter); if (failed(lowered)) { planOp.emitOpError("failed to lower selected row-strip Spatial Relu plan"); signalPassFailure(); return; } auto blueprint = cast(*outputBlueprint); FailureOr output = buildRowStripValue(blueprint, *lowered); if (failed(output)) { signalPassFailure(); return; } rowStripValues[blueprint.getResult()] = *output; eraseAfterLowering.insert(planOp); eraseAfterLowering.insert(blueprint); continue; } rewriter.setInsertionPoint(planOp); 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()); continue; } if (auto planOp = dyn_cast(&op)) { if (succeeded(getRowStripValue(rowStripValues, planOp.getInput()))) { auto outputBlueprint = llvm::find_if(planOp.getResult().getUsers(), [](Operation* user) { auto blueprint = dyn_cast(user); return blueprint && blueprint.getPhysicalLayout() == kRowStripLayout; }); if (outputBlueprint == planOp.getResult().getUsers().end()) { planOp.emitOpError("row-strip bias_add plan requires a row-strip blueprint result"); signalPassFailure(); return; } FailureOr input = getRowStripValue(rowStripValues, planOp.getInput()); rewriter.setInsertionPoint(planOp); FailureOr lowered = lowerRowStripBiasAdd(*input, planOp, rewriter); if (failed(lowered)) { planOp.emitOpError("failed to lower selected row-strip Spatial bias_add plan"); signalPassFailure(); return; } auto blueprint = cast(*outputBlueprint); FailureOr output = buildRowStripValue(blueprint, *lowered); if (failed(output)) { signalPassFailure(); return; } rowStripValues[blueprint.getResult()] = *output; eraseAfterLowering.insert(planOp); eraseAfterLowering.insert(blueprint); continue; } auto resultType = dyn_cast(planOp.getOutput().getType()); if (!resultType) { planOp.emitOpError("requires ranked output type"); signalPassFailure(); return; } rewriter.setInsertionPoint(planOp); FailureOr denseBias = materializeDenseBiasAddTensor(planOp.getBias(), resultType, rewriter, planOp.getLoc()); if (failed(denseBias)) { planOp.emitOpError("failed to materialize dense Conv-style bias"); signalPassFailure(); return; } 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()); continue; } if (auto materializeOp = dyn_cast(&op)) { if (materializeOp.getSourcePhysicalLayout() == kDenseLayout && materializeOp.getTargetPhysicalLayout() == kDenseLayout) { rewriter.replaceOp(materializeOp, materializeOp.getInput()); continue; } if (materializeOp.getSourcePhysicalLayout() != kRowStripLayout || materializeOp.getTargetPhysicalLayout() != kDenseLayout) { materializeOp.emitOpError("non-dense materialize_layout lowering is not supported yet"); signalPassFailure(); return; } FailureOr rowStripValue = getRowStripValue(rowStripValues, materializeOp.getInput()); if (failed(rowStripValue)) { materializeOp.emitOpError("expected a row-strip blueprint input during row-strip materialization"); signalPassFailure(); return; } rewriter.setInsertionPoint(materializeOp); FailureOr dense = materializeRowStripToDense(*rowStripValue, materializeOp.getLoc(), rewriter); if (failed(dense)) { materializeOp.emitOpError("failed to materialize selected row-strip layout back to dense NCHW"); signalPassFailure(); return; } rewriter.replaceOp(materializeOp, *dense); continue; } if (auto blueprintOp = dyn_cast(&op)) { if (blueprintOp.getPhysicalLayout() == kDenseLayout) { rewriter.replaceOp(blueprintOp, blueprintOp.getInput()); continue; } if (blueprintOp.getPhysicalLayout() != kRowStripLayout) { blueprintOp.emitOpError("non-dense blueprint lowering is not supported yet"); signalPassFailure(); return; } if (!eraseAfterLowering.contains(blueprintOp)) { blueprintOp.emitOpError("unhandled row-strip blueprint remained during LowerSpatialPlans"); signalPassFailure(); return; } } } bool erasedAny = true; while (erasedAny) { erasedAny = false; for (Operation& op : llvm::make_early_inc_range(funcOp.getBody().front())) { if (!eraseAfterLowering.contains(&op)) continue; if (!op.use_empty()) continue; eraseAfterLowering.erase(&op); rewriter.eraseOp(&op); erasedAny = true; } } if (!eraseAfterLowering.empty()) { for (Operation& op : funcOp.getBody().front()) if (eraseAfterLowering.contains(&op)) op.emitOpError("selected row-strip planning op could not be fully eliminated during LowerSpatialPlans"); signalPassFailure(); return; } ConversionTarget helperTarget(*ctx); helperTarget.addLegalDialect(); helperTarget.addLegalOp(); helperTarget.addIllegalOp(); helperTarget.markOpRecursivelyLegal(); RewritePatternSet helperPatterns(ctx); populateGemmPatterns(helperPatterns, ctx); populateTransposePatterns(helperPatterns, ctx); if (failed(applyPartialConversion(moduleOp, helperTarget, std::move(helperPatterns)))) { moduleOp.emitError("failed to lower helper ONNX ops emitted by selected Spatial plan lowering"); signalPassFailure(); return; } FrozenRewritePatternSet nestedHelperPatterns([&] { RewritePatternSet patterns(ctx); populateGemmPatterns(patterns, ctx); populateTransposePatterns(patterns, ctx); return patterns; }()); ConversionTarget nestedHelperTarget(*ctx); nestedHelperTarget.addLegalDialect(); nestedHelperTarget.addIllegalOp(); SmallVector computeLikeOps; funcOp.walk([&](Operation* op) { if (isa(op)) computeLikeOps.push_back(op); }); for (Operation* op : computeLikeOps) { if (failed(applyFullConversion(op, nestedHelperTarget, nestedHelperPatterns))) { op->emitOpError("failed to lower nested helper ONNX ops emitted by selected Spatial plan lowering"); signalPassFailure(); return; } } if (!verifyLogicalPhase("after nested helper conversions")) return; bool hasIllegalOps = false; moduleOp.walk([&](Operation* op) { if (isa(op)) return; if (isa(op) || op->getDialect()->getNamespace() == "onnx") { op->emitOpError("operation must not remain after LowerSpatialPlans"); hasIllegalOps = true; } }); if (hasIllegalOps) { signalPassFailure(); } else { dumpModule(moduleOp, "spatial1_graph"); spatial::SpatialDataflowExportStage exportMode = spatial::getSpatialDataflowExportStage(); if (spatial::shouldExportSpatialDataflowStage(exportMode, spatial::SpatialDataflowExportStage::Pre) && failed(spatial::exportSpatialDataflowCsvPre(funcOp))) { signalPassFailure(); return; } } if (!verifyLogicalPhase("at the end of LowerSpatialPlans")) return; } }; } // namespace std::unique_ptr createLowerSpatialPlansPass() { return std::make_unique(); } } // namespace onnx_mlir