把激活函数塞进卷积:少一个节点之前,先保住那条量化边界

融合卷积与激活之前,保住中间量化与合法形状。

Posted by Bruce Lee on 2026-04-25

系列目录 · 算子与布局 · Read in English

“这两个节点挨着,融合以后应该会快吧?”

“可能。先说清楚中间那个张量原来是什么。”

融合最诱人的画面是删掉图上的一个圆圈。可被删掉的不只是一个名字,还可能是一段真实的存储、一次量化、一种数据类型和一个被其他节点读取的值。若只把后处理开关打开,忘记中间语义,图确实更短了,答案也可能更远了。

先检查融合前提

教学计算为 u=Conv(x,w,b)y=GELU(u)。假设目标有卷积后处理路径,能够在卷积产生结果后接入查表或分段线性近似。融合的目标是让 y 直接成为卷积节点的最终输出,减少独立激活阶段的调度及中间搬运。是否真的消除这些成本,应由生成结果与测量验证。

第一个条件是 u 的使用者。如果旁边还有 z=Abs(u),把卷积输出直接改成激活后的 y,会让 z 看到完全不同的值。历史规则要求生产者只有一个使用者,并拒绝已经带融合激活的生产者。这个条件看似保守,却非常容易证明。复制卷积保留另一条分支也许能实现融合,但可能重复大量计算,不能作为没有成本模型的默认方案。

分组属性也是明确的限制:所检查的这几条卷积与转置卷积融合规则都要求 group=1。分组卷积是否先被合法拆解、拆解后的节点能否再融合,属于另一条需要单独验证的路径,不能由这些融合提交直接推出。

第二个条件是激活本身。图中一类查表节点可以表达多种非线性函数,不代表同一卷积后处理路径支持所有函数。历史的转置卷积先接受 GELU,后续扩展到 Sigmoid;普通卷积接受受限的两个函数。拒绝其他函数是能力边界,不应该被一个“非线性融合”大标题抹平。

其中一个提交标题看上去只是删除旧文件名,实际同时改变了匹配条件、代码生成验证和测试期望,把另一种激活加入支持。这类细节特别适合提醒读者:目录重命名可以伴随语义扩展,评审不能只检查文件是否搬过去。测试中原先“保留独立激活”的案例也变成了“应该融合”,证据来自断言变化。

第三个条件是形状关系。逐元素激活不改变元素的对应关系,但图优化期间可能出现等元素数、不同外观 rank 的类型。已查看的不同融合规则对 shape 的检查并不完全相同:有的要求形状相等,有的检查元素数相等。公开的通用结论应当是“必须证明元素一一对应且布局兼容”,不能把任一局部检查说成充分条件。

保留中间量化边界

真正容易丢失的是中间量化。假设卷积输出 u 使用比例 su,激活输出 y 使用比例 sy。LUT 的分段或查表输入依赖 u 的编码坐标。融合节点最终类型变成 y 的类型后,如果再从最终输出类型推导 LUT 输入,就可能错误地拿 sy 去解释卷积中间结果。

教学假设 su=0.25sy=0.0625,且两个 zero point 都为 0,同样的整数 8 分别代表 2 和 0.5。用错比例,会让查表访问的函数位置产生系统差异。融合后保留“激活前的类型描述”,就能明确区分生产者内部边界与节点最终输出。历史重写保存了这份信息,并携带 LUT、函数种类和表头数据。

可以把融合计划写成:

1
2
3
4
5
6
fused = ComputeWithActivation(
inputs = original_compute_inputs,
table = activation_table,
intermediate_type = type_of_u,
output_type = type_of_y,
activation_kind = supported_kind)

这份伪代码不对应原工程接口,只表达契约。关键不是属性叫什么,而是中间值的数值域不能随节点被删除而失忆。

代码生成与回归证据

图重写成功只是前半程。代码生成还必须取得 LUT 的已分配地址,构造后处理参数,选择正确函数编码,关联描述符,再在发出计算指令前装载 LUT 参数。历史修改在地址、描述符、参数内存和内核发射各层补齐链路。如果只看到后处理使能位,就宣布融合完成,可能遗漏一个仍未初始化的参数寄存器。

同时应验证属性集合完整。存在 LUT 操作数但缺少中间类型或表头,属于不完整融合状态;没有 LUT 却残留融合属性,也应拒绝。历史代码对这些组合加入诊断,并限制受支持的中间存储类型。这样外部手写 IR 或其他 pass 构造的异常状态,不会绕过重写规则直接进入发射阶段。

为什么普通卷积还要测试?因为增加可选融合输入最容易损害已有路径。无激活时,不应装载无用 LUT 参数,也不应意外打开后处理。历史测试同时查看融合与非融合指令,并确认融合后不再单独发出相应的向量查表操作。这比“图中少了一个节点”更接近实际执行行为。

但要小心:没有独立查表指令,只能证明生成形态改变,不能证明数值误差可接受。分段线性近似、定点参数精度、饱和与舍入仍需要专门数值测试。本文建议在激活曲线转折区、接近饱和区和量化边界附近加密取点,而不是仅用宽区间均匀随机样本。

一个有用的错误对照是保留两个教学实现:先卷积再独立激活,与融合实现。比较时应使用相同的中间量化约定。如果参考实现直接在高精度实数卷积结果上做激活,而融合路径模拟的是先量化再激活,两者不同可能来自参考定义不一致。优化正确性通常要求保持原图语义,而不是悄悄更换成另一个“看上去更精确”的函数。

成本、语义与安全重写

性能账单可以先做定性分解:潜在收益包括减少中间结果读写、任务启动和同步;新增或保留成本包括 LUT 参数装载、表数据访问,以及后处理吞吐对计算流水的限制。若中间张量本来就在片上缓存,省掉的成本与写回大内存完全不同。融合后还可能改变并行调度,不能把节点数量减少直接换算成速度百分比。

建议测量时按输出大小、通道规模和激活类型分组,并记录中间存储是否真的消失、LUT 是否重复装载、总体延迟与算术单元利用情况。对小算子,参数与启动成本可能主导;对大算子,计算或带宽可能主导。

融合还有一条容易被名字掩盖的限制:同样叫 GELU,不同表示可能采用不同近似、不同输入范围或不同的分段参数。匹配函数名字只能找到候选,真正执行时还需要保留生成好的表数据与表头含义。若把原激活的 LUT 换成另一个“也叫同名函数”的默认表,误差约定可能已经改变。历史规则把现有 LUT 及相关信息带到融合节点,是维护原有近似路径的重要一环。

可以把融合前后的语义写成两条数据链。原图先产生某种量化编码的 u,再按该编码访问激活近似,最后得到 y;融合图应在内部保留同样的 u 编码边界,即使它不再成为一个独立内存张量。边界不再落地,不代表边界不再存在。若目标后处理直接接收更宽的累加值,那么还需证明它怎样重现原本的缩放与舍入,或者明确给出允许的数值差异。

图重写中的删除顺序也值得评审。新融合节点先创建并替换激活结果,再删除原生产者,需要确保没有遗漏使用者、没有留下指向旧节点的引用,也没有把新节点插到依赖之前。单使用者限制有助于简化这部分证明。若未来放宽到多结果或跨区域,原有几十行模式就未必足够,支配关系、区域边界和副作用都需要加入考虑。

位置与诊断信息也是产物质量的一部分。融合以后出错,用户应该能回到卷积和激活这两个来源,而不只是看见一个新造的匿名节点。历史规则组合了来源位置,这有助于保留线索。通用建议是把来源追踪用于解释错误,但不要把所有源属性不加区分地复制为执行属性;某些属性描述的是旧输出,融合后可能已经不适用。逐项定义保留、转换与删除规则更可靠。

“所有属性都复制,再覆盖几个”为什么需要谨慎?假设某个属性缓存了原输出元素类型、量化区间或内存估算,覆盖结果类型后它就可能过时。被检查的差异明确保留了中间类型,但不能由此保证任意未来新增属性都会自动正确。测试应包含一个具有不同激活前后编码的案例,并验证生成参数来自该取的那一侧。属性演化是融合实现长期维护的一部分。

最后,结构负例应与原因一一对应。多使用者案例只破坏使用次数,函数不支持案例只改变函数类别,形状不符案例只改变对应关系。若一个负例同时包含多种不合法条件,它通过只能证明“某个条件挡住了”,无法锁定具体保护。教学测试越小、每次只破坏一条不变量,越能解释未来修改为什么使它从保留变成融合,或反过来。

最终,融合值得庆祝的时刻应是:使用者关系保住了,中间类型保住了,参数完整了,普通路径没退化,生成物和数值测试都支持等价。那个被删掉的节点,才算体面地退休。


返回系列目录 · 上一篇 · 下一篇


If you like this blog or find it useful for you, you are welcome to comment on it. You are also welcome to share this blog, so that more people can participate in it. All the images used in the blog are my original works or AI works, if you want to take it,don't hesitate. Thank you !