Pull to refresh

Behind the scene of TOP-1 supercomputer

Reading time 5 min
Views 12K

Это история о том, как мы c mildly_parallel замедляли ускоряли расчеты на самом мощном суперкомпьютере в мире.



В апреле этого года наша команда приняла участие в финале Asia supercomputer challenge 2017, одним из заданий которого было ускорение программы для моделирования океанских волн Masnum-Wave на китайском суперкомпьютере Sunway TaihuLight.


Все началось с отборочного тура в феврале: мы получили доступ к суперкомпьютеру и познакомились с нашим новым другом на ближайшие пару месяцев.


Вычислительные узлы выставлены в форме двух овалов, между которыми стоит сетевое железо. В узлах используются процессоры Shenwei 26010. Каждый из них состоит из 4 гетерогенных процессорных групп, которые включают одно управляющее и 64 вычислительных ядра с тактовой частотой 1,45 ГГц и размером локального кэша 64 Кб.



Охлаждается все это с помощью водяной системы, расположенной в отдельном здании.


Из ПО в нашем распоряжении были компиляторы фортрана и си с “поддержкой” OpenACC 2 и athreads (аналог POSIX Threads) и планировщик задач, совмещающий в себе одновременно возможности обычного планировщика и mpirun.


Доступ к кластеру осуществлялся через особый VPN, плагины для работы с которым были доступны только для Windows и Mac OS. Все это добавляло особенного шарма при работе.


Заданием было ускорение Masnum-Wave на этом суперкомпьютере. Нам предоставили исходный код Masnum-Wave, несколько крохотных readme файлов с описанием основ работы на кластере и данные для замера ускорения.


Masnum-Wave – это программа для моделирования движения волн по всему земному шару. Она написана на Фортране с использованием MPI. В двух словах, она итеративно выполняет следующее: читает данные функций внешнего воздействия, синхронизирует граничные области между MPI-процессами, вычисляет продвижение волн и сохраняет результаты. Нам дали workload на 8 модельных месяцев с шагом по 7.5 минуты.


В первый же день мы нашли в интернете статью: “The Sunway TaihuLight supercomputer:
system and applications, Haohuan Fu” с описанием ускорения Masnum-Wave на архитектуре Sunway TaihuLight с помощью конвейерной обработки. Авторы статьи использовали всю мощь кластера (10649600 ядер), в нашем же распоряжении было 64 вычислительных группы (4160 ядер).


Masnum-Wave состоит из нескольких модулей:



Мы впервые столкнулись с таким большим количеством кода, порожденного наукой. Так как код представляет собой смесь двух версий фортрана 90 и 77, это порой ломало работу их собственного компилятора. В большом количестве там встречаются чудесные конструкции goto, куски закомментированного кода и, конечно же, комментарии на китайском.


Сокращенный пример кода для наглядности:


do 1201 j=1,jl
        js=j
        j11=jp1(mr,j)
        j12=jp2(mr,j)
        j21=jm1(mr,j)
        j22=jm2(mr,j)

!****************************************************************

!      do 1202 ia=ix1,ix2
!      do 1202 ic=iy1,iy2

        eij=e(ks,js,ia,ic)
        !if (eij.lt.1.e-20) goto 1201
        if (eij.lt.1.e-20) cycle
        ea1=e(kp ,j11,ia,ic)
        ea2=e(kp ,j12,ia,ic)
! ...
        eij2=eij**2
        zua=2.*eij/al31
        ead1=sap/al11+sam/al21
        ead2=-2.*sap*sam/al31
        !      fcen=fcnss(k,ia,ic)*enh(ia,ic)
        fcen=fconst0(k,ia,ic)*enh(ia,ic)
        ad=cwks17*(eij2*ead1+ead2*eij)*fcen
        adp=ad/al13
        adm=ad/al23
        delad =cwks17*(eij*2.*ead1+ead2) *fcen
        deladp=cwks17*(eij2/al11-zua*sam)*fcen/al13
        deladm=cwks17*(eij2/al21-zua*sap)*fcen/al23

        !*      nonlinear transfer

        se(ks ,js )= se(ks ,js )-2.0*ad
        se(kp2,j11)= se(kp2,j11)+adp*wp11
        se(kp2,j12)= se(kp2,j12)+adp*wp12
        se(kp3,j11)= se(kp3,j11)+adp*wp21
        se(kp3,j12)= se(kp3,j12)+adp*wp22
        se(im ,j21)= se(im ,j21)+adm*wm11
        se(im ,j22)= se(im ,j22)+adm*wm12
        se(im1,j21)= se(im1,j21)+adm*wm21
        se(im1,j22)= se(im1,j22)+adm*wm22

!...

!        1202      continue
!        1212      continue
      1201      continue

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


Изучив скудную документацию к китайским компиляторам, мы решили использовать OpenACC, так как это стандарт от Nvidia с примерами и спецификацией. К тому же, код из readme к athreads казался нам неоправданно сложным и просто не компилировался. Как же мы ошибались.


Одна из первых идей, которая приходит в голову при оптимизации кода на ускорителях – это использование локальной памяти. В OpenACC это можно сделать несколькими директивами, но результат всегда должны быть один: данные перед началом вычислений должны быть скопированы на локальную память.


Для проверки и выбора нужной директивы мы написали на фортране несколько тестовых программ, на которых убедились, что они работают и что можно таким образом получить ускорение. Далее мы расставили эти директивы в Masnum-Wave, указав им имена наиболее используемых переменных. После компиляции они стали появляться в логах, сопутствующие надписи на китайском не были подсвечены красным, и мы посчитали, что все скопировалось и работает.



Но оказалось, что не все так просто. Компилятор OpenACC не копировал массивы в Masnum-Wave, но работал исправно с тестовыми программами. Проведя пару дней с Google Translate мы поняли, что он не копирует объекты, которые определены в файлах, подключаемых через директивы препроцессора (include)!


Всю следующею неделю мы переносили код Masnum-Wave из подключаемых файлов (а их больше 30) в файлы с исходным кодом, при этом нужно было убедиться, что все определятся и линкуется в правильном порядке. Но, так как ни у кого из нас не было опыта работы с Фортраном, и все сводилось к “давайте жахнем и посмотрим, что получится”, то без замены некоторых основных функций на костыльные версии тут тоже не обошлось.


И вот, когда все модули были перелопачены, и мы, в надежде получить свое мизерное ускорение, запустили свежескомилированный код, то получили очередную порцию разочарования! Директивы, написанные по всем канонам стандарта OpenACC 2.0, выдают ошибки в runtime. В этот момент в наши головы начала закрадываться идея, что этот чудесный суперкомпьютер поддерживает какой-то свой особый стандарт.


Тогда мы попросили у организаторов соревнований документацию, и с третьей попытки они нам ее предоставили.



Пару часов с Google Translate подтвердили наши опасения: стандарт, который они поддерживают, называется OpenACC 0.5, и он кардинально отличается от OpenACC 2.0, поставляемого с pgi компилятором.


Например, основной нашей задумкой было переиспользование данных на ускорителе. Для выполнения этого в стандарте 2.0 необходимо обернуть параллельный код в блок data. Вот как это делается в примерах от Nvidia:


!$acc data copy(A, Anew)
 do while ( error .gt. tol .and. iter .lt. iter_max )
   error=0.0_fp_kind

!$omp parallel do shared(m, n, Anew, A) reduction( max:error )
!$acc kernels
   do j=1,m-2
do i=1,n-2
 Anew(i,j) = 0.25_fp_kind * ( A(i+1,j  ) + A(i-1,j  ) + &
A(i  ,j-1) + A(i  ,j+1) )
 error = max( error, abs(Anew(i,j)-A(i,j)) )
end do
   end do
!$acc end kernels
!$omp end parallel do

   if(mod(iter,100).eq.0 ) write(*,'(i5,f10.6)'), iter, error
   iter = iter +1

!$omp parallel do shared(m, n, Anew, A)
!$acc kernels
   do j=1,m-2
do i=1,n-2
 A(i,j) = Anew(i,j)
end do
   end do
!$acc end kernels
!$omp end parallel do

 end do
!$acc end data

Но на кластере этот код не скомпилируется, потому что в их стандарте эта операция делается через указание индекса для каждого блока данных:


#include <stdio.h>
#include <stdlib.h>

#define NN 128

int A[NN],B[NN],C[NN];

int main()
{
    int i;
    for (i=0;i<NN;i++)
    {
        A[i]=1;
        B[i]=2;
    }

    #pragma acc data index(1)
    {
        #pragma acc parallel loop packin(A,B, at data 1) copyout(C)
        for (i=0;i<NN;i++)
        {
            C[i]=A[i]+B[i];
        }

    }
    for(i=0;i<NN;i++)
    {
        if(C[i]!=3)
        {
            printf("Test Error! C[%d] = %d\n", i, C[i]);
            exit(-1);
        }
    }
    printf("Test OL!\n");
    return 0;
}

Коря себя за выбор OpenACC, мы все же продолжили работу, так как времени оставалось всего пару дней. В последний день отборочного тура нам наконец-то удалось запустить наш “ускоренный” код. Мы получили замедление в 3.5 раза. Нам ничего не оставалось, кроме как написать в отчете к заданию все, что мы думаем о их реализации OpenACC в цензурной форме. Не смотря на это, мы получили множество позитивных эмоций. Когда еще придется удаленно отлаживать код на самом мощном компьютере в мире?


P.S.: В результате мы все же прошли в финальную часть конкурса и съездили в Китай.
Последним заданием финала была презентация с описанием решений. Лучшего результата добилась местная команда, которая написала свою библиотеку на Си c использованием athread т.к. OpenACC, по их словам, не работает.

Tags:
Hubs:
+47
Comments 15
Comments Comments 15

Articles