Ускорение неполного предварительного кондиционирования LDL ^ T с помощью OpenACC

#openacc

#openacc

Вопрос:

Оригинальный вопрос:

Я относительно новичок в OpenACC, но до сих пор мне удавалось с относительным успехом ускорить мой набор итеративных решателей Fortran (на основе CG), и я получаю ускорение около 7 на моей видеокарте Nvidia RTX GeForce. Все работает нормально, если предварительное кондиционирование не используется или используется диагональное предварительное кондиционирование. Но проблемы начинаются, если я хочу ускорить немного более сложные предварительные условия — inclomplete LDL ^ T является моим любимым.

Фрагмент кода, который выполняет неполную факторизацию LDL ^ T, выглядит следующим образом:

 20   do i = 1, n                        ! browse through rows
21     sum1 = a % val(a % dia(i))       ! take diagonal entry
22     do j = a % row(i), a % dia(i)-1  ! browse only lower triangular part
23       k = a % col(j)                 ! fetch the column
24       sum1 = sum1 - f % val(f % dia(k)) * a % val(j) * a % val(j)
25     end do
26
27     ! Keep only the diagonal from LDL decomposition
28     f % val(f % dia(i)) = 1.0 / sum1
29   end do
  

Этот фрагмент кода по своей сути является последовательным. Просматривая строки, я использую факторизацию, выполненную в предыдущих строках, поэтому с самого начала ее нелегко распараллелить. Единственный способ, который мне удалось найти, чтобы скомпилировать его с помощью директивы OpenACC, и имея в виду его последовательный характер, это:

 20   !$acc  parallel loop seq                               amp;
21   !$accamp; present(a, a % row, a % col, a % dia, a % val)  amp;
22   !$accamp; present(f, f % row, f % col, f % dia, f % val)
23   do i = 1, n                        ! browse through rows
24     sum1 = a % val(a % dia(i))       ! take diagonal entry
25     !$acc loop vector reduction( :sum1)
26     do j = a % row(i), a % dia(i)-1  ! browse only lower triangular part
27       k = a % col(j)                 ! fetch the column
28       sum1 = sum1 - f % val(f % dia(k)) * a % val(j) * a % val(j)
29     end do 
30
31     ! Keep only the diagonal from LDL decomposition
32     f % val(f % dia(i)) = 1.0 / sum1
33   end do
  

Хотя эти директивы OpenACC сохраняют вычисления на графических процессорах и дают те же результаты, что и вариант без графического процессора, те из вас, кто более опытен, чем я, уже увидят, что здесь не так много параллелизма и ускорения. Внешний цикл ( i , через строки) является последовательным, а внутренний цикл ( j и k ) просматривает только несколько элементов.

В целом, графический вариант неполного LDL ^ T-CG-решателя с предварительной обработкой в несколько раз медленнее, чем версия без GPU.

У кого-нибудь есть идея, как обойти это? Я понимаю, что это может быть далеко не тривиально, но, возможно, по этому вопросу уже была проделана некоторая работа, о которой я не знаю, или, может быть, есть лучший способ использовать OpenACC.

Обновление через пару дней

Я сделал свою домашнюю работу, прочитал несколько статей на сайте Nvidia и за его пределами, и да, алгоритм факторизации действительно последовательный, и, похоже, вопрос о том, как это сделать на графических процессорах, остается открытым. В этом недавнем блоге: https://docs.nvidia.com/cuda/incomplete-lu-cholesky/index.html автор использует cuBLAS и cuSPARSE для CG, но все еще выполняет факторизацию на CPU. Среднее ускорение, о котором он сообщает, составляет около двух, что, смею сказать, немного разочаровывает.

Итак, я решил обойти проблему, раскрасив строки матрицы и выполнив повторную факторизацию цвет за цветом, вот так:

 21   n_colors = 8
22
23   do color = 1, n_colors
24
25     c_low = (color-1) * n / n_colors   1  ! color's upper bound
26     c_upp =  color    * n / n_colors      ! color's lower bound
27
28     do i = c_low, c_upp                ! browse your color band
29       sum1 = a % val(a % dia(i))       ! take diagonal entry
30       do j = a % row(i), a % dia(i)-1  ! only lower traingular
31         k = a % col(j)
32         if(k >= c_low) then            ! limit entries to your color
33           sum1 = sum1 - f % val(f % dia(k)) * a % val(j) * a % val(j)
34         end if
35       end do
36
37       ! This is only the diagonal from LDL decomposition
38       f % val(f % dia(i)) = 1.0 / sum1
39     end do
40   end do    ! colors
  

В строке 32 я ограничиваю записи матрицы только цветом, принадлежащим его циклу. Очевидно, что, поскольку неполнота факторизованной матрицы более выражена (пренебрегается большим количеством записей), свойства сходимости хуже, но все же лучше, чем при простом диагональном предобуславливателе.

Я сделал следующее с OpenACC:

 21   n_colors = 8
22
23   !$acc  parallel loop num_gangs(8) tile(8)  ! do each tile in its own color
24   !$accamp; present(a, a % row, a % col, a % dia, a % val)  amp;
25   !$accamp; present(f, f % row, f % col, f % dia, f % val)
26   do color = 1, n_colors
27
28     c_low = (color-1) * n / n_colors   1  ! color's upper bound
29     c_upp =  color    * n / n_colors      ! color's lower bound
30
31     !$acc loop seq                     ! inherently sequential
32     do i = c_low, c_upp                ! browse your color band
33       sum1 = a % val(a % dia(i))       ! take diagonal entry
34       !$acc loop vector reduction( :sum1)
35       do j = a % row(i), a % dia(i)-1  ! only lower traingular
36         k = a % col(j)
37         if(k >= c_low) then            ! limit entries to your color
38           sum1 = sum1 - f % val(f % dia(k)) * a % val(j) * a % val(j)
39         end if
40       end do
41
42       ! This is only the diagonal from LDL decomposition
43       f % val(f % dia(i)) = 1.0 / sum1
44     end do 
45   end do    ! colors
  

Результаты, которые я получаю от OpenACC variant, идентичны результатам только для CPU, просто производительность на графических процессорах все еще намного ниже. Тем не менее, кажется, что tile директива работает так, как я ожидал, каждая группа, похоже, работает над своим собственным цветом, поскольку результаты, которые я получаю от графических процессоров, идентичны.

Есть какие-либо советы о том, как повысить производительность? Я делаю что-то совершенно глупое в приведенном выше коде? (Профилировщик показывает, что вычисления привязаны к GPU, поскольку вся система работает на GPU, но производительность действительно низкая.)

С наилучшими пожеланиями

Ответ №1:

Не уверен, что это поможет, но в этой статье описывается метод решения CG на графических процессорах. Он использует cuSPARSE и CUDA для реализаций, но вы могли бы получить идеи, которые можно было бы применить к вашему коду.

https://www.dcs.warwick.ac.uk/pmbs/pmbs14/PMBS14/Workshop_Schedule_files/8-CUDAHPCG.pdf