Умножение матриц, серия 5: вычисления на GPU (2)

Почему переделываем тесты?

Предыдущая моя статья на эту тему была написана в феврале 2007 года, сразу после выхода первой публичной бета-версии CUDA Toolkit/CUDA SDK. Представители NVidia предупреждали, что в бета-версии производительность не является оптимальной, к релизу она будет улучшена.

За прошедшие полгода, пока я занимался совсем другими вещами, были выпущены релизы:

  • NVidia CUDA: SDK и библиотеки CUBLAS/CUFFT v1.0;
  • NVidia CUDA Display Driver 162.xx (драйвер, собственно, транслирует псевдокод в реальные программы GPU);
  • RapidMind Platform версий 2.0.0, а затем и 2.0.1.

Интересно посмотреть, стала ли производительность лучше.

Приборы и материалы: NVidia CUDA и прочие

Как и в предыдущих статьях, рассматривать будем умножение матриц, либо библиотечный вызов SGEMM. Задача очень простая в первом приближении, а во втором — вылезают разнообразные особенности. Достигнуть высокой производительности на современной аппаратуре, где память много медленнее вычислительного процессора — непросто.

Рассматривались три доступных для NVidia G80 умножителя матриц:

  1. Реализация SGEMM от RapidMind, RapidMind Platform 2.0.1.
  2. SGEMM в составе библиотеки CUBLAS, версия 1.0.
  3. Тестовый пример от NVidia, который очень подробно разобран в документации. CUDA SDK версия 1.0.
Сравнивалось все с результатами, полученными весной 2007 г. на доступных мне компьютерах общего назначения (Dual Opteron 275 и Dual Xeon 5140), CPU-расчеты делались на Goto BLAS. Повторное (после весеннего) тестирование CPU не производилось.

Все прочие параметры рассмотрены в прошлой статье, за полгода они изменились слабо, разве только NVidia GeForce 8800GTX подешевела с $600-750 до $500.

Существенные изменения

NVidia CUDA

Главное видимое изменение: практически все вызовы CUDA API, включая вызов собственно computation kernel, стали асинхронными (исключения описаны в разделе 4.5.1.5 документации), отчего старые версии тестовых программ показывают резко выросшее быстродействие. После явно добавленной синхронизации все стало на свои места.

RapidMind Platform

RapidMind Platform версий 1.9 и 2.0beta была ограничена размером матриц 1020x1020. В версии 2.0.1 этого ограничения уже нет.

Результаты тестов

Хочется напомнить, что вычисления на видеокарте проходят в три этапа:
  • Загрузка данных в видеопамять
  • Запуск вычислительного модуля, исполняемого GPU
  • Выгрузка данных в основную память компьютера
При наличии асинхронного режима (в CUDA он уже есть), загрузка-выгрузка могут осуществляться одновременно с расчетами (если области видеопамяти не пересекаются), поэтому при оценке быстродействия интересны два времени: чистое время на расчет и полное время с учетом ввода-вывода данных.

Быстродействие на перемножении матриц (MFLOP/s)
Описание тестаразмер задачи
1024 2048 4096
RapidMind 2.0.1
Только вычисления
ускорение в сравнении с 2.0beta (разы)
117 488
1.07
115 234
*
114 575
*
Вычисления + ввод-вывод
ускорение в сравнении с 2.0beta (разы)
48 859
1.46
69 536
*
82 366
*
* — RapidMind Platform 2.0b была ограничена размером задачи 1020x1020
NVidia CUBLAS 1.0
Только вычисления
ускорение в сравнении с CUDA 0.8beta (разы)
120 555
1.23
122 432
1.23
121 039
1.23
Вычисления + ввод-вывод
ускорение в сравнении с CUDA 0.8beta (разы)
61 200
1.04
85 290
1.16
100 844
1.21
NVidia CUDA: пример из SDK 1.0
Только вычисления
ускорение в сравнении с CUDA 0.8beta (разы)
48 009
1.03
48 175
1.03
47 893
1.03
Вычисления + ввод-вывод
ускорение в сравнении с CUDA 0.8beta (разы)
30 660
1.22
41 648
1.16
45 173
1.11
Вычисления на CPU
2xXeon 5140, Goto BLAS (4 потока, single precision) 26 512 45 935 47 556

Обсуждение результатов

RapidMind

Предыдущая версия RapidMind не позволяла работать с большими размерами задач, поэтому полноценного сравнения не получается. По тем двум точкам, которые у нас есть, мы видим что:
  • Вычислительная часть не стала заметно быстрее. Более того, не исключено, что рост скорости вычислений обусловлен сменой драйвера NVidia (трансляцией кода шейдеров из P-code DX9 в команды конкретного оборудование занимается драйвер).
  • Ввод-вывод данных в/из GPU стал сильно быстрее, рост производительности определяется именно скоростью I/O. Естественно, скорость I/O могла подрасти не только за счет улучшений в RapidMind, но и за счет улучшений драйвера видеокарты.

NVidia CUDA: CUBLAS

Во всех тестах, где измеряется только скорость вычислений, производительность выросла в 1.23 раза (одинаково). Другими словами, вычислительная часть CUBLAS (cuSgemm) оптимизирована, как и обещали в NVidia.

Роста скорости I/O практически нет, поэтому рост общей скорости выполнения задачи заметен только на больших размерах матриц, где вычислительная стадия является определяющей (время на вычисления зависит от куба размера матриц, а объем ввода-вывода — от квадрата).

NVidia CUDA: пример из SDK

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

Вместе с тем, ввод-вывод в CUDA SDK очевидно ускорился и очень сильно, поэтому задача малого размера, где вклад I/O в общее быстродействие наибольший, серьезно ускорилась, а с ростом размера матриц прирост скорости для задачи в-целом уменьшается..

Выводы

На сегодняшний день самые быстрые CPU от Intel способны выдать вычислительную производительность до 5GFLOP/s на один гигагерц частоты процессорного ядра для вычислений с одинарной точностью (теоретически - до 8GFLOP/s, но теоретическое быстродействие не достигается на реальных задачах). Для десктопных процессорв стоимость гигафлопса получается порядка $10-11, для серверных: $21-27 (цены московские на октябрь 2007г.). На практике, установка двух 4-ядерных процессоров в один сервер возможна, но узким местом такой системы станет пропускная способность памяти, даже при 4-х работающих ядрах этот эффект заметен.

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

Гигафлопс на базе NVidia GeForce 8800 стоит порядка $5, если задача запрограммирована хоть сколько-нибудь оптимально. Другими словами, если нужно много считать и можно обойтись библиотеками BLAS/FFT и одинарная точность устраивает, то выбор на сегодня очевиден.

Если же хватает времени и сил на ручное программирование под GPU, то выигрыш может быть еще более значительным. Но об этом — позже.

Comments

Чего я не понимаю, так это почему задержки в CUBLAS по десятку микросекунд на любой вызов. Это же порядка 10 000 циклов.

У меня на эту тему больше вопросов, чем ответов. Вопросы могу перечислить
1) когда происходит компиляция P-кода в команды карты ? При старте программы (и тогда мы все kernels из cublas будем компилировать) ? Или при очередном вызове cu* ?
2) когда происходит загрузка kernel ? Вроде бы у 8800 под программы аж 8 мегабайт (пишу по памяти), при старте грузится весь cublas ?
3) сигналинг об окончании работы kernel - по прерываниям или по поллу ? А если по поллу, то с какой частотой ?
4) все вызовы kernel - через драйвер ? Нужно сменить контекст, cохранить/переключить стек и все такое ?

Собственно, 10 usec задержки - это 100000 крупных операций в секунду, если это по прерываниям - это очень много. Да и просто для context switch - нормально.

Вообще-то для интеловской х86 при правильно посчитанной пиковой, совершенно непонятно с какого потолка взята оценка реальной эффективности. 5Гфлопс из 8-ми это примерно 60% эффективности, а на самом деле она повыше будет. (до 80%).
Диджемм и сиджемм имеют хорошую локальность, этим все и объясняется. В случае с 4-хкоровой машинкой просад будет не столь существенным, как на том же ффт.
Но, пока CUDA будет только в single precision, переспективы ее применения в реальном HPC крайне призрачны. Пока только любители будут восторгаться дешевыми флопсами.

Реальная эффективность получена в ощущениях на Goto BLAS. В предыдущей серии:

http://blog.lexa.ru/2007/01/27/umnozhenie_matric_serija_2_woodcrest_prot...

В том, что касается Single Precision, не все так плохо. Во-первых, двойную точность обещали уже в этом году.
Во-вторых, это же тренд и на обычных машинах - делаем приближенное решение в single (это вдвое! быстрее), потом уточняем в double. Тот же Донгарра об этом любит поговорить.

Я затрудняюсь переварить те таблички, на которые вы ссылаетесь. Все пишут в флопсах-процентах, а у вас как-то туманно все, лень пересчитывать.
В качестве своего довода я представлю другую табличку:
http://icl.cs.utk.edu/hpcc/hpcc_results.cgi
-там есть интересующий вас DGEMM на HPC системах. Single DGEMM - это 1 поток на 1 узле, Star DGEMM - N потоков, зависит от конфигурации, можете посмотреть как проседает.

А по поводу Донгарры - интересно почитать, есть ссылка?

По вашей табличке. Берем те, где оптероны. Для оптерона мы ожидаем 4 FLOPS-а на такт (MADD, две штуки в параллель для double). Т.е. для 2.6 GHz вправе ожидать чуть больше 10 GFLOP/s. А имеем чуть меньше пяти. Для Xeon 3.4 GHz (т.е. это старый и уверенно можно сказать, что SSE2) - примерно так же.

Линк на Донгарру, например, вот:
http://doi.ieeecomputersociety.org/10.1109/ICPP.2006.68

(я в голове держу презентацию на Supercomputing-2006, но за две минуты не сумел найти).

>>Для Xeon 3.4 GHz (т.е. это старый и уверенно можно сказать, что SSE2) - примерно так же.

Это совсем другие корнеплоды... К сожалению там сейчас вовсю идет подготовка к SC'07 и поэтому поубирали все сколь-нибудь новые сабмишны.
Для вудкреста пик 12Гфлопс, реально бывает проскакивает 11, в среднем 10.5Гфлопс в однопоточном режиме и 9.5 в многопточном.

После 12 ноября все вернется на свои места - можно будет проверить мое высказывание.

Это 11 гигафлопсов на ядро или на процессор ?

На ядро конечно. MKL 9.1.23 или старше.

Здравствуйте. Не могу найти код SGEMM для RapidMind.
Он в девелоперской части сайта? К ней сложно получить доступ?

Он в девелоперской части
https://developer.rapidmind.net/sample-code/matrix-multiplication-samples/

Там *была* простая регистрация для всех, что сейчас - не знаю

очень интересный пост. добавлю лишь, что C2D в 64-битном режиме будет умножать матрицы чуть быстрее, т.к. sse регистров становится в 2 раза больше, а именно их кол-во ограничивает теоретические 8 sFLOP/c. чуть подрасти должен и A64, но меньше.

Чуть - это именно повышение эффективности с 5.21 операций на такт (которые я намерял полгода назад) чуть ближе к теоретическим восьми ?

Это не поможет, NVidia успела выпустить 8800GT, которая несколько хуже по
bandwidth, практически такая же по гигафлопсам (чуть меньше горшков, большая
частота) и вдвое дешевле.

я и не спорю, для умножения плотных матриц GPU эффективнее.
Conroe в x64 возможно дойдёт до 6-6.5 sFLOP/c, Penryn за счёт оптимизированного Shuffle до 7.
к сожалению, перемножение больших квадратных матриц очень тупая задача и используется, в основном, лишь в бенчмарках. в реальных приложениях, если и есть перемножение матриц, то относительно маленьких (8x8, 16x16) или же матрицы очень неквадратные.

Простейшая задача кластеризации небольшого количества небольших текстов - сведется именно к попарному перемножению векторов (результат будет симметричным т.к. по сути умножаем матрицу на себя же транспонированную).

Понятно, что чем больше текстов, тем оно разреженнее, но до некоторого предела выгоднее будет перемножать как dense.

На самом деле, как только оно стало всерьез разреженным, нужно вырезать плотные куски и перемножать их.

Мне в прошлой жизни приходилось равновесие химическое считать, там матрицы размером от полусотни до нескольких сотен (зато этих систем были сотни тысяч).

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

Да, одна. Хотя надо, конечно, какую-нибудь GF6xxx поставить на мониторный вывод.

5 секунд лимит, для тех задач, которые мне интересны (а это около-реалтайма т.е. время реакции - десятки миллисекунд) - вполне достаточно.
Это фича не CUDA, а виндов, имеющих watchdog на видеодрайвер.