- 文档
- 教程
- 人工智能
【免费下载链接】AISystem
AISystem 主要是指AI系统,包括AI芯片、AI编译器、AI推理和训练框架等AI全栈底层技术
导读:本文围绕 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)逐条匹配计算图节点完成的。以 ORT(ONNX Runtime)为例,它的基础图优化主要封装在继承自GraphTransformer与RewriteRule的优化实例中:RewriteRule从指定节点出发做局部保语义转换(消融、简化),GraphTransformer全局遍历节点寻找可优化子图,详见 01Optimizer.md。理解这个"模板 + 局部规则 + 全局遍历"的框架,再来看下面的每一种具体优化就会非常清晰。
二、常量折叠(Constant Folding)
常量折叠是编译器优化技术之一,通过对编译时常量或常量表达式进行计算来简化代码。落到推理引擎上,就是将计算图中可以预先确定输出值的节点替换成常量,并对计算图做一些结构简化。
考虑这样一个计算片段:
x = 5 y = 10 z = x * y这里x和y都是常量,因此它们的乘积z也可以在编译时被计算出来。常量折叠把这个乘法移到离线阶段完成,运行时不再需要乘法运算,代码等效变为:
z = 50推理引擎中典型的常量折叠可以细分为三种子优化:
- Constant folding(Const 折叠):如果一个 Op 的所有输入都是常量 Const,可以先计算好结果 Const 代替该 Op,而不必每次推理都重新计算一遍。
- Fold Const To ExpandDims(ExpandDims 折叠):ExpandDims Op 指定维度的输入是常量 Const,则把这个维度以参数形式折叠进 ExpandDims 算子。
- Fuse Const To Binary(Binary 折叠):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 的
concat; Seq2Out、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/Flatten:Global 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 Eliminate:Squeeze 压缩维度、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 的融合
(1)Conv + BN + Act
Conv 后跟着的 Batch Normal(BN)可以把 BN 的参数融合进 Conv。数学原理如下:
- 卷积是线性操作:
y = W*x + b(W 为权重,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 conv(2)Conv + Bias + Add
假设卷积输出为 X、偏置为 b、Add 操作的值为 a,则该序列的输出为Output = X + b + a。由于加法满足交换律和结合律,可把两个偏置相加得到新偏置b' = b + a,原序列简化为"Conv + Bias(值为 b')",Add 节点被融合进 Conv 的 Bias 参数。
(3)Conv + Scale + Act
Scale 是乘法操作:y = x * alpha,alpha 为可学习的尺度参数。参照 Conv+BN 的融合方式,把 Scale 的参数 alpha 融合进 Conv 的权重与偏置:
$$W' = alpha \cdot W$$
$$b' = alpha \cdot b$$
融合后得到的W'、b'直接用于 Conv,Scale 的计算被消除。
(4)Conv + MatMul + Act
Conv 后跟着的 MatMul 可以融合进 Conv 的 Weight,原理与上面的 Scale 融合相同(矩阵乘在数学上也是线性变换,其系数可以并入 Conv 的权重与偏置)。
上图中三组典型场景(Conv+BN+Act、Conv+Bias+Add、Conv+Scale/MatMul+Act)融合后的结果都是"一个带合并权重/偏置的 Conv",印证了"线性算子逐层吸收参数"这一核心思想。
4.2 示例二:围绕 Matmul / BN 的融合
(1)Matmul + Add:用 GEMM 代替
Matmul 后接 Add 可以直接使用 GEMM(通用矩阵乘,支持偏置)代替,把 Add 的偏置并入 GEMM 的 bias 参数。
(2)Matmul + 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 后会让数值进一步变小,可能导致精度下降。因此工程上需要在性能与精度之间做权衡。
(3)Mean + Add:用 Layer Norm 代替
Mean 后跟着 Add 的组合(先求均值再加偏置),本质上就是 Layer Norm 的归一化部分,可以整体使用 Layer Norm 算子代替。
(4)Batch Norm + Scale:s 和 b 直接融合进 BN
BN 操作后通常会有一个 Scale 操作用于恢复数据的原始分布:若 x 是 BN 的输出,则 Scale 为y = s * x + b(s、b 可学习)。把两个操作融合,即直接在 BN 算子内部完成 Scale。这样省掉了单独存储 BN 输出的内存与计算;同时由于 BN 和 Scale 是连续的线性操作,融合不会改变模型的表示能力。
(5)Matmul + Batch Norm
与 Conv + BN 的融合方式相类似,把 BN 的统计参数(mean、var、gamma、beta)按同样公式折算进 Matmul 的权重与偏置。
(6)Conv + ReLU / Conv + ReLU6 / Conv + Act
Act 激活操作与 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 -> Scale:BN 等价于 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 Replace:Shuffle 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, dim=1) # 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, dim=1) 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)
- 文档
- 教程
- 人工智能
【免费下载链接】AISystem
AISystem 主要是指AI系统,包括AI芯片、AI编译器、AI推理和训练框架等AI全栈底层技术
相关推荐
AISystem 推理引擎图优化进阶:融合算子替换、FlashAttention 与布局/内存优化实战解析
AISystem 推理引擎图优化进阶:融合算子替换、FlashAttention 与布局/内存优化实战解析 在 AI 推理引擎的离线模型转换链路中,计算图优化承
文档教程人工智能AISystem 模型优化深度解析:推理引擎计算图优化、算子融合与 Flash Attention 实战
AISystem 模型优化深度解析:推理引擎计算图优化、算子融合与 Flash Attention 实战 推理引擎要高效地执行训练好的模型,离不开离线阶段的“模
文档教程人工智能AISystem 前端优化:常量折叠(Constant Folding)原理与实现
AISystem 前端优化:常量折叠(Constant Folding)原理与实现 本文是 AISystem 仓库「AI 编译器前端优化」系列的第七篇,核心围绕
文档教程人工智能
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考