mlir 编译器学习笔记之五 -- 开发避坑

发布时间:2026/6/3 11:08:48

mlir 编译器学习笔记之五 -- 开发避坑 1、范围循环和索引循环的区别// 假设原始操作数: [A, A, A] (同一个值A被用了三次) // 范围for循环的问题 for (auto input : op.getDpsInputs()) { // 缓存了 [A, A, A] // 第一次迭代input A替换为 A_conv → 操作数变为 [A_conv, A, A] // 第二次迭代input A(仍然是缓存的A)但实际上应该处理 A_conv // 这会导致逻辑错误 } // 索引循环的正确行为 for (size_t i 0; i op.getDpsInputs().size(); i) { auto input op.getDpsInputs()[i]; // 每次都重新获取 // 第一次迭代input A替换为 A_conv → 操作数变为 [A_conv, A, A] // 第二次迭代重新获取 op.getDpsInputs()[1] A_conv (更新后的值) // 正确处理转换后的值 }2、replaceAllUsesExcept处理多输入的input时需要谨慎 (output不涉及)需要显式列出所有要排除的操作这要求我们知道所有不应该被修改的用户3、bufferize的实现要求操作数顺序先 所有的输入、再输出(attribute忽略)每类方言有自己的 BufferizableOpInterfaceImpl.cpp实现bufferize需要独立注册其中比较关键的是maybeBuffer getBuffer(rewriter, operand, options)获取tensor对应的memref4、elementwise 算子的所有张量参数具有相同的轴reduce不符合要求可以定义属性AllTensorParasWithSameAxesOpInterface (继承DestinationStyleOpInterface)def AllTensorParasWithSameAxesOpInterface : OpInterfaceAllTensorParasWithSameAxesOp, [DestinationStyleOpInterface] {let cppNamespace ::mlir;let description [{ All tensor parameters have same axes. }];let methods [ ];}5、动态shape纬度值不应该使用属性来描述以为属性要求编译时已知而动态shape是编译时未知的。6、涉及op-erase();类操作可能会破坏walk迭代器需要先收集待处理操作数然后统一处理7、处理函数参数时需要先更新函数签名 返回值处理的签名仍旧可以放到后面因为replaceAllUses替换只能更新消费者不能更新生产者bufferize时选项 -one-shot-bufferizebufferize-function-boundaries function-boundary-type-conversionidentity-layout-map use-encoding-for-memory-space参数处理顺序很重要先更新函数类型再更新块参数类型最后创建绑定操作更新函数类型func.setType(newFuncType)更新块参数entryBlock.getArgument(i).setType(newArgType)8、新增的算子需要 BufferizableOpInterfaceImpl.cpp 中定义相应的bufferize方法比如EncodingCastOp算子struct EncodingCastOpInterface : public DstBufferizableOpInterfaceExternalModelEncodingCastOpInterface,EncodingCastOp9、枚举的使用普通 enumenum Opcode { OP_COPY_IN}可以直接使用 OP_COPY_INclass类enum: enum class Opcode { OP_COPY_IN}需要 Opcode::OP_COPY_IN10、getODSOperands 是不会跳过optional参数而getOperands是获取实际输入的参数11、StringRef identity不拥有数据因此原来的数据释放后将导致值无效需要使用std::string identity才能进行值copy; StringRef 出错一般是因为传入的是局部变量struct SymExpr final : Expr {llvm::StringRefidentity; // 只是一个指针长度的包装不拥有数据SymExpr(llvm::StringRef v, SymExprCtx *ctx): Expr(ExprKind::Sym, ctx), identity(v) { // 只是复制指针不是复制数据}};12、纬度值对齐a. 动态值 IndexExpr wo IndexExpr(output, index, Builder);wo.alignTo(16).getValue()b. 静态值outputShape castRankedTensorType(output.getType()).getShape();llvm::SmallVectorint64_t outputMutable(outputShape.begin(), outputShape.end());llvm::alignTo(outputMutable[outputShape.size() - 1], 16); // 先去掉const属性注img2col可能需要对齐那么需要tensor.extract_slice将对齐shape后的结果回退13、转换生成vau_generic算子的时候需要注意操作数是否存在重叠auto genericOp buildVAUGeneric( rewriter, loc, srcs, scales, dsts, op.getOpLibraryCallName(), [](OpBuilder b, Location loc, ValueRange args) { IRMapping mapping; mapping.map(originValues, args); // 映射到新操作数的参数 auto *unitOp b.clone(*op, mapping); // 创建一个原始操作的副本但使用新的参数args替代原始操作数 b.createbau::YieldOp(loc, unitOp-getResults()); });效果 bau.some_op ins(%a, %b) outs(%c)转 bau.vau_generic ins(%a, %b, %c) { ^bb0(%arg0, %arg1, %arg2): %result bau.some_op ins(%arg0, %arg1) outs(%arg2) yield %result}注上述不能处理输入、输出相同的情况因为IRMapping是哈希表Value - Value14、If some tensor.empty op in scf.for and yield it out, or some op input and output are the same iter_arg, after bufferize may create an alloc inner and yield it out 解决方法增加memref.copy,将内部的变量复制回原来的缓冲区15、Linalg_Op确实是为结构化运算设计的barrier 作为控制流/同步操作不应继承它16、mlir中结构化的 scf.if/scf.for 不会切断父 BB分支是子区域17、std::unordered_mapstd::shared_ptrLogicalTensor, std::mapFractalType, std::setOperation* tensorTobeMap; 默认基于shared_ptr所管理的原始指针地址进行哈希可能导致多次运行时存在不一致的问题。解决方法新增自定义的hash18、算子插入点控制* setInsertionPointAfter 设置的是当前插入点不是永久固定在某个位置。每次 create 后插入点会自动移动到新创建的操作之后* setInsertionPointToStart则所有操作会连续插入在开头位置不会自动往后排。如果插入多个算子实际是逆序19、MLIR中不要先删除内层操作然后指望外层还能安全地持有它的旧指针。要么先匹配外层要么在每次使用前重新获取内层操作的有效指针。因为MLIR 的贪婪模式重写GreedyPatternRewriteDriver默认采用后序遍历操作先访问最深层的操作且重写过程中的替换/删除操作可能使外层模式持有的指针失效

相关新闻