Behind the scene of TOP-1 supercomputer

МЕНЮ


Искусственный интеллект
Поиск
Регистрация на сайте
Помощь проекту

ТЕМЫ


Новости ИИРазработка ИИВнедрение ИИРабота разума и сознаниеМодель мозгаРобототехника, БПЛАТрансгуманизмОбработка текстаТеория эволюцииДополненная реальностьЖелезоКиберугрозыНаучный мирИТ индустрияРазработка ПОТеория информацииМатематикаЦифровая экономика

Авторизация



RSS


RSS новости


Это история о том, как мы 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
", i, C[i]);             exit(-1);         }     }     printf("Test OL!
");     return 0; }

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

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


Источник: habrahabr.ru

Комментарии: