SDAccel — проверяем передачу данных

в 23:57, , рубрики: fpga, gpgpu, opencl, sdaccel, xilinx, высокая производительность, параллельное программирование

SDAccel — проверяем передачу данных - 1

В предыдущей статье «SDAccel – первое знакомство» я попытался описать основы применения OpenCL на ПЛИС Xilinx. Теперь настало время поделиться результатами экспериментов по передаче данных на модуле ADP-PCIe-KU3. Проверяется передача данных в обоих направлениях. Исходный код программ размещён на GitHub: https://github.com/dsmv/sdaccel

Аппаратура

Все эксперименты выполнены на модуле ADM-PCIe_KU3 компании Alpha-Data

SDAccel — проверяем передачу данных - 2

Центральным элементом является ПЛИС Xilinx Kintex UltraScale KU060
К ПЛИС подключены два модуля SODIMM DDR3-1600; Ширина памяти 72 бита, это даёт возможность использовать контроллер памяти с коррекцией ошибок.

Существует возможность подключения двух QSFP модулей. Каждый QSFP модуль это четыре двунаправленные линии со скоростью передачи до 10 Гбит/с. Это даёт возможность использовать 1G, 10G, 40G Ethernet, в том числе реализовать Low-Latency Network Card. Также есть интересное свойство – ввод секундной метки от GPS приёмника. Но в данной работе всё это не используется.

Сервер NIMBIX

Сервер NIMBIX предоставляет разные вычислительные услуги, в том числе среду разработки SDAccel и что более важно – выполнение программы на выбранном аппаратном модуле.

Модель вычислителя

Хочу напомнить, что из себя представляет система OpenCL.

SDAccel — проверяем передачу данных - 3

Система состоит из HOST компьютера и вычислителя, которые связаны между собой по шине. В данном случае это PCI Express v3.0 x8;

Прикладное программное обеспечение состоит из двух частей:

  • Программа HOST компьютера.
  • Одна или несколько функций для работы на вычислителе.

Обмен данными идёт только через глобальную память, в данном случае это два модуля SODIMM.

Для работы прикладного ПО требуется инфраструктура, которую должен кто-то предоставить. В данном случае — компания Xilinx. В состав инфраструктуры входят:

  • библиотека opencl — реализация функций стандарта OpenCL.
  • драйвер модуля — обеспечивает взаимодействие с модулем.
  • пакет DSA. Это основа для разработки собственной прошивки ПЛИС.

Пакет DSA содержит базовую прошивку, в состав которой входят контроллеры для PCI Express, динамической памяти и возможно для других узлов. В составе базовой прошивки есть элемент, который называется OpenCL Region. Вот внутри именно этого элемента и будут реализованы все функции OpenCL kernels. Загрузка прошивки внутрь OpenCL Regions производится через PCI Express с использованием технологии частичной перезагрузки (Partial Reconfiguration). Надо отметить, что Xilinx сильно продвинулся в скорости загрузки. Если в предыдущих версиях загрузка занимала несколько минут, что сейчас около 5 секунд. А в версии 2017.2 объявлено что можно вообще не проводить повторную загрузку прошивки.

На данный момент для модуля ADM-PCIe-KU3 в составе пакета SDAccel доступно два пакета:

  • xilinx:adm-pcie-ku3:2ddr:3.3
  • xilinx:adm-pcie-ku3:2ddr-xpr:4.0

Оба пакета имеют поддержку двух контроллеров памяти и PCI Express v3.0 x8; Обратите внимание на суффикс -xpr. Это достаточно важное различие. Вариант без xpr фиксирует положение DDR контроллеров и PCI Express. Вариант с xpr фиксирует только положение PCI Express, а контроллеры DDR участвуют в трассировке прикладных функций OpenCL. Это различие приводит и к различиям в результатах. Вариант без xpr разводится быстрее, а вариант с xpr может получить более оптимальную трассировку. Для данного проекта получилось 1 час 11 минут для варианта без xpr и 1 час 32 минуты для варианта xpr. Логи здесь.

Кстати, в состав каждого DSA пакета входит свой драйвер.

Программа CHECK_TRANSFER

Программа предназначена для проверки непрерывной передачи данных в трёх режимах:

  • Из ПЛИС в SODIM и в компьютер
  • Из компьютера в SODIMM и в ПЛИС
  • Одновременная передача в двух направлениях

На мой взгляд проверка скорости работы без проверки данных особого смысла не имеет. Поэтому я с помощью OpenCL реализовал на ПЛИС узел генератора тестовой последовательности и узел проверки тестовой последовательности.

Стандарт OpenCL предусматривает обмен между устройством и компьютером только через глобальную память устройства. Обычно это динамическая память на SODIMM. И здесь возникает очень интересный вопрос о возможности передачи данных с предельными скоростями. На модуле ADM-PCIe-KU3 применены два SODIM DDR3-1600. Скорость обмена для одного SODIMM около 10 Гбайт/с. Скорость обмена по шине PCI Express v3.0 x8 – около 5 Гбайт/с (пока получилось намного меньше). Т.е. существует возможность записывать в память один блок поступающий от PCI Express и одновременно считывать второй блок для обработки внутри ПЛИС. А что делать если надо ещё возвращать результат? PCI Express обеспечивает двунаправленный поток на высокой скорости. Но у памяти шина одна, и скорость будет делиться между чтением и записью. Вот здесь и нужен второй SODIMM. У нас существует возможность указать в каком именно модуле будет размещён буфер для обработки.

Операционная система

SDAccel может работать только под некоторыми системами Linux. В списке доступных систем CentOS 6.8, CentOS 7.3, Ubuntu 16.04, RedHat 6.8, RedHat 7.3; Первые эксперименты я начинал на CentOs 6.7; Далее попробовал использовать Ubuntu 16.04, но там не всё заработало. На данный момент я использую CentOS 7.3 и очень доволен этой системой. Однако при настройке SDAccel есть ряд тонкостей. Главная проблема – по умолчанию сетевой интерфейс имеет имя “enp6s0”. Такое имя не понимает сервер лицензий Xilinx. Ему требуется обычный “eth0”.
Настройка здесь: https://github.com/dsmv/sdaccel/wiki/note_04---Install-CentOS-7-and-SDAccel-2017.1

Qt 5.9.1 устанавливается но не работает. Для него требуется более новый компилятор gcc и git. Это тоже решается, подробности здесь: https://github.com/dsmv/sdaccel/wiki/note_05---Install-Qt-5.9.1-and-Git-2.9.3

Системы разработки

Для разработки я использую две системы:

  • SDAccel 2017.1 – для разработки kernal и небольших тестовых программ HOST
  • Qt 5.9.1 – для разработки более сложных программ. Qt используется только для разработки программ HOST.

Организация проекта на GitHub

Репозитарий dsmv/sdaccel предназначен для разработки примеров для SDAccel. В данный момент там есть только одна программа check_transfer. Для проекта используются ряд возможностей GitHub:

  • README.md – первый файл, который виден посетителю.
  • WiKi – описание программы
  • Development Notes – заметки по ходу разработки
  • Issues – список задач
  • Projects – управление проектом
  • А также есть документация на программу сформированная Doxygen

Основные каталоги программы

  • useful – полезные скрипты для настройки системы
  • qt – каталог для исходников Qt
  • sdx_wsp/check_transfer — рабочий каталог SDAccel

В данном проекте исходные тексты для Qt и SDAссel одни и те же, хотя и находятся в разных каталогах. Однако предполагается, что на Qt будут разрабатываться намного более сложные программы.

Два режима вывода

SDAccel — проверяем передачу данных - 4
(Нажмите на картинку для увеличения)

На рисунке показан внешний вид терминала во время работы программы. Обратите внимание на таблицу. Это таблица с выводом текущего состояния теста. Во время работы очень интересно узнать, а что собственно происходит. Тем более что предусмотрен режим без ограничения по времени. Таблица очень помогает. К сожалению, есть проблемы. SDAccel сделан на основе Eclipse. Мне не удалось научиться запускать программу из среды во внешнем терминале. А во встроенном терминале таблица не работает. Пришлось сделать режим запуска без таблицы. Кстати система Nsight Eclipse Edition для программирования устройств NVIDIA тоже не умеет запускать программы во внешнем терминале. Или может я что-то не знаю?

Мегабайты или Мебибайты ?

Я отношусь к тем людям, которые точно знают что 1 килобайт это 1024 байта (а также предполагают что в 1 километре 1024 метра). Но это уже незаконно. Что бы избежать путаницы программа может измерять в обоих режимах и текущий режим отображается в логе.

Давайте рассмотрим некоторые фрагменты кода программы.

Kernel gen_cnt

Код ядра gen_cnt() очень простой. Функция заполняет массив заданного размера тестовым блоком данных.

__kernel
__attribute__ ((reqd_work_group_size(1,1,1)))
void gen_cnt(
                __global ulong8    *pOut,
                __global ulong8    *pStatus,
                const   uint  size
              )
{
    uint    blockWr;
    ulong8  temp1;
    ulong8  checkStatus;
    ulong8  addConst;
    checkStatus = pStatus[0];
    temp1       = pStatus[1];
    addConst    = pStatus[2];
    blockWr     = checkStatus.s0 >> 32;
  __attribute__((xcl_pipeline_loop))
  for ( int ii=0; ii<size; ii++)
    {
     pOut[ii] = temp1;
        temp1.s0 +=addConst.s0;
        temp1.s1 +=addConst.s1;
        temp1.s2 +=addConst.s2;
        temp1.s3 +=addConst.s3;
        temp1.s4 +=addConst.s4;
        temp1.s5 +=addConst.s5;
        temp1.s6 +=addConst.s6;
        temp1.s7 +=addConst.s7;
    }
    blockWr++;
    checkStatus.s0 = ((ulong)blockWr)<<32     | 0xAA56;
    pStatus[0] = checkStatus;
    pStatus[1] = temp1;
}

Переменная temp1 имеет тип ulong8. Это стандартный тип OpenCL который представляет собой вектор из восьми 64-х разрядных чисел. Обращаться к элементам вектора можно по именам s0..s7 или так temp1.s[ii]. Это достаточно удобно. Ширина вектора 512 бит. Это соответствует ширине внутренней шины для контроллера SODIMM. Одним из элементов оптимизации как раз и заключается в обмене с памятью только 512 битными данными. По указателю pStatus находится блок со статусной информацией, из него считывается текущее значение и константы. Для каждого 64-х битного поля используется своя константа. Это позволяет сделать не только простой счётчик но и что то более сложное. Хотя пока что программа делает только простой счётчик. В конце функции производится запись текущего значения данных и число заполненных блоков.

check_cnt_m2a и check_read_input

Для реализации проверки я написал две функции, одна check_read_input — читает данные из динамической памяти и записывает их в pipe. Вторая – check_cnt_m2a – читает данные из pipe и проверяет их. Наверное в данном случае разделение на два kernel и их связь через pipe является избыточным. Но мне было интересно проверить эту технологию.

Код здесь

Структура программы HOST

Программа основана применении виртуальных классов TF_Test и TF_TestThread; На основе этих классов разработаны два класса тестирования

  • TF_CheckTransferOut — проверка передачи от устройства в компьютер
  • TF_CheckTransferIn – проверка передачи из компьютера в устройство

Базовый класс TF_Test содержит функции:

Название Назначение
Prepare() Подготовка
Start() Запуск
Stop() Останов
StepTable() Шаг отображения таблицы
isComplete() Работа теста завершена
GetResult() Вывод результата

Функция main() создаёт по одному экземпляру каждого класса и начинает выполнение.
Каждый класс тестирования создаёт свой поток выполнения, в котором происходит обмен с модулем. Функция main вызывает Prepare() для каждого класса. Внутри этой функции как раз и создаётся поток, выделяется память и проводится вся подготовка. После того как оба класса готовы вызывается старт, что приводит к запуску главного цикла тестирования. При нажатии Ctrl-C или при окончании заданного времени тестирования вызывается Stop(). Классы останавливают работу и с помощью функции isComplete() информируют об этом main(). После остановки вызывается GetResult() для получения результата. В процессе выполнения теста функция main() вызывает StepTable каждые 100 мс для обновления таблицы. Это позволяет обновлять статусную информацию без вмешательства в обмен данными.
Такой подход оказался очень удобным для построения тестовых программ. Здесь все тесты строятся по одинаковому шаблону. В результате их можно запускать параллельно, а можно и поодиночке. В данной программе легко организуется режим как одиночного запуска одного из тестов, так и одновременный запуск.

Режимы выполнения OpenCL программы

Система SDAccel предоставляет три режима выполнения программы:

  • Emulation-CPU — всё выполняется на HOST процессоре
  • Emulation-HW — функции OpenCL выполняются на симуляторе Vivado
  • System — работа на реальной аппаратуре.

Более подробно — в предыдущей статье.

Интересно сравнить скорости работы в трёх средах. Сравнение очень показательное:

Emulation-CPU Emulation-HW System
200 МБ/с 0.1 МБ/с 2000 МБ/с

Числа я округлил что бы лучше видеть порядок. Собственно разница в скорости между Emulation-CPU и Emulation-HW показывает что в разработке прошивок ПЛИС надо переходить на C/C++ или что-то аналогичное. Выигрыш на четыре порядка это очень много, это перекрывает все недостатки С++. При этом надо отметить, что разработка на VHDL/Verilog не исчезнет, и эти языки скорее всего придётся применять для достижения предельных характеристик. Очень перспективным выглядит возможность создания OpenCL kernel на VHDL/Verilog, это позволит сочетать высокую скорость разработки и предельные характеристики ПЛИС. Но это уже тема отдельного исследования и отдельной статьи.

Результат трассировки

SDAccel — проверяем передачу данных - 5

Вот что получилось. Обратите внимание на количество DSP для gen_cnt. Для реализации восьми 64-х разрядных счётчиков потребовалось 128 DSP блоков. Это по 16 блоков на счётчик. Скорее всего это результат работы оптимизатора по раскрытию цикла.

Различия в методах оптимизации для FPGA и GPU

Для достижения предельных результатов должны применяться разные методы оптимизации. GPU имеет фиксированную структуру. Если условно говоря один процессорный элемент GPU может выполнять одну операцию, то что бы параллельно выполнить 100 операций надо задействовать 100 процессорных элементов. А вот в ПЛИС это не является единственным вариантом. Да, мы можем написать один kernel и разместить несколько экземпляров в ПЛИС. Но это приводит к большим накладным расходам. Xilinx рекомендует использовать не более 16 kernel, а точнее портов памяти. Зато внутри одного элемента нет ограничений на распараллеливание. Собственно пример gen_cnt это и показывает. Там сразу в коде записаны восемь 64-х разрядных сумматоров. Кроме того сработал оптимизатор и развернул цикл. Для GPU этот пример надо написать по другому, например сделать один kernel для получения 64-х разрядного отсчёта и запустить сразу восемь экземпляров.

Что может показать Emulation-HW

Этот режим может показать что происходит на шинах доступа к памяти. На картинке показан процесс чтения данных из памяти функцией check_read_input().

SDAccel — проверяем передачу данных - 6
(Нажмите что бы увеличить)

Во первых здесь видно с какой большой задержкой приходят данные. Задержка от первого запроса до появления первых данных 512 нс. Во вторых видно что чтение идёт блоками по 16 слов (размером 512 бит). При разработке на VHDL я бы использовал больший размер блока. Но видимо контроллер умеет объединять блоки и это не приводит к замедлению. В третьих видно что есть разрывы в получении данных. Они тоже объяснимы. Частота работы OpenCL 250 МГц, частота шины памяти для SODIMM DDR3-1600 составляет 200 МГц. Разрывы точно соответствуют переходу от шины 200 МГц к шине 250 МГц.

Результаты

Результаты интересные, но я ожидал достичь более высоких скоростей.

Одиночные тесты

Компьютер Ввод [MiB/s] Вывод [MiB/s]
Intel Core-i5, PCIe v2.0 x8 2048 1837
Intel Core-i7, PCIe v3.0 x8 2889 2953

Двунаправленный тест

Компьютер Ввод [MiB/s] Вывод [MiB/s]
Intel Core-i5, PCIe v2.0 x8 1609 1307
Intel Core-i7, PCIe v3.0 x8 2048 2057

Для сравнения, на нашем модуле с аналогичной ПЛИС рекордная скорость ввода составила 5500 MiB/s, хотя по ряду причин пришлось её снизить до 5000. Так что возможности для увеличения скорости обмена есть.

Что дальше

Работа будет продолжаться.

  • Исследование возможностей SDAccel 2017.2
  • Реализация узла свёртки на основе библиотеки FPFFTK от Александра Капитанова ( capitanov )
  • Разработка собственных DSA пакетов, в том числе с поддержкой 10G Ethernet
  • И главное — разработка собственного модуля, название уже есть — DSP135P

P.S. Хочу выразить благодарность Владимиру Каракозову за помощь в разработке шаблона программы тестирования.

Автор: dsmv2014

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js