Загоняем Альпаку на Эльбрус (Часть 2. Оптимизации)

—

от автора

Что нового

В прошлой статье я писал о запуске Alpaca на Эльбрусе. На момент написания той статьи оптимизации под Эльбрус не проводились. Однако теперь, благодаря стараниям @troosh можем протестировать Эльбрус уже с оптимизациями. ВНИМАНИЕ! Проект llama.cpp обновляется очень часто, и многое меняется. На данный момент это самая актуальная версия llama.cpp под Эльбрус. Эльбрус 16С поддерживает аппаратную виртуализацию, мне как раз выдали доступ к «виртуалке». Благодарность @shigorin

И сразу тесты

В прошлой статье я уже описал что делал. Поэтому тут я сразу начну с тестов.

Тест проводился следующей командой. Данная команда запрашивает рандомную шутку и подсчитывает время выполнения запроса.

for a in {1..8};do printf "%s;" $a;./main -t $a -m ./models/ggml-alpaca-7b-q4.bin -s 42 -p "Random joke:" -n 32 2>&1 |grep "llama_print_timings:       eval time" | cut -d "(" -f 2 | grep -o -e "[0-9\.]*" ;done

Ryzen 7 5800H

Эльбрус-16С

Эльбрус-8СВ

707,81

903,02

1094,07

370,47

472,6

571,45

258,1

330,39

398,84

199,1

256,79

310,96

163,97

213,01

260,76

140,87

184,04

226,59

127,37

163,37

207,54

126,05

148,54

193,7

График скорости (меньше - лучше)

График скорости (меньше — лучше)

Тесты с оптимизацией и без.

Потоки

Ryzen 7 5800H

Эльбрус-16С (Оптимизировано)

Эльбрус-8СВ (Оптимизировано)

Эльбрус-8СВ 1550MHz

Эльбрус-16С 2000MHz

1

707,81

903,02

1094,07

2542,64

2389,05

2

370,47

472,6

571,45

1279,05

1225,16

3

258,1

330,39

398,84

915,73

823,2

4

199,1

256,79

310,96

710,14

638,5

5

163,97

213,01

260,76

575,53

513,72

6

140,87

184,04

226,59

487,12

438,66

7

127,37

163,37

207,54

419,23

380,11

8

126,05

148,54

193,7

375,21

342,84

График скорости (Меньше - лучше)

График скорости (Меньше — лучше)

Бенчмарк

Тесты были сделаны при помощи benchmark-q4_0-matmult. Собиралось через команду: make benchmark

1 поток

FLOPS_per_u_Second

Ryzen 7 5800H

3200МГц

40205.95

Эльбрус-16С

2000МГц

22183.21

Эльбрус-8СВ

1550МГц

17452.88

И многопоточный тест

8 потоков

FLOPS_per_u_Second

Ryzen 7 5800H

3200МГц

255353.06

Эльбрус-16С

2000МГц

161953.14

Эльбрус-8СВ

1550МГц

129111.80

До оптимизации результаты были следующими:

1 поток

FLOPS_per_u_Second

Ryzen 7 5800H

3200МГц

40205.95

Эльбрус 16С

2000МГц

10761.69

Эльбрус 8СВ

1550МГц

7202.17

8 потоков

FLOPS_per_u_Second

Ryzen 7 5800H

3200МГц

255353.06

Эльбрус 16С

2000МГц

82397.41

Эльбрус 8СВ

1550МГц

55424.05

Что было сделано

Был оптимизирован код ggml.c под Эльбрус и конкретно под модель Q4_0. Немного пояснений от @troosh. В данной статье тесты проводились на модели Q4_0. При оптимизации использовались интринсики Эльбруса.

Попытался оптимизировать работу в формате Q4_0 для e2k процессоров с 5-й и выше версией системы команд (для тех которые с 128-ми битными регистрами), выкладываю сюда: https://github.com/E2Kports/llama.cpp

Именно в этом формате проверят умножение матриц бенчмарк. А вот используемая в статье модель сконвертирована под Q4_1, на ней ускорения ждать не стоит. Нужно брать модели в Q4_0, либо подождать пока доработаю и этот формат.

А вообще, проект llama.cpp ну очень уж быстро меняется — пришлось пару раз под новые правки подстраиваться…

Ну и небольшой пример кода

 #if defined(__e2k__) && __iset__ >= 5 static inline __v2di __attribute__((__always_inline__)) e2k_dot_4_0_8_0_quants(__v2di bx, __v2di by0, __v2di by1) {     const __v2di lowMask = __builtin_e2k_qppackdl(0x0f0f0f0f0f0f0f0fLL,                                                   0x0f0f0f0f0f0f0f0fLL);     const __v2di bais = __builtin_e2k_qppackdl(0x0808080808080808LL,                                                0x0808080808080808LL);     const __v2di ones = __builtin_e2k_qppackdl(0x0001000100010001LL,                                                0x0001000100010001LL);          // Unpack nibbles into individual bytes         __v2di bx0 = __builtin_e2k_qpand( bx,  lowMask ); // {HLhl} -> {oLol}         __v2di bx1 = __builtin_e2k_qpsrlh( bx, 4 );       // {HLhl} -> {oHLh}                bx1 = __builtin_e2k_qpand( bx1, lowMask ); //        -> {oHoh}         // The output vectors contains 32 bytes, each one in [ 0 .. 15 ] interval          // Reorder bytes in "y" block to order in bx0,bx1         __v2di lo = __builtin_e2k_qppermb(by1, by0,                       __builtin_e2k_qppackdl(0x1e1c1a1816141210LL,                                              0x0e0c0a0806040200LL));         __v2di hi = __builtin_e2k_qppermb(by1, by0,                       __builtin_e2k_qppackdl(0x1f1d1b1917151311LL,                                              0x0f0d0b0907050301LL)); #if __iset__ >= 7         // Move each one in [ -8 .. +7 ] interval:         bx0 = __builtin_e2k_qpsubb(bx0, bais);         bx1 = __builtin_e2k_qpsubb(bx1, bais);          __v2di xy_int32 = __builtin_e2k_qpidotsbwss(bx0, lo, __builtin_e2k_qppackdl(0, 0));                xy_int32 = __builtin_e2k_qpidotsbwss(bx1, hi, xy_int32); #else         // Get absolute values of "x" vectors:         __v2di ax0 = __builtin_e2k_qppermb(bx0 /* not used */,                         __builtin_e2k_qppackdl(0x0706050403020100LL,                                                0x0102030405060708LL), bx0);         __v2di ax1 = __builtin_e2k_qppermb(bx1 /* not used */,                         __builtin_e2k_qppackdl(0x0706050403020100LL,                                                0x0102030405060708LL), bx1);          // Move each one in [ -8 .. +7 ] interval:         bx0 = __builtin_e2k_qpsubb(bx0, bais);         bx1 = __builtin_e2k_qpsubb(bx1, bais);          // Sign the values of the y vectors         __v2di sy0 = __builtin_e2k_qpsignb(lo, bx0);         __v2di sy1 = __builtin_e2k_qpsignb(hi, bx1);          // Perform multiplication and create 16-bit values         __v2di dot0 = __builtin_e2k_qpmaddubsh(sy0, ax0);         __v2di dot1 = __builtin_e2k_qpmaddubsh(sy1, ax1);          // Reduce to 8 int16_t (overflow not possible: 8 bit * 4 bit => 12 bit)         __v2di dot = __builtin_e2k_qpaddh(dot0, dot1);          // Reduce to 4 int32_t by integer horizontal sums         __v2di xy_int32 = __builtin_e2k_qpmaddh(ones, dot); #endif          // Convert vector of 4 int32_t to 4 floats         return __builtin_e2k_qpistofs(xy_int32); }

Из интересного, в данном коде предусматривается работа с Эльбрус V7.

Заключение

На данный момент это лучшие из возможных результаты, в дальнейшем можно сделать оптимизации для модели Q4_1.

Благодаря оптимизациям под архитектуру VLIW можно добиться довольно хороших результатов. С учетом того что Ryzen 7 5800H произведен по техпроцессу 7нм и имеет частоту 3200МГц с ускорением до 4400МГц. А Эльбрус 16с произведен по 16нм техпроцессу и имеет 2000МГц (У 8СВ вообще 1550МГц) результаты вполне неплохие.


ссылка на оригинал статьи https://habr.com/ru/articles/732508/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *