SincNet Conv1d 优化:从模型对称性到矩阵单元利用率
Laceprndpm

以 SincNet Conv1d 为例,串联模型对称性、混合精度、矩阵映射与软件流水,解释如何把模型语义转化为硬件性能收益。

SincNet Conv1d 优化:从模型对称性到矩阵单元利用率

面向读者: 理解 Conv/GEMM,希望知道“除了调整 kernel 参数,还能从哪里找到优化机会”的算子工程师。本文从固定 shape 出发,沿模型契约、精度预算、矩阵映射与片上流水,解释怎样将模型语义转化为硬件收益,并进一步追踪编译器未能利用的并行机会。

核心问题与结论: 一个能够正确执行的通用 Conv1d,为什么会在特定模型上浪费大量计算资源?这个项目先修复 Cin=1 与通用路径的映射错配,再利用滤波器奇偶对称性折叠计算,用三角 HP6 减少高精度矩阵乘项数,并重组数据布局与流水。最终方案在目标 shape 上相对原 HP9 路径获得约 300× 的 kernel 加速。对照手工与自动排布,还定位到隐藏地址状态缺少精确内存操作数、导致预处理组被过宽依赖阻塞的问题。

范围与依据: 本文复盘同一款加速器上、固定 SincNet 第一层工作负载的优化,输入与权重为 FP32,目标是推理专用路径。性能数字来自项目记录,硬件发射限制来自后续实卡 profile 的观察,编译器部分依据预处理阶段的 MIR 与调度依赖诊断;本文整理时没有重新运行实卡实验。芯片单元使用功能名称,不提供公司源码、内部硬件型号、实现及测试版本标识。约 300× 是指定基线和 shape 下的结果。

最初框架提供的是固定 shape 和随机权重。我先沿调用链确认 Conv1d 进入了通用 Conv2d 高精度实现,再分析 shape 为什么没有被有效利用。随后追踪模型来源,确认该层的滤波器结构,并与框架侧约定特化条件。这个过程依次回答三个问题:通用实现在哪浪费,模型允许少算什么,剩下的计算怎样在硬件上连续执行。

1. 从 shape 找到通用路径的浪费

1.1 先把 Conv1d 展开成矩阵问题

目标输入采用 NCL 布局,即 batch、channel、length:

对象 形状
输入 [21, 1, 160000]
权重 [80, 1, 251]
bias [80]
输出 [21, 80, 15975]

卷积参数为 stride=10, padding=0, dilation=1, groups=1。输出长度为:

为避免 batch 大小和矩阵名称混淆,以下用 表示滑窗输入矩阵、 表示权重矩阵。忽略最后的 bias 加法,卷积可以写成 ,其中:

对 ,有:

是解释计算的逻辑矩阵;实现不必先在外存生成完整 im2col。

1.2 为什么 Cin=1 会成为问题

原路径沿通用 Conv2d 的通道维度组织 128-wide 计算。在当前 Cin=1 的工作负载中,每个核位置只有一条有效 lane,其余位置被 mask,随后再遍历 251 个核位置。

因此第一步是改变 K 维组织:把通道与核位置合起来,形成长度为 251 的有效向量,让连续核位置填入原本空闲的计算位置。这一阶段不依赖权重对称,随机权重也能验证它。

我首先尝试让 DMA 直接生成 im2col,但该路径的最小 offset 粒度是 32 个元素,不能表达窗口内部逐元素移动。于是把连续搬运与细粒度重排分给不同单元:

1
2
3
4
5
连续原始输入
→ DMA 搬入片上存储
→ 向量加载
→ 片上重排单元生成滑窗
→ 紧凑 K 维计算

这一步的实卡周期从 338,021,562 降到 28,062,366,约 12.045×。它验证了映射方向,但还没有解决高精度分量的重复处理、矩阵列利用率和流水阶段之间的等待。

2. 从模型契约获得进一步特化

2.1 对称性来自模型,不来自 shape

固定 shape 无法证明权重对称。追踪模型后,这个工作负载对应 ParamSincFB(80,251):前 40 个输出滤波器为偶对称,后 40 个为奇对称。公开实现先拼接偶对称滤波器,再拼接奇对称滤波器;后者的中心权重为零。这是具体滤波器构造的性质,不能推广到任意同 shape 的 Conv1d。ParamSincFB 源码

框架侧显式保证这个契约,kernel 按两组通道处理,不在热路径扫描或猜测权重。非对称权重继续使用通用实现;新增专用入口不等于已经实现了自动检测或自动 fallback。

2.2 偶对称相加,奇对称相减

偶对称滤波器满足 ,镜像乘积可合并为:

因此定义:

奇对称滤波器满足 ,于是:

奇对称中心项不参与乘积。为了统一表示,可以令 ,其中心权重也为零。偶对称有 126 个有效位置,奇对称有 125 个有效位置;两者都可以装入一个宽度为 128 的 K 块。

两组结果分别为:

这里的 分别保留对应 40 个通道的前半权重及中心位置。两组共享原始输入,但不能直接共享同一份折叠激活。

1
2
3
同一个原始滑窗
├─ 镜像相加,保留中心 → A+ → 偶对称权重 → 输出通道 0…39
└─ 镜像相减,中心置零 → A- → 奇对称权重 → 输出通道 40…79

镜像相加或相减在 FP32 中完成,再进行后续分量分解。这样可以避免先量化两份输入再折叠带来的额外低精度误差;浮点结果与原始逐项卷积是否满足门限,仍需验证。

3. 精度预算怎样决定矩阵乘次数

矩阵单元擅长 BF16 乘法,但接口输入和精度要求是 FP32。项目采用多分量表示:将每个 FP32 数分解为主值、第一层残差和第二层残差对应的三个 BF16 分量,再用矩阵乘组合它们。此类做法利用低精度乘法与较高精度累加构造更高精度结果,相关原理可参见 Henry 等人的论文。

令两侧的分量分别为 和 。HP9 计算全部九个乘积;这里的数字表示乘积项数量,并非多项式阶数。若 BF16 的 unit roundoff 为 ,分量量级可按 理解:

乘积项
,保留 ,保留 ,保留
,保留 ,保留 ,省略
,保留 ,省略 ,省略

三角 HP6 保留 的六项:

相比 HP9,少算的是两个三阶项和一个四阶项。偶、奇两组分别应用这一分解与乘积选择。

项目的比较门限为 rtol=1e-5, atol=1e-5。六项方案的依据是保留全部到二阶的乘积,使分解与截断误差在良态输入下进入约 的量级。这只是数值方案的筛选依据,不是对任意输入的正确性保证:强抵消、累加顺序和舍入仍会影响结果。完整推导及各误差项放在附录。

4. 让计算结构匹配硬件

4.1 先说明几个硬件角色

下文只保留影响决策的功能与约束,名称经过通用化处理:

部件 承担的工作 调度时关心什么
DMA 外存与片上存储之间搬运连续数据 粒度、启动开销及能否与计算重叠
片上重排单元 生成滑窗、镜像配对与矩阵转置 吞吐、输入就绪与发射限制
向量计算单元 FP32 折叠、分量生成、打包及结果累加 短依赖与寄存器占用
矩阵输入暂存队列、右操作数寄存器 准备并保存矩阵单元即将使用的数据 哪份数据何时可覆盖、能被复用多久
MXU(Matrix Multiply Unit) 矩阵乘与相应累加 阵列利用率、操作间发射间隔

功能独立不自动意味着任意指令都能并行发射。后面的数据流先按依赖组织,实际能够重叠多少,再由 profile 判断。

4.2 为什么选择转置 GEMM

最初的逻辑矩阵是 。将 80 个输出通道放到硬件 128-wide 的矩阵列方向,只有 的列有效。即使省掉部分乘积,剩下的乘积仍可能低效执行。

可以选择以下三种组织方式;比较时均允许使用前面的奇偶折叠与三角 HP6:

方案 改善 仍需付出的代价
按输出块即时生成输入分量,计算 避免完整 im2col 落到外存 分量准备和切换容易重复,矩阵列未满
在片上保留输入分量,仍计算 延长分量生命周期,增加复用 输出通道维度仍然较窄
按奇偶组计算 、 把长输出位置维放到矩阵列方向 需要相应转置、两组折叠与片上调度

最终采用第三种。每个 batch 内,每组输出的逻辑形状为 [40,15975]。输出位置沿 128-wide 列方向分块,40 个通道在当前 8-row 发射粒度下正好是五组。这里描述的是矩阵计算的硬件宽度与发射粒度,不是最终采用的软件 tile 尺寸。

两组分别写回前、后 40 个通道,组合后的 [80,15975] 恰好匹配该 batch 的连续 NCL 输出布局,不需要再把整个输出转置回去。bias 在对应通道的结果上添加。

4.3 把重复准备变成片上复用

权重的三组分量预先生成,并在后续输出位置计算中复用。输入按局部窗口处理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
连续 raw input → 片上缓冲 → 滑窗及镜像配对
│
┌─────────────────┴─────────────────┐
▼ ▼
FP32 相加折叠 FP32 相减折叠
偶对称输入分量 奇对称输入分量
│ │
▼ ▼
转置、分解、打包 转置、分解、打包
│ │
矩阵输入准备 矩阵输入准备
│ │
与偶对称权重计算 与奇对称权重计算
└─────────────────┬─────────────────┘
▼
累加、bias、NCL 写回

图中两路表达数据关系,不要求实现始终同时保留两套完整中间结果。相应 stage 只需持有当前计算和后续准备所需的数据。

在同一奇偶组中,右侧激活分量可以依次供多个权重分量和输出通道块使用。例如 对应 三项, 对应两项, 对应一项。权重复用跨输出位置,激活分量复用跨乘积项和组内通道块。

4.4 为什么后来反而缩小 M tile

这里的 M tile 指一次准备的输出位置范围。增大它可以摊薄搬运与控制开销,但也会增加原始窗口、折叠结果和分量缓冲的占用,使能够同时在途的预处理 stage 变少。

HP6 的多个分量乘积,以及同一输入分量跨通道块的使用,增强了已搬入数据的复用。因此,缩小 M tile 后不必立刻退化成“每搬一点数据只做很少计算”。我后续减小 M tile,将省下的片上空间用于更多预处理 stage,让搬运、重排、量化与矩阵计算拥有更多交叠机会。

1
2
3
4
5
HP6 分量与通道复用
→ 较小 M tile 仍保留一定计算复用
→ 单份工作集缩小
→ 更多预处理 stage 可同时在途
→ 增加覆盖计算等待的机会

这是一项容量与并行度的取舍。tile 太小也会增加边界、搬运启动和控制开销,可能重复读取相邻滑窗重叠部分。最终看完整 kernel 的周期和 stage 等待,而不能只看单个 tile 的计算密度。这里保留设计原因,不填写尚未确认的前后 tile 尺寸或 stage 数量。

4.5 让 producer 和 consumer 按各自资源推进

producer 负责连续输入的搬运、滑窗重排、折叠和分量准备;consumer 负责矩阵输入装填、六项计算及结果回收。两者通过有明确所有权的片上缓冲交接:producer 写完才能交给 consumer,consumer 最后一次使用完成后才能复用该缓冲。

准备后续输入时,应尽量保留当前矩阵输入的有效生命周期;当前计算执行时,可以推进依赖已经满足的后续准备。多 stage 使这些工作能够交叠,但不消除数据就绪和覆盖顺序要求。

最终方案的组织重点,是配平重排、矩阵输入供给、量化和 MXU 消费的吞吐,并让控制与结果回收融入稳态。增加预处理 stage 提供更多独立工作,能否真正缩短等待,还取决于实际发射限制。

5. 性能结果与理想化结构收益

5.1 先固定比较对象

以下均针对前述固定 shape,与原通用 HP9 路径比较:

阶段 完整 kernel cycles 相对原 HP9
通用 HP9 338,021,562 1×
固定 shape 的紧凑 K 维路径 28,062,366 12.045×
奇偶折叠、三角 HP6 与最终数据流 —(仅保留约数加速比) 约 300×

最终记录保留的是约 300× 的汇总值。不能用它反算一个看似精确的 cycles 数,再当作实测记录。上述结果描述 kernel 收益,不是完整模型或服务的端到端加速。

5.2 614.4× 是怎样算出来的

若只看原路径中的结构浪费,可以按四个因素做一个理想化估算:

因素 计算方式 理想化收益
通道维只有 1 条有效 lane 用核位置填充原来闲置的 128-wide 计算位置 128×
镜像折叠 偶对称 251→126,奇对称 251→125;近似取减半 2×
高精度乘积项 HP9 九项改为三角 HP6 六项 9/6=1.5×
矩阵列利用率 从约 80/128 提高到接近满列 128/80=1.6×

于是得到:

这里将各因素视为可组合,并假定节省的工作能够转换为有效吞吐。它可作为理想化的结构收益上限参考,不能当作严格的完整 kernel 时间下界,也不是把几个独立实测加速比相乘。两组折叠的准备成本、对齐与尾块、量化、搬运、输出回收和真实发射约束均未被这个乘积完整计入。

因此,约 300× 与 614.4× 的比例也不能解释成硬件利用率。

5.3 为什么实际没有达到这个估算

后续实卡 profile 发现,一代硬件存在额外的 hold-issue 问题:转置单元与 MXU 虽然在这些操作之间没有算法数据依赖,实际发射仍出现了限制,削弱了预期的重叠。这是项目中识别出的主要问题。

这里能够确认的是“存在额外的跨单元发射限制”,尚未细分为单向或双向阻塞,也没有把全部额外周期归因到某一个方向。它与算法上的等待不同:即使下一 stage 的数据已经就绪,硬件仍可能暂时不允许相关指令发射。

此外,尾块、量化、DMA 启动、控制和结果回收也有成本。缩小 M tile 与增加 stage 提供了隐藏等待的机会,但不能突破硬件发射约束;约 300× 是这些因素共同作用后的完整 kernel 结果。

6. 从手工流水追到编译器的依赖建模

手工排布能够交叠多组预处理后,另一个问题是:为什么 LLVM 没有自动排出相同的并行?这条调查关注的是输入分量生成与打包阶段。诊断记录中的“四路”指多个已有预处理组之间的交叠,不是四个硬件核心,也不是最终软件 tile 的尺寸。

6.1 先看节点能否进入候选队列

最初容易怀疑寄存器压力或展开策略:编译器是不是认为两路比四路更划算?但检查源码后发现,这段预处理通过模板在编译期展开,相关指令已经存在;观察到的两个数据流也是源码显式提供的,并非某个循环优化 pass 选择了 factor=2。

因此下一步应检查 MachineScheduler 的依赖图。只有当节点的依赖允许它进入 READY 候选集合,才有必要讨论它是否被收益或寄存器压力启发式放弃。

诊断中,后续组的输入 load 被地址状态设置指令的 Ord Barrier 边挡住,尚未进入可选择范围。寄存器压力确实偏高,但它不是这些节点无法提前 READY 的第一原因。

6.2 NoAlias 已成立,为什么仍不能重排

折叠输入与打包输出已通过别名分析证明是不同存储对象,普通访存之间的假 MayAliasMem 边已经消除。剩下的限制来自一种设置索引访存地址生成状态的指令,下文用功能名 set_addr_state 表示它。

这条指令设置隐藏状态,本身不写折叠输入或打包输出。但后端将它标记为 mayStore=1,最终 MachineInstr 又缺少说明访问对象的 MachineMemOperand(MMO)。在该后端的调度逻辑中,它因此被视为需要全局排序的未知内存操作:

1
2
3
4
5
6
设置地址生成状态
→ 指令被标记为可能写内存
→ 缺少精确 MMO,无法识别其实际作用对象
→ 调度器建立全局 BarrierChain
→ 后续组的普通 load/store 也必须等待
→ 多组预处理无法按预期交叠

关键是,全局 barrier 的处理发生在相关 AA 查询之前,甚至把已归为 NoAlias 的访存也连接进来。因此继续添加 restrict 并不能消除这条约束:缺失的是隐藏状态的精确建模,而不是两个普通指针之间的独立性证明。这里的 barrier 是编译器依赖图中的排序约束,不意味着程序主动发出了一条全局硬件 fence。

6.3 收窄错误依赖,同时保留真实约束

地址状态不能简单忽略。后续 indexed load/store 要读取它;同一状态被覆盖前,还必须保证旧消费者不会误用新地址。普通访存与索引访存也仍然共享加载/存储执行资源。

诊断提出用 PseudoSourceValue(PSV) 表示地址生成器的隐藏资源,并通过 MMO 将其读写传递到机器指令。功能模型如下,名称仅用于解释:

1
2
3
set_addr_state(n) : 写地址状态 n
indexed load : 读实际数据 + 读地址状态 n
indexed store : 写实际数据 + 读地址状态 n

这样可以保留同一状态的设置、使用和覆盖顺序,同时让无关的普通访存与向量计算获得重排空间。

约束 如何处理
地址状态设置 → 使用它的 indexed 操作 保留真实数据依赖
旧 indexed 操作 → 同一状态的下一次覆盖 保守保留,除非明确建模了状态重命名
普通与 indexed 访存的执行资源竞争 保留在资源模型中
对实际重叠存储的读写依赖 保留真实 alias 约束
设置地址状态 → 所有无关内存操作的全局排序 用精确资源依赖收窄

共享一个执行通道,限制的是同周期能发射哪些工作,并不自动要求所有相关指令严格按源码顺序执行。直接清除 mayStore 或按 opcode 删除全部 barrier,会同时抹掉需要保留的约束。

6.4 验证顺序:依赖、调度、正确性、性能

验证这种修复,需要先检查机器指令是否携带地址状态与真实内存的 MMO,再确认调度图中无关的全局边消失、必要的设置/使用/覆盖边仍存在。随后检查同一时刻可见的预处理组、寄存器压力及 spill/reload,最后验证地址访问正确性与完整 kernel 周期。

诊断还发现浮点延迟模型可能偏保守,但这属于另一项校准,应与依赖修复分开做单变量对照。一次修改多个条件再只看总周期,无法判断收益来自哪里。

这份诊断记录止于根因定位与修复建议,不单独提供修复后的收益证明。它建立的可迁移方法是:先区分“还不允许调度”和“已经允许但没有被选中”,再定位限制来自真实数据、硬件资源,还是过宽的编译器模型。

前面的跨单元 hold-issue 是实卡发射限制;本节的全局 barrier 是编译器建模问题。解除后者不会自动消除前者,也不能据此承诺固定倍数的加速。

7. 正确性与扩展边界

7.1 用未折叠卷积检验特化

正确性 reference 应使用完整 251 项的 FP32 Conv1d,包含 bias,并按目标 NCL 输出比较。只用一个折叠实现验证另一个折叠实现,可能让相同的索引或符号错误相互掩盖。

项目记录描述的检查包括镜像 pair、中心项、batch 与块边界、并行计算路径、高精度分量及错误参数,并通过框架的高精度门禁。本文不将整理过程中的公式核对冒充新的实卡验收。

逐元素检查使用 rtol=1e-5, atol=1e-5。若把 CPU 结果作为参考项,对应条件为:

allclose 的相对误差项依赖第二个参数,测试时应固定参数顺序。接近零的输出主要由绝对误差门限约束。PyTorch allclose 文档

特化入口只处理已保证奇偶契约的推理输入。布局、梯度或权重结构不满足条件时,不应静默套用该公式;通用实现的可用性与框架是否自动选择它,是两个不同问题。

7.2 可以复用什么,仍需重新验证什么

对固定的输入通道数、核形状和输出通道数,输出位置增多主要意味着重复更多局部块。只在片上生成当前和相邻 stage 所需的 im2col,可避免完整中间矩阵随输出长度增长。

设单次配置下的活跃 stage 数固定,稳态可以表示为:

活跃片上工作集不必随 线性增长,但改变 K 或输出通道数时仍需重新分块与规划缓冲。

这种 producer/consumer 划分也提供了扩展方向:ConvXd 的滑窗坐标生成可放在 producer 中,高精度方案保留哪些分量乘积可交给 consumer 的调度表描述。更换 HP 项数后,消费者吞吐和结果累加压力会变化,必须重新配平。本文不据此宣称任意 ConvXd 已得到相同性能或完成全部正确性验证。

8. 可迁移的方法与未解问题

这个项目的排查顺序是:先用 shape 和调用链定位实际浪费,再向模型寻找显式契约,用精度预算筛选计算方案,再让数据布局、资源复用和在途 stage 共同服务于完整 kernel 的吞吐;当自动排布没有利用这些机会时,继续追踪机器指令上的依赖建模。

如果一项优化只减少了数学运算,却增加了无法隐藏的准备或发射等待,它不一定更快。反过来,缩小 tile、增加局部中间结果,也可能因为提供了更多重叠机会而改善整体性能。性能模型用来解释和预测,profile 用来检查硬件是否兑现这些机会。

关于 MMO、调度 DAG 与寄存器压力的通用背景,可继续阅读 LLVM 跨层优化与诊断复盘。

附录:奇偶折叠与三角 HP6 的误差分析

A. 分开实数等价与浮点误差

对任意一个通道,令 表示偶对称、 表示奇对称。镜像 pair 满足:

这是实数域中的恒等式。浮点实现改变了加法和乘法的组织顺序。以下采用 round-to-nearest 模型,假设相关运算没有溢出、下溢或 flush-to-zero 导致的模型失效;特殊值不包含在相对误差推导中。

FP32 unit roundoff 为 。先折叠输入:

$$
\widetilde f_k=\mathrm{fl}{32}(a_k+s b_k)
=(a_k+s b_k)(1+\delta_k),\qquad |\delta_k|\le u
{32}.
$$

因此仅由 fold 带来的输出扰动满足:

偶对称中心值直接保留,没有 pair fold;奇对称中心权重为零。该界比较的是对同一份存储输入进行实数计算的结果,不包含之前输入量化的误差。

B. 三个 BF16 分量怎样生成

令 为 BF16 的 round-to-nearest-even 量化,BF16 unit roundoff 为 。分量转回 FP32 后参与残差计算:

在上述正常范围内,量化值与被量化值足够接近;这些残差减法可在 FP32 中精确表示。于是:

三分量重构残差进入 的量级,但这不意味着整个矩阵乘已经具有一个 FP32 舍入单位以内的误差。

C. 六项截断的界

设 , 。单个标量乘积的三角六项为:

从完整九项中省略的部分是:

再用 ,以及 ,得到两侧分解残差的界:

故六项在实数累加下满足:

对一条折叠后的点积,将各项绝对误差相加:

该估算保留了分量幅值中的 因子;核心结论仍是省略项与分解尾项从三阶开始,而不是某个小数位恰好等于固定值。

D. 为什么不能只看这个系数

设未加 bias 的精确折叠点积为 。当 时,其条件数为:

乘积同号时 ,fold 加分解/截断的一阶估算约为 ,即约 。有抵消时 可以很大;点积或加 bias 后的结果接近零时,应直接检查绝对误差。

此外,实际 MXU 点积、多个分量乘积的合并和 bias 加法都有舍入。若某段实现符合长度为 的普通 FP32 顺序累加模型,可用:

乘以相应被累加项绝对值之和做保守估算。但实际归约树、分块和融合乘加方式必须按实现判断,不能把这一模型直接当作芯片的实测误差。

最终需要同时考虑:

与 FP32 reference 比较时,还要考虑 reference 自身的舍入。因此,“约 的分解误差”不能直接推出全体输出一定通过 门限。

E. 为什么选三角六项

候选乘积集合 首先省略的项 对选择的影响
左上 2×2 的四项 等 省略了二阶项,误差预算更紧
左上 3×2 的矩形六项 等 虽然同为六项,仍省略二阶项
的三角六项 保留全部到二阶的乘积,是本文选择
全部九项 不截断分量乘积 仍有分解残差与累加误差,矩阵计算更多

四项或矩形六项并非在每个输入上都会失败,但不能仅凭项数较少就假定满足门限。三角六项是本文分量展开中保留全部到二阶乘积所需的六项,不是对所有可能高精度算法的全局最少乘法证明。

参考与复现材料