我有一个迭代问题,在每次迭代中,我需要执行一系列矩阵运算,我希望加快这些运算的速度。除了一个变量外,大多数计算都涉及常量变量。我的问题是:有没有可能建立一个这个操作的“ backbone ”,以加快第一次迭代之后的计算速度?(有点类似于CVXPY中优化问题的参数热启动).我需要处理的代码目前在一个类的专用函数中,它是这样的:
# some constants definitions (in a class)
m = 5501
Y = np.random.rand(10, m)
U = np.random.rand(75, m)
sig_on = 0.15
sig_off = 0.25
a = 15
L = 25
def obtain_A_B(x):
# for readability I omit internal variable declarations as a = self.a
varying_part = (a * sig_on / np.linalg.norm(x)) ** 2 + L * sig_off
F = varying_part * np.identity(m) + Y.T @ Y
Fi = np.linalg.inv(F)
UFiUTi = np.linalg.inv(U @ Fi @ U.T)
A = (Fi - Fi @ U.T @ UFiUTi @ U @ Fi) @ Y.T
B = Fi @ U.T @ UFiUTi
return A, B
x = np.random.rand(10)
result = obtain_A_B(x)
我对加速A和B的计算很感兴趣,而'varying_part'返回一个标量,该标量在给定x的情况下每次迭代时都会发生变化。由于这里真正耗时的部分是计算逆Fi,因此加快计算速度将是一个很大的帮助。
我已经将求逆函数移到了专用变量中(如上所述),以减少需要计算的次数--这将执行时间从约41秒降低到约10秒--但如果我能使执行速度更快就更好了(这就是“操作 backbone ”想法的由来)。
1条答案
按热度按时间sz81bmfz1#
基本优化
首先,在
A
中使用B
的表达式,因此可以首先计算B
并在A
的表达式中重用B
。此外,
varying_part * np.identity(m) + Y.T @ Y
可以更有效地计算。事实上,varying_part * np.identity(m)
创建了2个很大的临时数组,填充速度很慢。这是因为现在的RAM与CPU的性能相比很慢(参见“内存墙”)。您可以只更新数组的对角线。此外,计算
B @ (U @ Fi)
比计算(B @ U) @ Fi
(等价于B @ U @ Fi
)要快得多,而两者在分析上是等价的,因为矩阵乘法是 associative(尽管在数值上有点不同)。下面是优化的实施:
这种优化使我的机器上的代码速度提高了55%,将近90%的时间花在了
np.linalg.inv(F)
函数调用上。坏消息是,矩阵求逆很难在CPU上优化。在Numpy中用于执行此计算的默认OpenBLAS库是次优的,但它相对较好,因为它使用相对优化的本地C代码并行运行。此外,更改对角线会严重影响计算,所以我认为不可能编写逆的增量计算。实际上,将
F[0,0]
增加1使得在高斯-乔丹算法中强制重新计算所有线的权重t0(并且因此所有随后的权重计算)。如果不降低浮点精度或算法的准确性(例如近似值),很难编写出更快的代码。如果您有服务器GPU,或者降低精度对您来说没什么问题,GPU会有很大帮助。
基于GPU的实现
我们可以使用GPU来提高目标平台和输入浮点类型的代码速度。离散GPU往往比主流CPU具有更大的计算能力。然而,对于主流PC GPU上的双精度来说,这并不正确。事实上,PC GPU针对单精度浮点数据进行了优化(通常用于游戏和3D应用程序)。为了实现快速的双精度计算,需要服务器端GPU。此外,需要注意的是,CPU和GPU之间的数据传输非常昂贵。但矩阵求逆是瓶颈,它在
O(N**3)
中运行,因此数据传输可以忽略不计。Cupy可以用来轻松编写GPU实现(适用于Nvidia GPU)。以下是生成的GPU代码:注意,Cupy在第一次执行时可能会非常慢,但之后应该会快得多(这对于这里的迭代循环来说是很好的)。
实验结果
以下是在我的机器上使用i5- 9600 KF CPU和Nvidia 1660超级GPU(主流独立PC GPU)的结果:
我们可以看到,基于GPU的实现在处理简单精度浮点输入时明显更快。这是因为我的GPU为此进行了优化。在计算服务器上,
obtain_A_B_gpu_opt
应该比其他实现快得多。请注意,cp.linalg.inv
在我的GPU上仍然占用了90%以上的时间。