Skip to content

第二章 前向数学与传统卷积对比

从一般一维卷积出发

深度学习框架常说的 convolution 实际按互相关计算:不预先翻转权重。标准分组 Conv1d 的一个输出可写成:

$$ z_{b,o,t}=\beta_o+\sum_{i\in G(o)}\sum_{k=0}^{K-1} W_{o,i,k}\,\widetilde x_{b,i,ts-p_l+kd}. $$

这里 $s$ 是 stride,$d$ 是 dilation,$p_l$ 是左 padding,$G(o)$ 是输出通道 $o$ 所属分组的输入通道集合。越界输入取零。输出长度为:

$$ L_{out}=\left\lfloor\frac{L+p_l+p_r-d(K-1)-1}{s}\right\rfloor+1. $$

这些是相互独立的维度:是否因果决定时间依赖,groups 决定通道连接,stride 决定输出采样,dilation 决定窗口间隔。普通卷积也能设为因果;depthwise 卷积也可以是非因果。

化简到 Dao 的算子

取 $C_{in}=C_{out}=C$、groups=$C$、每通道只有一个核,即 depthwise multiplier=1。再取 $s=d=1$、$p_l=K-1$、$p_r=0$,得到:

$$ z_{b,c,t}=\beta_c+\sum_{k=0}^{K-1}w_{c,k}\widetilde x_{b,c,t-K+1+k},\qquad y_{b,c,t}=\phi(z_{b,c,t}). $$

$k=0$ 对应窗口最旧的位置,$k=K-1$ 对应当前 token。这一权重顺序与 Dao 的参考函数和 CUDA 累加下标一致。Dao 接口与参考函数

也可以令 $a_j=w_{K-1-j}$,写为:

$$ z_t=\beta+\sum_{j=0}^{K-1}a_j\widetilde x_{t-j}. $$

两种写法表达相同函数,但 $a_0$ 与 $w_0$ 含义不同。移植时最常见的方向错误,就是拿一种写法的权重顺序套入另一种下标。

一个能识别方向错误的例子

令 $x=[1,2,3,4]$,$w=[2,3,5]$,$\beta=1$,无激活。则:

$$ \begin{aligned} y_0&=1+2\cdot0+3\cdot0+5\cdot1=6,\ y_1&=1+2\cdot0+3\cdot1+5\cdot2=14,\ y_2&=1+2\cdot1+3\cdot2+5\cdot3=24,\ y_3&=1+2\cdot2+3\cdot3+5\cdot4=34. \end{aligned} $$

不能只用全 1 输入和对称权重检查实现,因为这些数据可能掩盖核方向反转。对脉冲输入 $[1,0,0,0]$、零 bias,输出应为 $[5,3,2,0]$。

为什么对称 padding 再切片也成立

PyTorch F.conv1d(..., padding=K-1, groups=C) 会在左右都补 $K-1$ 个零,输出长度是 $L+K-1$。其前 $L$ 个输出恰好满足上述因果公式,因此取 [..., :L] 即可。另一个等价表达是先只在左侧 padding,再调用 padding=0 的 Conv1d。

# 省略 dtype 转换;x: [B,C,L], w: [C,K]
y = F.conv1d(F.pad(x, (K - 1, 0)), w[:, None, :],
             bias=bias, groups=C)

这是数学参考表达,不意味着高性能 kernel 要真正生成 padding 张量。首块按边界填零,后续块读取真实历史,就能实现同样的结果。

矩阵形式揭示因果性

仍取 $K=3,L=4$,不含 bias 和激活:

$$ \begin{bmatrix}y_0\y_1\y_2\y_3\end{bmatrix} = \underbrace{\begin{bmatrix} w_2&0&0&0\ w_1&w_2&0&0\ w_0&w_1&w_2&0\ 0&w_0&w_1&w_2 \end{bmatrix}}_{T(w)} \begin{bmatrix}x_0\x_1\x_2\x_3\end{bmatrix}. $$

矩阵为下三角带状 Toeplitz:下三角表示不读取未来,带宽表示有限历史,斜对角常数表示时间共享权重。不同通道分别拥有自己的 $T(w_c)$,通道之间没有交叉项。

与传统卷积逐项比较

属性 普通 dense Conv1d Dao causal depthwise Conv1d
时间依赖 由 padding 和 dilation 决定 当前与最近 $K-1$ 个位置
通道连接 每输出通道可聚合全部输入通道 每输出通道只读取同编号输入通道
权重形状 $[C_{out},C_{in},K]$ $[C,K]$,框架 Conv1d 中可写为 $[C,1,K]$
参数量,不计 bias $C_{out}C_{in}K$ $CK$
乘加次数 $BL_{out}C_{out}C_{in}K$ $BLCK$
常见映射 较大的通道归约,可转成矩阵乘 小窗口逐通道乘加,搬运和复用很重要
推理历史状态 因果配置也能维护状态 固定短窗口状态,适合专用 update

对于 groups=$G$ 的一般分组卷积,参数量为 $C_{out}(C_{in}/G)K$。depthwise 是特殊情形,节省的 $C$ 倍来自通道连接减少;不是因果性本身让计算量下降了 $C$ 倍。

二维图像卷积在高宽两个维度滑动,一般没有自然的时间“未来”方向。要与本算子比较,应先对齐维度、groups、stride 和 padding,再比较执行方式。

数量级与性能直觉

取示例 $B=8,C=4096,L=2048,K=4$:输出共 $67,108,864$ 个元素,卷积约 $536,870,912$ FLOPs。若输入输出均 FP16,理想情况下各读写一次,共约 256 MiB,不计权重、halo 和其他开销。

由此得到理想算术强度:

$$ I\approx\frac{2BCLK}{2BCLs_a}=\frac K{s_a}. $$

$s_a$ 是激活每元素字节数,FP16 时为 2,所以 $K=4$ 对应约 2 FLOPs/byte。这提示该算子容易受带宽约束,但不能据此断言所有 shape 都带宽受限。短序列可能受启动成本约束,SiLU 可能增加特殊函数成本,实际重复读写还会降低有效强度。

下一章:反向传播的完整推导。