
文档教程人工智能【免费下载链接】AISystemAISystem 主要是指AI系统包括AI芯片、AI编译器、AI推理和训练框架等AI全栈底层技术项目地址https://gitcode.com/GitHub_Trending/ai/AISystem点击查看免费下载导读本文围绕 AISystem 项目中推理引擎离线优化模块的基础图优化技术展开系统讲解保留计算图语义的五大基础优化手段——常量折叠、冗余节点消除、算子融合、算子替换与算子前移。文中不仅有可直接复用的数学推导与 Python/C 代码示例还结合本仓库 04Inference/05Optimize 下的源码与配套课程01Optimizer.md、03Extend.md讲清底层实现脉络。读完你将掌握离线图优化各类手法的作用对象、适用条件与工程实现思路能据此分析和改进自己模型的推理性能。一、离线图优化中的基础优化到底做什么在推理引擎的离线转换模块中图优化负责在保留计算图语义的前提下降低推理开销。上一章 计算图优化架构 已经指出推理引擎面对的是四种冗余结构冗余无效节点、重复子图、精度冗余FP32 可压缩、算法冗余kernel 实现层面的多余计算和读写冗余重复访存、内存访问不连续。其中针对结构冗余最常用的手段就是本节的主角——基础图优化Basic Optimization。基础图优化指的是涵盖所有保留语义的修改它不会改变计算图的基本结构和运算逻辑只负责在语义等价的约束下提升运行效率主要包括五种形态Constant folding 常量折叠针对模型推理中值不变的常量。如果某个操作的所有输入都是常量那么它的输出也必然是常量这个操作可以在编译/离线阶段直接执行把结果存成常量从而消除运行时的重复计算。Redundant eliminations 冗余节点消除消除模型中重复执行的冗余节点例如同样的计算被多次执行的情况既减少内存消耗又提升运算效率。Operation fusion 算子融合把几个连续的算子合并成一个算子减少中间结果的读写提升运算速度。Operation Replace 算子替换找到一个等效但更高效的算子来完成相同计算例如把高精度计算替换为低精度计算。Operation Forward 算子前移如果某个算子的输入与程序其他部分无关就把这个算子前移提前执行减少运行时的计算负担。从推理引擎的工程视角看这些优化大多不是通过运行时 JIT 编译实现的而是基于预先写好的优化模板Pass逐条匹配计算图节点完成的。以 ORTONNX Runtime为例它的基础图优化主要封装在继承自GraphTransformer与RewriteRule的优化实例中RewriteRule从指定节点出发做局部保语义转换消融、简化GraphTransformer全局遍历节点寻找可优化子图详见 01Optimizer.md。理解这个模板 局部规则 全局遍历的框架再来看下面的每一种具体优化就会非常清晰。二、常量折叠Constant Folding常量折叠是编译器优化技术之一通过对编译时常量或常量表达式进行计算来简化代码。落到推理引擎上就是将计算图中可以预先确定输出值的节点替换成常量并对计算图做一些结构简化。考虑这样一个计算片段x 5 y 10 z x * y这里x和y都是常量因此它们的乘积z也可以在编译时被计算出来。常量折叠把这个乘法移到离线阶段完成运行时不再需要乘法运算代码等效变为z 50推理引擎中典型的常量折叠可以细分为三种子优化Constant foldingConst 折叠如果一个 Op 的所有输入都是常量 Const可以先计算好结果 Const 代替该 Op而不必每次推理都重新计算一遍。Fold Const To ExpandDimsExpandDims 折叠ExpandDims Op 指定维度的输入是常量 Const则把这个维度以参数形式折叠进 ExpandDims 算子。Fuse Const To BinaryBinary 折叠Binary Op 的第二个输入是标量 Const把这个标量以参数形式折叠到 Binary Op 的属性中。2.1 Const 折叠把全常量子图算成一张常量表上图展示了最经典的场景两个常量输入经过 Op1接收两个常量和 Op2接收 Op1 的输出两个操作。既然 Op1 的所有输入都是常量离线阶段就能预先算出 Op1 的结果并把该结果作为新的常量直接喂给 Op2从而把 Op1 这个节点整体消掉。原来的Const1 - Op1 - Const2结构退化为Const3 - Op2。从计算图的存储结构看这正好对应仓库中 graph_op.fbs 所定义的数据模型每个 Op 通过inputIndexes、outputIndexes引用张量节点type标记算子类型main/parameter存放参数。常量折叠本质上是新建一个 Const 节点 重写下游 Op 的inputIndexes指向删除 Op1 及其对应边。配套的 serial.cpp 与 deserial.cpp 展示了这种以索引列表描述拓扑的网络结构如何被序列化写入net.mnn文件、又如何被反序列化读出离线优化 Pass 正是在这种图上做节点增删与边重连的。2.2 ExpandDims 折叠常量维度并入算子参数ExpandDims 的作用是给张量增加一个维度而增加哪个维度axis常常来自一个常量输入。优化时可以直接把这个常量维度堆叠进 ExpandDims 算子自身的参数里于是 ExpandDims 从两个输入数据 常量维度变成一个输入只有数据整张图的输入节点减少了一个。因为常量在图中既可以表现为一个算子节点也可能单独占用一块内存把它收编进算子属性就同时省掉了节点和内存开销。2.3 Binary 折叠标量常量收编进算子属性Binary 折叠的原理与 ExpandDims 折叠类似当 Binary如 Add、Mul、Sub、Div的第二个输入是标量 Const 时直接把这个标量作为 Binary 算子的一个参数保存计算时由算子内部完成与标量的运算。结果是图中减少了一个计算节点提高计算效率、节省计算资源。三、冗余节点消除Redundant Eliminations冗余节点消除的目标是在不改变图结构语义的前提下删除所有冗余节点。根据冗余产生的根源可以细分为四类Op 本身无意义、Op 参数无意义、Op 位置无意义、Op 前后反义此外还有基于公共子图公共子表达式的消除。3.1 Op 本身无意义根本不参与计算有些 Op 本身不参与计算在推理阶段直接去掉也不影响结果。典型的包括转换前后类型相同的cast只有一个输入 tensor 的concatSeq2Out、Identity、NoOp、Print、Assert、StopGradient、Split等算子以及通过模板删除的dropout等训练期算子推理时无意义。处理图中存在冗余算子时会遇到三种情况需要分别处理当前冗余算子的输出对下一个节点有意义直接去除冗余算子把上一个算子的输出与下一个算子的输入相连短路直连。当前冗余算子的输出对下一个节点无意义把图切成两个子图——一个子图是input - op1另一个子图是op2 - output互不影响。当前冗余算子的输入对下一个节点无意义只要这个节点的输入没有意义就轮循向上删除直到输入变得有意义为止。这种短路直连/子图切分/向上追溯的处理逻辑正是前述RewriteRule中satisfyCondition判断是否满足删除条件与apply执行边重连两个函数要实现的局部重写也是 ORT 中 Cast Elimination、BatchNormalization Elimination 等消融类优化实例的通用做法见 01Optimizer.md。3.2 Op 参数无意义参数退化使算子失效有些 Op 本身是有意义的但当参数被设置成某个特殊值时它就变得毫无作用。典型示例cast 算子当source源类型等于destination目标类型时类型转换毫无意义cast 算子可删除。ExpandDims 算子当输出的 shape 与输入的 shape 一致时要扩展的维度实际不存在变化ExpandDims 算子可删除。slice / pooling 算子当index_start等于 0、或index_end等于channel-1即切片覆盖整个通道范围、以及 pooling 算子的窗口为1x1时算子均可删除。实践证明这类参数退化算子的删除对模型性能提升有极大帮助。3.3 Op 位置无意义处于计算图中特殊位置而多余一些 Op 单独看有计算意义但在计算图的特定位置上变得多余无效的 Cast 算子例如紧邻的数据流已经保证类型一致时Cast 没有实际作用可直接删除。无效的 UnSqueeze 算子如果在计算图中没有起到实质作用形状本来就满足要求同样可以删除。无后续输出的 Op1输入数据给到某个算子 Op1但 Op1 的输出没有被其他算子接收说明该分支的计算结果从未被利用可以整支删除。Global pooling 之后的 Reshape/FlattenGlobal pooling 的输出本身已经是1x1xC之类的固定形状后面再接 Reshape 或 Flatten 不会改变结果可删除。Linear 之前的 Reshape/Flatten与上面类似Linear 前接的 Reshape/Flatten 不会改变 Linear 的输入语义可删除。两个相反的 Reshape 算子先 reshape 再 reshape 回原形状两个算子相互抵消可同时删除。两个相反的 Cast 算子一个把数据从类型 A 转成 B另一个再从 B 转回 A结果不改变数据类型两个算子都可删除。这些模式说明冗余判断不能只看单个节点还必须结合节点的位置上下文前驱、后继、输出是否被消费来分析这正是GraphTransformer全局遍历相比局部RewriteRule的优势所在。3.4 Op 前后反义语义相反的两个相邻 Op 同时删除前后两个相邻 Op 若执行的操作语义相反那么它们等效于什么都没做可以同时删除。常见组合Squeeze / ExpandDims EliminateSqueeze 压缩维度、ExpandDims 扩展维度二者反义。当连续出现的这两个 Op 指定的 axis 相等时可同时删除。Inverse Cast Eliminate两个连续的内存排布转换 Op 参数前后反义即前者的src1等于后者的dst2可同时删除。Quant Dequant Eliminate连续进行量化和反量化可同时删除。Concat Slice Elimination先合并Concat又按同样方式拆分Slice可同时删除进一步地可以用单个Slice操作直接替换Concat Slice组合抽象化计算过程并提高计算效率。以Concat Slice为例如果 Concat 把若干输入拼在一起后紧接着的 Slice 恰好只取回其中一段那么这张子图可以用一个直接从原输入取数的 Slice 替代消除了 Concat 产生的拼接与拷贝开销。3.5 公共子图优化公共子表达式消除在一个神经网络中如果几个子图的类型、参数和输入均相同则称它们为公共子图公共子表达式。对公共子图只需计算其中一个子图的值其他子图的值可以通过赋值/引用共享得到这个过程称为公共子图消除Common Subexpression Elimination, CSE。它是传统编译器中常用的优化手段经迁移后同样适用于深度学习编译器/推理引擎。基本实现思路是维护一张 MAP 表记录截止当前已处理过的同类型 Op对当前正在处理的 Op先查找 MAP 表若存在与当前 Op 类型相同的已处理 Op则逐个遍历比对——如果某个 Op 的输入和参数与当前 Op 完全相同则它们是公共子表达式结果可以互相替代复用已有的计算结果节点如果所有已记录 Op 都无法与当前 Op 匹配则将当前 Op 复制一份即插入记录返回供后续节点继续比对。图中展示的场景正是两条完全相同的计算分支同样的输入组合与同样的 Op优化后只保留一条计算路径消除重复的同结构子图减少冗余计算量。四、算子融合Operator Fusion算子融合是深度学习中常见的优化技术主要用于减少 GPU/CPU 内存访问从而提高模型执行效率。神经网络模型由大量算子卷积、激活、池化等组成每个算子的计算过程都伴随数据的读写。将多个算子融合为一个复合算子就可以减少中间结果的访存次数。以y ReLU(Conv(x))为例拆开执行需要两步temp Conv(x)先把结果写入内存y ReLU(temp)再从内存读回而融合后Conv的结果直接喂给ReLU无需额外的中间访存。算子融合不仅能减少访存次数还能提高计算密度让 GPU 等硬件更充分地利用计算资源。当然融合必须考虑算子的计算顺序与计算精度不能随意合并。下面按相邻 Op 存在数学上线性可融合关系来逐一展开。4.1 示例一围绕 Conv 的融合1Conv BN ActConv 后跟着的 Batch NormalBN可以把 BN 的参数融合进 Conv。数学原理如下卷积是线性操作y W*x bW 为权重x 为输入b 为偏置批量归一化y (x - mean(x)) / sqrt(var(x) eps) * gamma beta其中mean(x)、var(x)是均值和方差gamma、beta是可学习的尺度与偏移参数。把 Conv 和 BN 融合并重构成y W*x b的形式即把 BN 的gamma、beta融入 Conv 的权重 W 和偏置 b$$W \frac{gamma}{\sqrt{var(x) eps}} \cdot W$$$$b beta - \frac{gamma}{\sqrt{var(x) eps}} \cdot mean(x)$$融合后新的W、b直接用于 Conv 操作BN 的计算被完全消除。以下是一个简单可运行的 PyTorch 代码示例def fuse_conv_bn(conv, bn): # 计算新的权重和偏置 w conv.weight mean bn.running_mean var_sqrt torch.sqrt(bn.running_var bn.eps) beta bn.weight gamma bn.bias w_prime gamma / var_sqrt * w b_prime beta - gamma / var_sqrt * mean # 更新 Conv 的权重和偏置 conv.weight.data w_prime conv.bias.data b_prime return conv2Conv Bias Add假设卷积输出为 X、偏置为 b、Add 操作的值为 a则该序列的输出为Output X b a。由于加法满足交换律和结合律可把两个偏置相加得到新偏置b b a原序列简化为Conv Bias值为 bAdd 节点被融合进 Conv 的 Bias 参数。3Conv Scale ActScale 是乘法操作y x * alphaalpha 为可学习的尺度参数。参照 ConvBN 的融合方式把 Scale 的参数 alpha 融合进 Conv 的权重与偏置$$W alpha \cdot W$$$$b alpha \cdot b$$融合后得到的W、b直接用于 ConvScale 的计算被消除。4Conv MatMul ActConv 后跟着的 MatMul 可以融合进 Conv 的 Weight原理与上面的 Scale 融合相同矩阵乘在数学上也是线性变换其系数可以并入 Conv 的权重与偏置。上图中三组典型场景ConvBNAct、ConvBiasAdd、ConvScale/MatMulAct融合后的结果都是一个带合并权重/偏置的 Conv印证了线性算子逐层吸收参数这一核心思想。4.2 示例二围绕 Matmul / BN 的融合1Matmul Add用 GEMM 代替Matmul 后接 Add 可以直接使用 GEMM通用矩阵乘支持偏置代替把 Add 的偏置并入 GEMM 的 bias 参数。2Matmul Add / Scale / Div可一直往后融合Matmul 前或后接的 Add / Scale / Div 可以融合进 Matmul依据是如下的线性变换恒等式$$(in \cdot W bias0) bias1 in \cdot W (bias0 bias1)$$$$(in \cdot W bias0) \cdot scale1 in \cdot (W \cdot scale1) (bias0 \cdot scale1)$$$$(in \cdot W bias0) / scale2 in \cdot (W / scale2) (bias0 / scale2)$$因此可以沿着这条链一直融合下去。需要注意这种融合在性能上一定有提升但在精度上可能产生牺牲特别是融合了 mul 算子时——原矩阵乘的 weight、bias 以及 scale 通常都是小于 1 的数值把 scale 融合进 weight 和 bias 后会让数值进一步变小可能导致精度下降。因此工程上需要在性能与精度之间做权衡。3Mean Add用 Layer Norm 代替Mean 后跟着 Add 的组合先求均值再加偏置本质上就是 Layer Norm 的归一化部分可以整体使用 Layer Norm 算子代替。4Batch Norm Scales 和 b 直接融合进 BNBN 操作后通常会有一个 Scale 操作用于恢复数据的原始分布若 x 是 BN 的输出则 Scale 为y s * x bs、b 可学习。把两个操作融合即直接在 BN 算子内部完成 Scale。这样省掉了单独存储 BN 输出的内存与计算同时由于 BN 和 Scale 是连续的线性操作融合不会改变模型的表示能力。5Matmul Batch Norm与 Conv BN 的融合方式相类似把 BN 的统计参数mean、var、gamma、beta按同样公式折算进 Matmul 的权重与偏置。6Conv ReLU / Conv ReLU6 / Conv ActAct 激活操作与 Conv 虽然连续但计算过程独立推理时先算 Conv 层访问 Conv 输出位置再计算 ReLU 层即第二次访存。这导致同一份输出被访问两遍增加了访存时间、降低推理效率。优化思路是算出 Conv 结果后立即进行 Act 激活计算、直接把最终结果输出则输出只需访存一次。计算量不变但访存次数减半推理速度明显提升。五、算子替换Operator Substitution算子替换是将模型中某些算子替换为功能相同或相似、但计算效率更高或对特定硬件优化更好的算子。例如两个连续的卷积层在某些情况下可以被替换为一个等效的卷积层减少计算量用深度可分离卷积depthwise separable convolution替换标准卷积可以显著减少计算量而保持相似性能。算子替换要求替换后的模型在功能上与原模型尽可能接近以保证模型性能不因替换而下降。其本质是通过合并同类项、提取公因式等数学方法简化算子的计算公式并把简化后的公式映射到某类算子达到降低计算量、降低模型大小的目的。实际应用中算子替换通常与算子融合等其他优化技术结合使用。5.1 一换一替换一个算子换一个算子一换一替换的核心收益是减少推理引擎需要单独实现与支持的 Op 数量把不常见的算子归一化到常用算子上。典型示例MatMul - Conv2D把矩阵乘变成卷积因为一般框架对 Conv 做了更多优化GEMM 化、Winograd 等Linear - Conv2D把全连接层转变成 1x1 Conv同样是因为 Conv 的优化更充分Batch Normal - ScaleBN 等价于 Scale Op转成 Scale 计算量更少、速度更快pReLU - Leaky ReLU在不影响性能和精度的前提下把 pReLU 归一化为 Leaky ReLU让推理引擎聚焦于有限数量的算法实现Conv - Linear (After global pooling)在 Global Pooling 之后Conv 算子转换成为全连接层此时卷积退化为逐通道乘加等价于全连接。5.2 一换多替换一个算子换一组算子一换多替换的目标是减少推理引擎需要单独实现及支持的 Op 数量某些复合算子如 Shuffle Channel、Pad-2、ShapeN、Group Conv在大部分框架中没有单独实现可以用基础算子组合等价实现。Shuffle Channel ReplaceShuffle Channel Op 大部分框架缺乏单独实现可以通过组合Reshape Permute实现import torch import torch.nn.functional as F def shuffle_channel(x, groups): batchsize, num_channels, height, width x.data.size() channels_per_group num_channels // groups # reshape x x.view(batchsize, groups, channels_per_group, height, width) # permute x x.permute(0, 2, 1, 3, 4).contiguous() # flatten back x x.view(batchsize, -1, height, width) return xPad Replace将老版 ONNX 的 pad-2 的 pads 从参数形式转成输入形式import torch.nn.functional as F def pad_replace(x, pads): return F.pad(x, pads)ShapeN Replace将 ShapeN Op 通过组合多个 Shape 的方式实现def shape_n_replace(*xs): return [x.shape for x in xs]Group Conv Replace把 Group 卷积通过组合 Slice、Conv 实现按 group 切片分别卷积再拼接import torch import torch.nn.functional as F def group_conv_replace(x, weight, bias, stride, padding, dilation, groups): # slice input xs torch.chunk(x, groups, dim1) # apply conv for each slice ys [F.conv2d(xi, wi, bi, stride, padding, dilation, 1) for xi, wi, bi in zip(xs, weight, bias)] # concat back y torch.cat(ys, dim1) return y可以看到一换多的代价是可能把单个算子展开成多个算子图变复杂、节点数变多因此它更适用于框架根本不支持该算子的场景把复杂度从框架实现转移到离线图转换阶段统一处理。六、算子前移Operation Forward算子前移指将某些计算过程提前执行以减少重复计算、提高运行效率。典型场景模型中有一部分计算是固定的——无论输入是什么这部分计算的结果都不变。此时可以把这部分计算提前完成并保存结果实际计算时直接使用保存的结果避免重复计算。需要注意算子前移同样必须考虑模型的计算顺序和数据依赖性不能随意把计算过程提前。典型示例Slice and Mul把切片后的乘法计算前移避免对整张张量做不必要的乘法再切片先裁减再计算减少计算量Bit shift and Reduce Sum利用算术简化中的交换律对计算算子进行交换减少数据的传输和访存次数。从工程实现看算子前移通常需要先做数据依赖分析判断算子是否可以被提前而不破坏依赖再配合常量折叠把提前计算的结果固化为常量节点这与第一节提到的先验知识模板化思路一致。七、小结与思考本节内容主要讨论了计算图优化中的常量折叠和冗余节点消除详细解读了如何借助这两种方法优化复杂计算图、提高计算效率、减少不必要的计算任务并进一步覆盖了算子融合、算子替换、算子前移三类基础优化手段常量折叠Const 折叠、ExpandDims 折叠、Binary 折叠把离线可确定的计算固化进常量或算子参数消除运行时计算冗余节点消除从Op 本身无意义、参数无意义、位置无意义、前后反义四个维度识别冗余并通过公共子图消除CSE合并等价子图算子融合围绕 Conv、Matmul、BN 等线性算子链的数学等价变换用参数吸收减少中间访存算子替换一换一归一化到更优算子与一换多用基础算子组合实现复合算子降低推理引擎的支持成本算子前移基于依赖分析把固定计算提前减少重复计算。值得进一步思考的点深入探讨了 cast、ExpandDims、Squeeze、Slice 等算子在神经网络中的搭配与使用以及它们在不同组合情况下的优化可能性有助于在神经网络设计和计算过程中减少重复计算与冗余计算提高整体性能算子融合如 Mul 融合进 Matmul在带来性能提升的同时可能造成数值精度损失工程落地时需要设置融合安全门限或精度回退机制基础优化之后的扩展优化针对特定硬件的复杂优化与布局 内存优化会在后优化阶段继续展开二者与本节的基础优化共同构成完整的离线优化流水线详见 03Extend.md。附本节配套资源本小节配套 PPT 与字幕文件04Inference/05Optimize/02Basic.pdf、04Inference/05Optimize/02Basic.pptx配套图优化架构总览01Optimizer.md配套扩展优化Flash Attention、布局转换、内存优化03Extend.md计算图数据结构示例Op 的inputIndexes/outputIndexes/type/parameter定义graph_op.fbs计算图序列化与反序列化示例serial.cpp / deserial.cpp赞分享文档教程人工智能【免费下载链接】AISystemAISystem 主要是指AI系统包括AI芯片、AI编译器、AI推理和训练框架等AI全栈底层技术项目地址https://gitcode.com/GitHub_Trending/ai/AISystem点击查看免费下载相关推荐AISystem 推理引擎图优化进阶融合算子替换、FlashAttention 与布局/内存优化实战解析AISystem 推理引擎图优化进阶融合算子替换、FlashAttention 与布局/内存优化实战解析 在 AI 推理引擎的离线模型转换链路中计算图优化承文档教程人工智能AISystem 模型优化深度解析推理引擎计算图优化、算子融合与 Flash Attention 实战AISystem 模型优化深度解析推理引擎计算图优化、算子融合与 Flash Attention 实战 推理引擎要高效地执行训练好的模型离不开离线阶段的“模文档教程人工智能AISystem 前端优化常量折叠Constant Folding原理与实现AISystem 前端优化常量折叠Constant Folding原理与实现 本文是 AISystem 仓库「AI 编译器前端优化」系列的第七篇核心围绕文档教程人工智能上一篇15年编辑器传奇TextMate从1.0到2.0的进化之路下一篇Wand-Enhancer 完整指南WeMod 本地补丁的原理、构建流程与失效回退创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考