Я пытаюсь создать проблему в 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__
вашей группе или компоненту. Задавать его в функции setup()
компонента или группы слишком поздно, потому что OpenMDAO выполняет все разбиение коммуникатора MPI в функции _setup_procs
, что происходит до при вызове setup
компонента/группы. Та же проблема с синхронизацией относится и к вызову функции approx_totals
. Он должен быть вызван до вызова Проблема setup
. Наконец, имя атрибута, который мы используем внутри для указания количества параллельных вычислений FD, на самом деле self._num_par_fd
, а не self.num_par_fd
. Установка внутреннего атрибута _num_par_fd
не рекомендуется, но если необходимо, вам придется установить его до Проблема вызывается setup
.
Примечание: это сильно отредактированная версия моего исходного ответа.
Это имеет смысл. Однако я попытался изменить
self.num_par_fd = matrix.shape[1]
наself._num_par_fd = matrix.shape[1]
, но получил точно такой же результат. Кроме того, когда я вместо этого изменил код, поэтому я делаюp.model = Model(num_par_fd=5)
, я получаюRuntimeError: '': num_par_fd = 5 but FD is not active.
.