系列目录 · 算子与布局 · Read in English
“都是矩阵乘法,权重放左边怎么就不能编译?”
从线性代数看,这是个合理的疑问。从后端看,矩阵单元的数据流、权重打包、偏置参数与量化通道可能都围绕 动态输入 @ 常量权重 组织。当常量跑到左侧,算子名字没变,原有实现的假设却几乎全被触碰。
转置恒等式与形状证明
最直接但危险的方案是交换两个输入。矩阵乘法通常不满足交换律,连输出形状都可能不一样。正确的教学出路是:
1 | Y = W @ A |
常量 W 的转置可以在编译期完成,运行时 A 的转置与结果恢复则需要相应图节点。这样核心计算又回到已有的右侧常量权重路径。
用一组小矩阵验证,而不是背公式:
1 | W = [[ 1, 2, 0], |
计算 A^T@W^T 得到 [[4,7],[9,-7]],再转置回来恰好恢复结果。这里既验证值,也验证了维度:W 为 M×K,A 为 K×N,中间计算为 N×K 乘 K×M,输出 N×M,最后恢复 M×N。
批量维与支持前提
有批量维时,也不能只转置整个张量。假设 A:[B0,B1,K,N],共享 W。可以把批量维展平成 B=B0*B1,得到 [B,K,N];交换最后两维为 [B,N,K];乘 W 的转置后得到 [B,N,M];再交换为 [B,M,N],最后恢复 [B0,B1,M,N]。
已检查的历史 lowering 使用了这条形状链,并为需要的阶段创建 reshape 与 permute。它不是无条件支持任意 MatMul:左边必须为常量、右边不能是权重、原操作不能带偏置,而且若已有输入或输出转置标记,也不会直接进入这条专用模式。常量形状与动态操作数 rank 也有限制。公开稿应把它称为受限支持扩展,而不是通用矩阵变换器。
为什么先排除偏置?若 Y=W@A+b,偏置可能沿输出列广播,也可能有其他形状。转置后,它的广播轴随之改变。把原偏置未经变换送入新 MatMul,会把“按列加”变成“按另一个方向加”。与其在第一版里默默错误,不如先拒绝,再为偏置单独设计可证明的映射。
量化角色与偏置补偿
量化又把问题抬高一层。右侧权重路径通常以输出通道为中心准备比例和偏置。转置之前,W 的输出行对应原计算输出的 M 维;转置之后,它们变成 W^T 的列,也是新计算的输出通道。若逐通道比例原先绑定的是别的轴,不能原样复制一个数组就假装含义相同。
历史代码为转置权重创建新常量,再读取其量化信息。进一步检查辅助接口可见,量化比例按权重名称查询外部映射。这说明新常量的名字、派生元数据与通道语义必须接得上;仅有转置后的数值并不充分。该提交同时保留未立即匹配到权重节点的参数编码,为后续生成或查找留下信息,但这不等于自动证明所有新名字和所有逐通道轴都正确。
更完善的回归应使用每个通道不同的比例,并检查转置后比例绑定的语义坐标,而不是只看数组长度。
导入阶段还存在“输入编号”的陷阱。如果激活量化信息只有一份,且模型把权重放在操作数 0、动态激活放在操作数 1,就不能按通常的“第一份输入编码对应第一个操作数”处理。历史修改识别此类静态左 MatMul,跳过左侧权重的激活编码匹配,让动态一侧获得正确类型。数据流角色比位置编号更可靠。
偏置参数即使数学模型没有偏置,也可能不为空。量化整数乘加需要补偿输入 zero point。以简化情况说明,若动态输入 zero point 为 z,某输出通道的量化权重为 q_w,乘加展开会出现与 -z*sum(q_w) 相关的常量补偿。历史修改将偏置计算从直接依赖旧操作对象的接口,抽成可接收输入、权重、输出形状与转置条件的形式,以供新路径调用。
这里的通用教训是:重用代码之前先把“谁是输入、谁是权重、输出通道在哪里”的隐含假设变成显式参数。否则即使函数名仍叫“计算 MatMul 偏置”,内部还盯着原右操作数,转置变换就只完成了一半。
编译成本与正确性测试
编译性能也有账要算。常量转置需要分配新数组,可能增加编译峰值内存和产物体积;运行时转置 A 与结果可能带来两次额外搬运。若相邻算子能直接产生或消费需要的布局,部分转置可以吸收;若不能,扩展了可编译范围,却未必带来理想速度。能力支持与性能优化是不同承诺。
一个对照实验可以固定乘加规模,改变 M、N、K 和批量维数量,分别统计常量预处理时间、临时缓冲区大小、转置搬运量与核心 MatMul 时间。特别关注矩阵很小但批量很多的情况:启动与转置可能比乘加更显眼。不要把恒等式里的三个转置符号当成三个免费的数学装饰。
正确性测试可以从上面的非方阵开始,避免方阵让错误形状蒙混过关。再加入两个批量维、各批使用不同标签的动态输入、非零输入 zero point、逐通道不同的比例,以及被限制的偏置和转置属性负例。恢复 shape 后要检查每个批次,而不是只比较 flatten 之后的整体统计量。
还应验证失败传播。派生权重创建、量化查询、补偿参数构造和权重 lowering 任一步失败,不能留下一个看似成功的新 MatMul。可以先完成形状与元数据计划再修改 IR,减少半途失败时产生不完整图的风险。这是针对设计可审查性的建议,不是声称已查看历史代码具备完整事务机制。
量化轴、共享与边界
为了把量化轴说得更具体,可以给 W 的每一行绑定一个不同的比例。转置后,这组比例应跟随原来的行元素,成为 W^T 对应列的解释规则;比例数值本身不一定变化,绑定的轴位置却变化了。若原比例实际上沿 K 维定义,那么转置后它又落到另一语义位置,不能硬说它是新输出通道比例。迁移规则必须来自原编码含义,不是来自“转置以后总有一维长度相等”。
因此一个优秀的派生常量接口可以返回的不只是新张量,还包括旧坐标到新坐标的映射,以及已变换或待变换的量化描述。这样后续偏置和打包阶段不必从名字猜血缘。
批量展平也有前提。如果 W 在各批共享,展平所有动态批量维不会改变权重选择;如果左权重本身带不同批次的数据,就不能把前导维一概当成装饰。历史专用路径限制左权重形状,只允许受控的前导单位维,正是避免误承诺完整批量广播。进一步扩展时,需要先定义左右批量维的广播结果,再讨论展平是否保持对应关系。
教学上可以给每个批次一个独特标识,比如把同一个基础矩阵分别加上不同的低整数偏移。恢复批量形状后,检查每个批次结果是否保持其标识。只把整个输出拉平成向量比较总和,可能无法发现批次顺序交换。若两个批次的输入恰好相同,这个测试也会失去作用,所以应主动避免对称数据。
权重转置的编译期成本还可能重复发生。一个常量若被多个静态左 MatMul 使用,每个 lowering 都新建一份转置,可能增加内存与产物体积。可考虑缓存或公共子表达式复用,但缓存键应包括内容、元素类型、量化编码及目标存储格式。只按原权重名字缓存,遇到同名或不同量化视图时可能复用错误对象。这是潜在优化方向,本篇没有声称历史实现已缓存。
最后,批量维乘积也应检查溢出与动态值。数学记号 B=B0·B1 看起来很自然,宿主整数却有范围,动态维也可能用特殊标记表示。路径名里写“静态左”并不自动意味着右侧所有维度都已静态且合法。审查时应逐层寻找这些前置保证;若找不到,就把它记录为待验证约束,而不是根据函数名替实现补上保证。这样的诚实边界,是把真实工程经验写成公共技术文章时最值得保留的部分。
最终,一条漂亮的恒等式只是开门钥匙。真正穿过这扇门,需要让形状、常量字节、量化角色、偏置补偿与运行时布局一起移动。数学负责证明换一个姿势仍是同一道题,编译器负责保证每一层都真的换了姿势。
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 !