OpenMDAO 中的并行有限差分计算在每个进程中执行每个点
Parallel finite-difference calculation in OpenMDAO executes each point in each process
我正在尝试在 OpenMDAO 中设置一个问题,并希望使用并行有限差分计算。但是,当我调用 compute_totals()
时,每个 MPI 进程实际上都会计算所有扰动点。
我做了一个最小的例子来说明这个问题。考虑可以用矩阵乘法表示的模型的简单情况。该模型的雅可比矩阵就是模型的矩阵。请看下面的代码:
import numpy as np
import time
from openmdao.api import ExplicitComponent, Problem, IndepVarComp, Group
from openmdao.utils.mpi import MPI
rank = 0 if not MPI else MPI.COMM_WORLD.rank
class MatMultComp(ExplicitComponent):
def __init__(self, matrix, **kwargs):
super().__init__(**kwargs)
self.matrix = matrix
def setup(self):
self.add_input('x', val=np.ones(self.matrix.shape[1])))
self.add_output('y', val=np.ones(self.matrix.shape[0])))
def compute(self, inputs, outputs, **kwargs):
outputs['y'] = self.matrix.dot(inputs['x'])
print('{} :: x = {}'.format(rank, np.array_str(inputs['x'])))
class Model(Group):
def setup(self):
matrix = np.arange(25, dtype=float).reshape(5, 5)
self.add_subsystem('ivc', IndepVarComp('x', np.ones(matrix.shape[1])), promotes=['*'])
self.add_subsystem('mat', MatMultComp(matrix), promotes=['*'])
self.approx_totals(step=0.1)
self.num_par_fd = matrix.shape[1]
if __name__ == '__main__':
p = Problem()
p.model = Model()
p.setup()
p.run_model()
t0 = time.time()
jac = p.compute_totals(of=['y'], wrt=['x'], return_format='array')
dt = time.time() - t0
if rank == 0:
print('Took {:2.3f} seconds.'.format(dt))
print('J = ')
print(np.array_str(jac, precision=0))
当我 运行 此代码没有 MPI 时,我得到以下输出:
0 :: x = [1. 1. 1. 1. 1.]
0 :: x = [1.1 1. 1. 1. 1. ]
0 :: x = [1. 1.1 1. 1. 1. ]
0 :: x = [1. 1. 1.1 1. 1. ]
0 :: x = [1. 1. 1. 1.1 1. ]
0 :: x = [1. 1. 1. 1. 1.1]
Took 5.008 seconds.
J =
[[ 0. 1. 2. 3. 4.]
[ 5. 6. 7. 8. 9.]
[10. 11. 12. 13. 14.]
[15. 16. 17. 18. 19.]
[20. 21. 22. 23. 24.]]
这是正确的结果,如预期的那样大约需要 5 秒。现在,当我 运行 使用 MPI,使用 5 个进程,使用命令 mpirun -np 5 python matmult.py
,我得到以下输出:
0 :: x = [1. 1. 1. 1. 1.]
1 :: x = [1. 1. 1. 1. 1.]
2 :: x = [1. 1. 1. 1. 1.]
3 :: x = [1. 1. 1. 1. 1.]
4 :: x = [1. 1. 1. 1. 1.]
0 :: x = [1.001 1. 1. 1. 1. ]
1 :: x = [1.001 1. 1. 1. 1. ]
2 :: x = [1.001 1. 1. 1. 1. ]
3 :: x = [1.001 1. 1. 1. 1. ]
4 :: x = [1.001 1. 1. 1. 1. ]
3 :: x = [1. 1.001 1. 1. 1. ]
0 :: x = [1. 1.001 1. 1. 1. ]
1 :: x = [1. 1.001 1. 1. 1. ]
2 :: x = [1. 1.001 1. 1. 1. ]
4 :: x = [1. 1.001 1. 1. 1. ]
2 :: x = [1. 1. 1.001 1. 1. ]
3 :: x = [1. 1. 1.001 1. 1. ]
0 :: x = [1. 1. 1.001 1. 1. ]
1 :: x = [1. 1. 1.001 1. 1. ]
4 :: x = [1. 1. 1.001 1. 1. ]
1 :: x = [1. 1. 1. 1.001 1. ]
2 :: x = [1. 1. 1. 1.001 1. ]
3 :: x = [1. 1. 1. 1.001 1. ]
0 :: x = [1. 1. 1. 1.001 1. ]
4 :: x = [1. 1. 1. 1.001 1. ]
0 :: x = [1. 1. 1. 1. 1.001]
1 :: x = [1. 1. 1. 1. 1.001]
2 :: x = [1. 1. 1. 1. 1.001]
3 :: x = [1. 1. 1. 1. 1.001]
4 :: x = [1. 1. 1. 1. 1.001]
Took 5.072 seconds.
J =
[[ 0. 1. 2. 3. 4.]
[ 5. 6. 7. 8. 9.]
[10. 11. 12. 13. 14.]
[15. 16. 17. 18. 19.]
[20. 21. 22. 23. 24.]]
最后的结果当然是正确的。然而,这违背了使用 MPI 的目的,因为 5 个进程中的每一个都计算了所有的扰动点,并且总的执行时间和以前一样大约需要 5 秒。我期望得到以下输出:
0 :: x = [1. 1. 1. 1. 1.]
1 :: x = [1. 1. 1. 1. 1.]
2 :: x = [1. 1. 1. 1. 1.]
3 :: x = [1. 1. 1. 1. 1.]
4 :: x = [1. 1. 1. 1. 1.]
0 :: x = [1.1 1. 1. 1. 1. ]
1 :: x = [1. 1.1 1. 1. 1. ]
2 :: x = [1. 1. 1.1 1. 1. ]
3 :: x = [1. 1. 1. 1.1 1. ]
4 :: x = [1. 1. 1. 1. 1.1]
Took 1.000 seconds.
J =
[[ 0. 1. 2. 3. 4.]
[ 5. 6. 7. 8. 9.]
[10. 11. 12. 13. 14.]
[15. 16. 17. 18. 19.]
[20. 21. 22. 23. 24.]]
请注意,实际上进程完成的顺序是任意的,所花费的时间将超过 1 秒。
我怎样才能让它按预期工作?请注意,我使用的是 OpenMDAO 2.5.0.
这里有一些问题。首先是 num_par_fd
通常应作为 __init__
arg 传递给您的组或组件。在组件或组的 setup()
函数中设置它为时已晚,因为 OpenMDAO 在 _setup_procs
函数中执行其所有 MPI 通信器拆分,这发生在 之前 Component/Group setup
调用。同样的计时问题也适用于调用 approx_totals
函数。它必须在问题 setup
调用之前调用。最后,我们内部用来指定并行 FD 计算数量的属性名称实际上是 self._num_par_fd
而不是 self.num_par_fd
。不建议设置内部 _num_par_fd
属性,但如果必须,则必须在 之前设置它 问题 setup
被调用。
注意:这是我原始答案的经过大量编辑的版本。
我正在尝试在 OpenMDAO 中设置一个问题,并希望使用并行有限差分计算。但是,当我调用 compute_totals()
时,每个 MPI 进程实际上都会计算所有扰动点。
我做了一个最小的例子来说明这个问题。考虑可以用矩阵乘法表示的模型的简单情况。该模型的雅可比矩阵就是模型的矩阵。请看下面的代码:
import numpy as np
import time
from openmdao.api import ExplicitComponent, Problem, IndepVarComp, Group
from openmdao.utils.mpi import MPI
rank = 0 if not MPI else MPI.COMM_WORLD.rank
class MatMultComp(ExplicitComponent):
def __init__(self, matrix, **kwargs):
super().__init__(**kwargs)
self.matrix = matrix
def setup(self):
self.add_input('x', val=np.ones(self.matrix.shape[1])))
self.add_output('y', val=np.ones(self.matrix.shape[0])))
def compute(self, inputs, outputs, **kwargs):
outputs['y'] = self.matrix.dot(inputs['x'])
print('{} :: x = {}'.format(rank, np.array_str(inputs['x'])))
class Model(Group):
def setup(self):
matrix = np.arange(25, dtype=float).reshape(5, 5)
self.add_subsystem('ivc', IndepVarComp('x', np.ones(matrix.shape[1])), promotes=['*'])
self.add_subsystem('mat', MatMultComp(matrix), promotes=['*'])
self.approx_totals(step=0.1)
self.num_par_fd = matrix.shape[1]
if __name__ == '__main__':
p = Problem()
p.model = Model()
p.setup()
p.run_model()
t0 = time.time()
jac = p.compute_totals(of=['y'], wrt=['x'], return_format='array')
dt = time.time() - t0
if rank == 0:
print('Took {:2.3f} seconds.'.format(dt))
print('J = ')
print(np.array_str(jac, precision=0))
当我 运行 此代码没有 MPI 时,我得到以下输出:
0 :: x = [1. 1. 1. 1. 1.]
0 :: x = [1.1 1. 1. 1. 1. ]
0 :: x = [1. 1.1 1. 1. 1. ]
0 :: x = [1. 1. 1.1 1. 1. ]
0 :: x = [1. 1. 1. 1.1 1. ]
0 :: x = [1. 1. 1. 1. 1.1]
Took 5.008 seconds.
J =
[[ 0. 1. 2. 3. 4.]
[ 5. 6. 7. 8. 9.]
[10. 11. 12. 13. 14.]
[15. 16. 17. 18. 19.]
[20. 21. 22. 23. 24.]]
这是正确的结果,如预期的那样大约需要 5 秒。现在,当我 运行 使用 MPI,使用 5 个进程,使用命令 mpirun -np 5 python matmult.py
,我得到以下输出:
0 :: x = [1. 1. 1. 1. 1.]
1 :: x = [1. 1. 1. 1. 1.]
2 :: x = [1. 1. 1. 1. 1.]
3 :: x = [1. 1. 1. 1. 1.]
4 :: x = [1. 1. 1. 1. 1.]
0 :: x = [1.001 1. 1. 1. 1. ]
1 :: x = [1.001 1. 1. 1. 1. ]
2 :: x = [1.001 1. 1. 1. 1. ]
3 :: x = [1.001 1. 1. 1. 1. ]
4 :: x = [1.001 1. 1. 1. 1. ]
3 :: x = [1. 1.001 1. 1. 1. ]
0 :: x = [1. 1.001 1. 1. 1. ]
1 :: x = [1. 1.001 1. 1. 1. ]
2 :: x = [1. 1.001 1. 1. 1. ]
4 :: x = [1. 1.001 1. 1. 1. ]
2 :: x = [1. 1. 1.001 1. 1. ]
3 :: x = [1. 1. 1.001 1. 1. ]
0 :: x = [1. 1. 1.001 1. 1. ]
1 :: x = [1. 1. 1.001 1. 1. ]
4 :: x = [1. 1. 1.001 1. 1. ]
1 :: x = [1. 1. 1. 1.001 1. ]
2 :: x = [1. 1. 1. 1.001 1. ]
3 :: x = [1. 1. 1. 1.001 1. ]
0 :: x = [1. 1. 1. 1.001 1. ]
4 :: x = [1. 1. 1. 1.001 1. ]
0 :: x = [1. 1. 1. 1. 1.001]
1 :: x = [1. 1. 1. 1. 1.001]
2 :: x = [1. 1. 1. 1. 1.001]
3 :: x = [1. 1. 1. 1. 1.001]
4 :: x = [1. 1. 1. 1. 1.001]
Took 5.072 seconds.
J =
[[ 0. 1. 2. 3. 4.]
[ 5. 6. 7. 8. 9.]
[10. 11. 12. 13. 14.]
[15. 16. 17. 18. 19.]
[20. 21. 22. 23. 24.]]
最后的结果当然是正确的。然而,这违背了使用 MPI 的目的,因为 5 个进程中的每一个都计算了所有的扰动点,并且总的执行时间和以前一样大约需要 5 秒。我期望得到以下输出:
0 :: x = [1. 1. 1. 1. 1.]
1 :: x = [1. 1. 1. 1. 1.]
2 :: x = [1. 1. 1. 1. 1.]
3 :: x = [1. 1. 1. 1. 1.]
4 :: x = [1. 1. 1. 1. 1.]
0 :: x = [1.1 1. 1. 1. 1. ]
1 :: x = [1. 1.1 1. 1. 1. ]
2 :: x = [1. 1. 1.1 1. 1. ]
3 :: x = [1. 1. 1. 1.1 1. ]
4 :: x = [1. 1. 1. 1. 1.1]
Took 1.000 seconds.
J =
[[ 0. 1. 2. 3. 4.]
[ 5. 6. 7. 8. 9.]
[10. 11. 12. 13. 14.]
[15. 16. 17. 18. 19.]
[20. 21. 22. 23. 24.]]
请注意,实际上进程完成的顺序是任意的,所花费的时间将超过 1 秒。
我怎样才能让它按预期工作?请注意,我使用的是 OpenMDAO 2.5.0.
这里有一些问题。首先是 num_par_fd
通常应作为 __init__
arg 传递给您的组或组件。在组件或组的 setup()
函数中设置它为时已晚,因为 OpenMDAO 在 _setup_procs
函数中执行其所有 MPI 通信器拆分,这发生在 之前 Component/Group setup
调用。同样的计时问题也适用于调用 approx_totals
函数。它必须在问题 setup
调用之前调用。最后,我们内部用来指定并行 FD 计算数量的属性名称实际上是 self._num_par_fd
而不是 self.num_par_fd
。不建议设置内部 _num_par_fd
属性,但如果必须,则必须在 之前设置它 问题 setup
被调用。
注意:这是我原始答案的经过大量编辑的版本。