#include "mlir/Conversion/AffineToStandard/AffineToStandard.h" #include "mlir/Dialect/Affine/IR/AffineOps.h" #include "mlir/Dialect/Arith/IR/Arith.h" #include "mlir/Dialect/Bufferization/IR/Bufferization.h" #include "mlir/Dialect/Func/IR/FuncOps.h" #include "mlir/Dialect/MemRef/IR/MemRef.h" #include "mlir/Dialect/SCF/IR/SCF.h" #include "mlir/Dialect/SCF/Utils/Utils.h" #include "mlir/Dialect/Tensor/IR/Tensor.h" #include "mlir/IR/BuiltinDialect.h" #include "mlir/IR/BuiltinOps.h" #include "mlir/IR/BuiltinTypeInterfaces.h" #include "mlir/IR/BuiltinTypes.h" #include "mlir/IR/PatternMatch.h" #include "mlir/IR/SymbolTable.h" #include "mlir/IR/Value.h" #include "mlir/Pass/Pass.h" #include "mlir/Transforms/FoldUtils.h" #include "mlir/Transforms/WalkPatternRewriteDriver.h" #include "llvm/ADT/StringRef.h" #include "llvm/Support/Casting.h" #include "llvm/Support/raw_ostream.h" #include #include #include "Common/IR/ShapeUtils.hpp" #include "Common/IR/ConstantUtils.hpp" #include "Common/PimCommon.hpp" #include "Common/Support/CheckedArithmetic.hpp" #include "Conversion/ONNXToSpatial/ONNXToSpatialVerifier.hpp" #include "Conversion/ONNXToSpatial/Common/Common.hpp" #include "Conversion/SpatialToPim/Common.hpp" #include "Conversion/SpatialToPim/Patterns.hpp" #include "Dialect/Pim/PimOps.hpp" #include "Dialect/Spatial/SpatialOps.hpp" #include "Pass/PIMPasses.h" #include "SpatialToPimPass.hpp" using namespace mlir; using namespace onnx_mlir; using namespace pim; namespace onnx_mlir { static FailureOr createZeroPaddedTensor(IRRewriter& rewriter, Location loc, Value value, RankedTensorType resultType) { auto sourceType = cast(value.getType()); SmallVector lowPads(sourceType.getRank(), rewriter.getIndexAttr(0)); SmallVector highPads; highPads.reserve(sourceType.getRank()); for (auto [sourceDim, resultDim] : llvm::zip(sourceType.getShape(), resultType.getShape())) highPads.push_back(rewriter.getIndexAttr(resultDim - sourceDim)); auto padOp = tensor::PadOp::create(rewriter, loc, resultType, value, lowPads, highPads); auto* padBlock = new Block(); for (int64_t i = 0; i < sourceType.getRank(); ++i) padBlock->addArgument(rewriter.getIndexType(), loc); padOp.getRegion().push_back(padBlock); rewriter.setInsertionPointToStart(padBlock); auto zero = getOrCreateConstant( rewriter, padOp.getOperation(), rewriter.getZeroAttr(sourceType.getElementType()), sourceType.getElementType()); tensor::YieldOp::create(rewriter, loc, zero); rewriter.setInsertionPointAfter(padOp); return padOp.getResult(); } static FailureOr padHVectorInputToCrossbarSize(IRRewriter& rewriter, Location loc, Value vector) { auto vectorType = cast(vector.getType()); ArrayRef shape = vectorType.getShape(); assert(isHVectorShape(shape) && "expected a horizontal vector"); assert(shape[1] <= static_cast(crossbarSize) && "vector width must fit in one crossbar"); if (shape[1] == static_cast(crossbarSize)) return vector; auto paddedType = RankedTensorType::get( {shape[0], static_cast(crossbarSize)}, vectorType.getElementType(), vectorType.getEncoding()); return createZeroPaddedTensor(rewriter, loc, vector, paddedType); } void onnx_mlir::raptor::SpatialToPimPass::runOnOperation() { outputTensors.clear(); operationsToRemove.clear(); ModuleOp moduleOp = getOperation(); MLIRContext* ctx = moduleOp.getContext(); auto entryFunc = getPimEntryFunc(moduleOp); if (failed(entryFunc)) { moduleOp.emitError("failed to locate the PIM entry function during Spatial-to-PIM lowering"); signalPassFailure(); return; } func::FuncOp funcOp = *entryFunc; if (failed(verifyScheduledSpatialInvariants(funcOp))) { funcOp.emitOpError( "scheduled Spatial verification failed at the start of SpatialToPim"); signalPassFailure(); return; } IRRewriter rewriter(&getContext()); OperationFolder constantFolder(&getContext()); ConversionTarget target(*ctx); target.addLegalDialect(); target.addLegalOp(); RewritePatternSet initialPatterns(ctx); populateInitialPatterns(initialPatterns); if (failed(applyPartialConversion(moduleOp, target, std::move(initialPatterns)))) { moduleOp.emitError("failed to lower required Spatial ops to the initial PIM form"); signalPassFailure(); return; } RewritePatternSet globalTensorPatterns(ctx); populateGlobalTensorMaterializationPatterns(globalTensorPatterns); walkAndApplyPatterns(moduleOp, std::move(globalTensorPatterns)); auto returnOp = cast(funcOp.front().getTerminator()); addReturnOutputBuffers(returnOp, rewriter); if (failed(allocateAndInitializeCoreLocalVariables(funcOp, rewriter))) { funcOp.emitOpError("failed to allocate or initialize core-local tensors during Spatial-to-PIM lowering"); signalPassFailure(); return; } for (auto computeOp : funcOp.getOps()) { markOpToRemove(computeOp); if (failed(lowerComputeOp(computeOp, rewriter, constantFolder))) { computeOp.emitOpError("failed to lower spat.scheduled_compute to pim.core"); signalPassFailure(); return; } } for (auto computeBatchOp : funcOp.getOps()) { markOpToRemove(computeBatchOp); if (failed(lowerComputeBatchOp(computeBatchOp, rewriter))) { computeBatchOp.emitOpError("failed to lower spat.scheduled_compute_batch to pim.core_batch"); signalPassFailure(); return; } } RewritePatternSet initialTensorPackingPatterns(ctx); populateTensorPackingPatterns(initialTensorPackingPatterns); walkAndApplyPatterns(funcOp, std::move(initialTensorPackingPatterns)); eraseUnusedTensorPackingOps(funcOp, rewriter); SmallVector receiveOps; for (auto op : funcOp.getOps()) receiveOps.push_back(op); for (auto receiveOp : receiveOps) { bool onlyPendingRemovalUsers = llvm::all_of( receiveOp->getUsers(), [&](Operation* user) { return llvm::is_contained(operationsToRemove, user); }); if (onlyPendingRemovalUsers) { markOpToRemove(receiveOp); continue; } } RewritePatternSet coreBodyPatterns(ctx); populateCoreBodyPatterns(coreBodyPatterns); populateAffineToStdConversionPatterns(coreBodyPatterns); FrozenRewritePatternSet frozenCoreBodyPatterns(std::move(coreBodyPatterns)); ConversionTarget coreBodyTarget(*ctx); coreBodyTarget.addLegalDialect(); coreBodyTarget.addLegalOp(); SmallVector coreOps; funcOp.walk([&](pim::PimCoreOp coreOp) { coreOps.push_back(coreOp); }); for (auto coreOp : coreOps) { if (failed(applyFullConversion(coreOp.getOperation(), coreBodyTarget, frozenCoreBodyPatterns))) { coreOp.emitOpError("failed to convert nested Spatial ops inside pim.core"); signalPassFailure(); return; } } SmallVector coreBatchOps; funcOp.walk([&](pim::PimCoreBatchOp coreBatchOp) { coreBatchOps.push_back(coreBatchOp); }); for (auto coreBatchOp : coreBatchOps) { if (failed(applyFullConversion(coreBatchOp.getOperation(), coreBodyTarget, frozenCoreBodyPatterns))) { coreBatchOp.emitOpError("failed to convert nested Spatial ops inside pim.core_batch"); signalPassFailure(); return; } } if (failed(enlargeVMMOutTensorsToCrossbarSize(funcOp, rewriter))) { funcOp.emitOpError("failed to enlarge VMM output tensors to crossbar size"); signalPassFailure(); return; } replaceReturnWithOutputBuffers(returnOp, rewriter); eraseOpsToRemove(); RewritePatternSet finalTensorPackingPatterns(ctx); populateTensorPackingPatterns(finalTensorPackingPatterns); walkAndApplyPatterns(funcOp, std::move(finalTensorPackingPatterns)); eraseUnusedTensorPackingOps(funcOp, rewriter); ConversionTarget communicationTarget(*ctx); communicationTarget.addLegalDialect(); communicationTarget.addLegalOp(); communicationTarget.addIllegalOp(); RewritePatternSet communicationPatterns(ctx); populateChannelLoweringPatterns(communicationPatterns); if (failed(applyFullConversion(funcOp, communicationTarget, std::move(communicationPatterns)))) { funcOp.emitOpError("failed to lower Spatial communication ops to PIM communication ops"); signalPassFailure(); return; } hoistAndUniquifyIndexConstants(funcOp, rewriter); // Dump to file for debug dumpModule(moduleOp, "pim0"); } LogicalResult raptor::SpatialToPimPass::enlargeVMMOutTensorsToCrossbarSize(func::FuncOp funcOp, IRRewriter& rewriter) { bool hasFailure = false; funcOp.walk([&](PimVMMOp vmmOp) { auto outputType = cast(vmmOp.getOutput().getType()); ArrayRef outputShape = outputType.getShape(); assert(isHVectorShape(outputShape) && "expected a horizontal vector output"); assert(outputShape[1] <= static_cast(crossbarSize) && "output width must fit in one crossbar"); rewriter.setInsertionPoint(vmmOp); auto paddedInput = padHVectorInputToCrossbarSize(rewriter, vmmOp.getLoc(), vmmOp.getInput()); if (failed(paddedInput)) { hasFailure = true; return WalkResult::interrupt(); } auto paddedOutputType = RankedTensorType::get( {outputShape[0], static_cast(crossbarSize)}, outputType.getElementType(), outputType.getEncoding()); Value paddedOutputBuffer = outputShape[1] == static_cast(crossbarSize) ? vmmOp.getOutputBuffer() : createEmptyTensorFromShaped(rewriter, vmmOp.getLoc(), paddedOutputType).getResult(); vmmOp.getInputMutable().assign(*paddedInput); vmmOp.getOutputBufferMutable().assign(paddedOutputBuffer); vmmOp.getOutput().setType(paddedOutputType); if (outputShape[1] == static_cast(crossbarSize)) return WalkResult::advance(); SmallVector offsets = {rewriter.getIndexAttr(0), rewriter.getIndexAttr(0)}; SmallVector sizes = {rewriter.getIndexAttr(outputShape[0]), rewriter.getIndexAttr(outputShape[1])}; SmallVector strides = {rewriter.getIndexAttr(1), rewriter.getIndexAttr(1)}; rewriter.setInsertionPointAfter(vmmOp); auto sliceOp = tensor::ExtractSliceOp::create(rewriter, vmmOp.getLoc(), outputType, vmmOp.getOutput(), offsets, sizes, strides); SmallPtrSet exceptions = {vmmOp, sliceOp}; vmmOp.getOutput().replaceAllUsesExcept(sliceOp.getResult(), exceptions); return WalkResult::advance(); }); return success(!hasFailure); } LogicalResult raptor::SpatialToPimPass::allocateAndInitializeCoreLocalVariables(func::FuncOp funcOp, IRRewriter& rewriter) { Location loc = funcOp.getLoc(); OperationFolder constantFolder(funcOp.getContext()); bool hasFailure = false; auto insertMemCopyHostToDev = [&](Value inputTensor, int64_t elementsOffset) { auto tensorType = cast(inputTensor.getType()); Type elementType = tensorType.getElementType(); if (!hasByteSizedElementType(elementType)) return; size_t elementByteSize = getElementTypeSizeInBytes(elementType); rewriter.setInsertionPointAfter(inputTensor.getDefiningOp()); auto deviceTensor = tensor::EmptyOp::create(rewriter, loc, tensorType.getShape(), elementType); auto offsetBytes = pim::checkedMul( static_cast(elementsOffset), elementByteSize, deviceTensor.getOperation(), "host input byte offset"); auto byteSize = pim::getCheckedShapedTypeSizeInBytes(tensorType, deviceTensor.getOperation(), "host input copy byte size"); auto sizeAttr = succeeded(byteSize) ? pim::getCheckedI32Attr(rewriter, deviceTensor.getOperation(), *byteSize, "host input copy byte size") : FailureOr(failure()); if (failed(offsetBytes) || failed(sizeAttr)) { hasFailure = true; return; } auto memCopyHostToDevOp = PimMemCopyHostToDevOp::create( rewriter, loc, tensorType, getOrCreateIndexConstant(constantFolder, deviceTensor.getOperation(), 0), getOrCreateIndexConstant(constantFolder, deviceTensor.getOperation(), static_cast(*offsetBytes)), deviceTensor, inputTensor, *sizeAttr); rewriter.replaceAllUsesExcept(inputTensor, memCopyHostToDevOp.getResult(), {memCopyHostToDevOp}); }; for (auto& op : funcOp.getBody().getOps()) if (auto computeOp = dyn_cast(op)) { if (!computeOp.getInputs().empty() || computeOp.getBody().front().getNumArguments() != 0) continue; for (auto getGlobal : computeOp.getOps()) { if (getGlobal.getName().starts_with("arg") || getGlobal.getName().starts_with("const_")) { assert(getGlobal->hasOneUse() && "global must have a single entry point in the compute"); auto toTensorOpValue = *getGlobal->getUsers().begin()->getResults().begin(); insertMemCopyHostToDev(toTensorOpValue, 0); } } } return success(!hasFailure); } void raptor::SpatialToPimPass::markOpToRemove(Operation* op) { if (!llvm::is_contained(operationsToRemove, op)) operationsToRemove.push_back(op); } void raptor::SpatialToPimPass::eraseOpsToRemove() { for (Operation* op : operationsToRemove) { op->dropAllUses(); op->erase(); } } std::unique_ptr createSpatialToPimPass() { return std::make_unique(); } } // namespace onnx_mlir