【发布时间】:2020-12-31 05:17:40
【问题描述】:
def forward(x, f, s):
B, H, W, C = x.shape # e.g. 64, 16, 16, 3
Fh, Fw, C, _ = f.shape # e.g. 4, 4, 3, 3
# C is redeclared to emphasise that the dimension is the same
Sh, Sw = s # e.g. 2, 2
strided_shape = B, 1 + (H - Fh) // Sh, 1 + (W - Fw) // Sw, Fh, Fw, C
x = as_strided(x, strided_shape, strides=(
x.strides[0],
Sh * x.strides[1],
Sw * x.strides[2],
x.strides[1],
x.strides[2],
x.strides[3]),
)
# print(x.flags, f.flags)
# The reshaping changes the einsum from 'wxyijk,ijkd' to 'wxyz,zd->wxyd'
f = f.reshape(-1, f.shape[-1])
x = x.reshape(*x.shape[:3], -1) # Bottleneck!
return np.einsum('wxyz,zd->wxyd', x, f, optimize='optimal')
(相反,变体 without reshapes 使用return np.einsum('wxyijk,ijkd->wxyd', x, f))
作为参考,这里是 x 和 f 在重塑之前的标志:
x.flags:
C_CONTIGUOUS : False
F_CONTIGUOUS : False
OWNDATA : False
WRITEABLE : True
ALIGNED : True
WRITEBACKIFCOPY : False
UPDATEIFCOPY : False
f.flags:
C_CONTIGUOUS : True
F_CONTIGUOUS : False
OWNDATA : True
WRITEABLE : True
ALIGNED : True
WRITEBACKIFCOPY : False
UPDATEIFCOPY : False
有趣的是,例程中的主要瓶颈不是einsum,而是x 的重塑(扁平化)。我知道f 不会遇到此类问题,因为它的内存是 C 连续的,因此重塑相当于在不更改数据的情况下进行快速内部修改 - 但由于 x 不是 C 连续的(并且不拥有它的数据,就此而言),重塑要昂贵得多,因为它涉及经常更改数据/获取非缓存对齐的数据。这反过来又是由在x 上执行的as_strided 函数导致的——步幅的修改必须以扰乱自然顺序的方式进行。 (仅供参考,as_strided 的速度非常快,无论跨度如何,都应该很快)
有没有办法在不产生瓶颈的情况下达到相同的结果?也许通过在使用as_strided 之前重塑x?
另请注意,对于几乎 100% 的应用程序: B:[1-64],H,W:[1-60],C:[1-8] 前锋,前锋:[1-12]
我还在这里包含了一些图表,用于随着张量尺寸 B(批量大小)以及我设备上 H, W(图像大小)的变化而变化的时间(如您所见,涉及 reshape 的那个已经与 Tensorflow 具有相当的竞争力):
编辑:一个有趣的发现 - 重塑算法在 CPU 上以 5 倍击败非重塑算法,但是当我使用 GPU(即使用 CuPy 而不是 NumPy)时,两种算法都同样快(大约是 TensorFlow 前向传播速度的两倍)
标签: python numpy conv-neural-network tensor numpy-einsum