Sophgo INT4 编译中的 scale 通道对齐限制 ​

在 BM1684X 上将 Qwen3.5-2B 转为 W4BF16 时,编译出现 invalid scale channel。W4BF16 表示四位权重配合 BF16 激活,是权重量化路径;量化后的 scale、zero point 与权重本身都需要转换为目标后端能够读取的布局。

复现组合为 tpu_mlir 1.28.1、BM1684X、Qwen3.5-2B。q_group_size=128 和 64 都失败,BF16 转换通过。这首先是后端模型生成问题,还没有进入“量化模型可运行,但任务精度下降”的阶段。

公开问题记录还包含从 AutoRound INT4 权重转换时的同类报错。原始浮点权重由转换器量化,与读取已有量化权重,是两种不同的输入路径。已有量化权重的分组和编码需要从配置与张量中确认,不能仅凭命令行改了 group size,就认为它已经按新的分组重新量化。

因此,“工具支持 W4”只是入口条件。实际模型的层形状、分组方式、元数据布局和 kernel 约定也必须匹配,才会生成可用产物。

一、分组与通道对齐 ​

按输出通道组织 Linear 权重为 [N,K],其中 K 是输入特征数、N 是输出特征数。对于此处分组量化的逻辑布局,设 group size 为 g,scale 形状为:

shape(S)=[N,K/g]

这一表达式假设所选路径满足分组条件;实际模型还要核对转置和已发生的重排。

text
scale: [ N, K/g ]
         ↑    ↑
      通道数  输入方向的组数

BM1684X 对应的重排路径需要把通道分配到 P 个 NPU lane,概念变换为:

text
[N, K/g]
    → reshape [N/P, P, K/g]
    → permute [P, N/P, K/g]

在该 BM1684X 路径中 P=64。必须先满足 NmodP=0,才能按这个布局整分。改变 g 影响的是 K/g,不会让 N 自动变成 64 的倍数。

调整项主要影响是否自动修复通道对齐
q_group_sizeK 方向的分组数量和元数据量否
输出通道 Nscale 的通道维还需保持模型语义
浮点路径不使用这套 W4 重排可用于隔离问题,仍需完整验收

这里的 64 指芯片内部 lane 数,不是 64 张卡,与 num_device 也不是同一个概念。

用一个假设形状看会更直观:若 N=96、K=4096,将 g 从 64 改为 128,scale 会从 [96,64] 变为 [96,32]。两次的第一维仍是 96,都无法按 64 个 lane 整分。这里的 96 仅用于说明维度关系,不是此次失败层已经确认的形状。

分组大小还会改变存储成本

group size 对这次断言无效,不代表它没有作用。更小的组允许不同输入区间使用各自的量化参数,同时也增加元数据。若暂不考虑 padding 和其他对齐,权重有 NK 个四位值,每组保存一个 16 位 scale 和一个 8 位 zero point,则每个权重对应的平均存储位数为:

beff=4+16+8g

这里按上述 BM1684X 重排分支中 scale 和 zero point 分别存储的方式估算,不代表所有 INT4 格式。g=64 时为 4.375 bit,g=128 时为 4.1875 bit;相对 BF16 权重,理论压缩比分别约为 3.66 和 3.82。实际 BModel 还包含未量化层、对齐空间和其他数据,不能据此推算整模型文件一定缩小相同比例。

因此分组大小首先应作为量化精度与存储的取舍来评估。遇到通道重排失败时,必须先解决 N 方向的布局问题,之后再比较不同分组的任务效果和运行成本。

二、重排的约束 ​

关键检查及后续维度计算可以压缩为:

cpp
int64_t npu_num = backend::Arch::NPU_NUM;
if (scale_shape[0] % npu_num) {
    llvm_unreachable("invalid scale channel");
}
auto groups = scale_shape[1];
auto channels_per_lane = scale_shape[0] / npu_num;

这个检查并非多余限制。后续重排以整数 N/P 为基础计算偏移和目标形状,尾部通道没有被这条路径处理。直接删除断言,可能把显式编译失败变成错误布局、输出损坏或越界。

令 G=K/g,原通道编号为 c=jP+i,其中 i 是 lane 编号、j 是 lane 内的通道序号。根据源码的读写偏移,重排关系为:

S′[i,j,k]=S[jP+i,k],Z′[i,j,k]=Z[jP+i,k]

这说明 scale 和 zero point 必须按相同顺序移动。某个输出通道的权重如果配上了另一个通道的量化参数,张量大小即使正确,反量化结果也会出错。因而检查不能停留在元素总数相等,还要核对通道的对应关系。

这属于所选后端重排实现的布局约束,不能直接扩大成“BM1684X 无法计算任何非 64 倍数输出的 Linear”。浮点路径、其他量化分支以及带补齐或尾部处理的实现,需要分别判断。当前主线的相关分支仍能看到这一检查,但它不能单独证明新版本对该模型仍会失败:上游导出和 lowering 也可能改变进入此处的形状。

补齐通道需要改哪些内容

一种值得研究的修复方向是 padding,但它要同时覆盖权重、scale、zero point、kernel 和输出裁剪。只把一个 shape 改大,并不等于完成等价转换。

例如对单个 Linear,按行补零可写为:

W′=[W0],Y′=X(W′)T,Y=Y′[:,0:N]

这是局部数学等价的示意。它不能直接证明量化后的布局等价,也不能让额外通道不经裁剪就进入后续 attention、reshape 或残差。修复必须在正确边界上补齐,再恢复原接口。

量化存储中的“补零”尤其容易出错。按常见的仿射反量化关系 W^=s(q−z),浮点零对应的是 q=z;若 zero point 不为零,直接填入全零字节并不表示零权重。实际还要处理 INT4 的打包顺序、合法 scale、偏置的补齐,以及裁剪应放在哪个算子之后。

补齐也会引入额外计算和元数据。对前面的示意形状,96 个通道补到 128,物理通道增加了三分之一;这不代表整模型耗时增加三分之一,却说明低位权重节省的存储不一定能完全转化为运行收益。是否值得补齐,需要在正确性通过后测量。

子图导出、后端编译和设备运行的分工见 Sophgo Qwen3-VL 子图导出与部署边界,可先据此确认失败停在哪一阶段。

三、排查与验证 ​

先保存失败 block 的 MLIR,核对 A16MatMul 的权重、scale 和 zero point 形状;然后确认到底进入哪条重排分支。这样才能把报错落到具体层及布局条件,而不是继续盲调 group size。

公开复现日志的失败阶段包含 --weight-reorder,并有多个 block 的并行编译报错。应先保留其中一个失败 block 对应的完整 tpuc-opt 命令、输入 MLIR 和权重文件,再单独复现。这样可以排除并行日志交错,也能判断是所有失败 block 共享同一种层形状,还是存在多个问题。

逐层检查时,至少记录以下信息:

text
来源层与 MLIR location
输入、输出、权重的逻辑形状
weight_bits、q_group_size、w_transpose
进入 weight-reorder 前的 scale / zero point 形状与类型
目标芯片、工具版本和完整失败命令

不能直接从打包后权重的某个维度推断 N:INT4 两个值可能共用一个字节,转换过程中也可能已经转置或拆分。应结合 A16MatMul 的属性和模型原始投影形状,确认 scale_shape[0] 对应的实际通道,再回溯到导出端的 Linear 构造。

实际排查可以把检查分成两个位置。进入重排之前,确认量化参数是否已经对应正确的逻辑通道;重排之后,确认数据能否按 kernel 的布局恢复原通道顺序。如果前者已错,继续改后端重排只会掩盖导出问题;如果前者正确而后者失败,才有依据修改这条后端路径。

即使模型的 hidden size 是 64 的倍数,也不能据此跳过其他投影。模型中不同 Linear 的输出宽度可能不同,融合后的输出也可能被拆分;最终应检查失败算子的实际 N,而不是模型配置中的某一个主维度。

检查层次要回答的问题
导出图哪个投影层产生了不满足条件的 N?
量化数据scale 的转置、分组和 padding 是否一致?
编译路径浮点通过而 W4 失败,差异集中在哪里?
修复验收生成模型后,局部输出和整模型任务是否都正确?

已有证据解释了为何修改 group size 无效,但尚未记录具体投影层的 N,也没有新版工具链复测。修复方案应当继续验证,不能把这篇诊断当成已完成补丁。

若继续处理,可以先用固定版本的新工具链重跑同一个最小样例,再比较失败层在重排前的形状。需要修改编译器时,先对单层验证量化参数重排、补齐和裁剪,再验证整模型输出。已有记录中 BF16 转换通过,可作为隔离 W4 编译路径的对照,但还不等于部署后的性能和任务效果已经完成验证。

把布局误差与量化误差分开验证

只比较最终生成文本,很难知道修复是否正确。更直接的方法是给失败 Linear 固定一组输入,并保留三份输出:

对照输出生成方式用途
浮点参考原始权重执行 Linear观察量化带来的误差
量化参考用同一组 q、scale、zero point 在主机上解包、反量化后计算固定量化方案本身的结果
设备输出经重排、补齐及裁剪后的算子执行结果检查后端实现是否保留量化语义

量化参考与浮点参考的差异,主要反映当前量化方案的损失;设备输出与量化参考的差异,则包含布局、打包和设备数值计算的影响。两组对比要分别记录,不能将明显的通道错位解释成“四位量化本来就有误差”。主机参考还必须采用源格式实际使用的 signed/unsigned 编码与 zero point 定义。

输入可以先包含全零、固定随机值,以及只激活某个输入维度的向量。全零输入用于核对偏置;单维输入便于观察某列权重是否落到了错误通道。除最大绝对误差外,也查看逐输出通道的误差分布;参考值接近零时,相对误差会失真,不能单独使用。

单层通过后,再检查 prefill 与 decode 的输出形状和数值。它们可能使用同一份权重,却经过不同的切分与执行路径。最后才进行整模型任务与性能测试,避免补齐只在某一种输入形状下成立。

四、一些反思 ​

量化故障至少分成两类:一种是模型已经可运行,需要研究误差如何影响输出;另一种是后端根本无法表示当前量化布局。校准集和敏感层搜索主要帮助前者,具体方法见 Sophgo INT8 量化与精度调优;对后者应先检查 shape、对齐和数据重排。

判断参数是否值得调整,要先知道它控制哪个维度。group size、NPU lane 数和多设备数量虽然都表现为整数配置,作用对象却不同;定位到实际 shape 后,许多无效尝试可以直接排除。

这里更有价值的线索是,量化参数的布局本身也是算子实现的一部分。权重位宽、分组大小都合法,并不意味着这些数据已经符合后端的读取方式。修复应当让导出布局与 kernel 约定一致,并证明原通道的结果没有改变。

五、参考资料 ​