<bgdev />free

Вход Регистрация

neon
0

0 1 2 3

#127987 (ツ) BIGBUGEX
Създадено на 21.11.2024 , видяно: 657 пъти.
johnfound

Никога не съм обичал играта "Да напишем такъв код на C, че да получим нужният ни машинен код".

Много по-лесно е да си го напиша директно на асемблер. rofl

Но искрено се забавлявам да гледам такова шоу. Нещо като старите комедии с Лоурел и Харди.

Ма ти не знаеш ли? Вече е срамно да се пише на асемблер. Гледат с лошо око, колегите.

#127989 (ツ) johnfound
Създадено на 21.11.2024 , видяно: 655 пъти.
BIGBUGEX

Ма ти не знаеш ли? Вече е срамно да се пише на асемблер. Гледат с лошо око, колегите.

Хм, моите колеги, като че ли не ме гледат с лошо око. Добре, че не са програмисти.

#128004 (ツ) Rabin
Създадено на 21.11.2024 , видяно: 630 пъти.
johnfound

Никога не съм обичал играта "Да напишем такъв код на C, че да получим нужният ни машинен код".

Много по-лесно е да си го напиша директно на асемблер. rofl

Но искрено се забавлявам да гледам такова шоу. Нещо като старите комедии с Лоурел и Харди.

И като видиш как С пише по-оптимално от тебе...

#128005 (ツ) Baj_boeb
Създадено на 21.11.2024 , видяно: 621 пъти.

хубаво е все пак да имаш job security, затова колкото повече асемблер, толкова по-добре

#128006 (ツ) Rabin
Последно редактирано на 21.11.2024 от Rabin, видяно: 618 пъти.
Baj_boeb

хубаво е все пак да имаш job security, затова колкото повече асемблер, толкова по-добре

Кой ви учи на тез глупости ве, банкирането ми е мацано на .нет, кажи неко асемблерска банка, че по-сигурна rofl :-P🤪 🤓 🤑 🙃 ...

Оп, сори, после разбрах ко искаше да кажеш. Дремя на едни лекции по многозадачност. Съгласен съм.

#128346 (ツ) |
Създадено на 26.11.2024 , видяно: 523 пъти.
BIGBUGEX

Пробвах и clang. Определено се справя по-добре от gcc.

eqsub iters <1767> 0.022 ns/b 45.278 GB/s

spcalc iters <801> 0.049 ns/b 20.559 GB/s

Edit: Но не и за моя вариант с тестване за нула.

Абе BIGBUGEX, какво ще стане ако края на вектора е в невалидна памет? Ще гръмне ли програмата или AVX2 знае как да го оправи? Защото не го виждам в кода ти.

Ето моята SVE имплементация, базирана на оптимизирана версия на strchr. Тества за нула.


       setffr                          /* initialize FFR */
        ptrue   p1.b                    /* all ones; loop invariant */
        mov     x1, xzr
        dup     z1.b, 'p'               /* replicate byte across vector */
        dup     z2.b, 's'               /* replicate byte across vector */

        .p2align 4
0:      ldff1b  z0.b, p1/z, [x0, xzr]
        rdffrs  p0.b, p1/z
        b.nlast 1f

        /* First fault did not fail: the whole vector is valid.
           Avoid depending on the contents of FFR beyond the branch.  */
        incb    x0                              /* speculate increment */
        cmpeq   p3.b, p1/z, z0.b, 0             /* search for 0 */
        b.ne    1f

        /* no end string */
        brka    p4.b, p1/z, p3.b                /* find first 0 */
        cmpeq   p2.b, p4/z, z0.b, z1.b          /* search for 'p' */
        decp    x1, p2.b                        /* count the predicates and subtract them from x1 */
        cmpeq   p3.b, p4/z, z0.b, z2.b          /* search for 's' */
        incp    x1, p3.b                        /* count the predicates and add them to x1 */
        b       0b

1:      /* end string or first fault */
        brka    p4.b, p0/z, p3.b                /* find first 0 */
        cmpeq   p2.b, p4/z, z0.b, z1.b          /* search for 'p' */
        decp    x1, p2.b                        /* count the predicates and subtract them from x1 */
        cmpeq   p3.b, p4/z, z0.b, z2.b          /* search for 's' */
        incp    x1, p3.b                        /* count the predicates and add them to x1 */
        mov     x0, x1
        ret

Скоростта на Graviton4 e 1.86 пъти по-бавна от eqsub.

Ето и версията ми за SME. Това е реално Streaming SVE, но имплементацията е различна защото SSVE няма FFR и incp/decp са много бавни (мързи ме да обяснявам, но ако някой е любопитен може да го направя).


        smstart
        dup     z1.b, 'p'                       /* replicate byte across vector */
        dup     z2.b, 's'                       /* replicate byte across vector */
        dup     z3.b, 0                         /* all 0s for sel */
        dup     z4.b, 1                         /* all 1s for sel */
        dup     z5.b, -1                        /* all -1s for sel */
        dup     z8.s, 0                         /* return value(s) */
        ptrue   p1.b                            /* all ones; loop invariant */

        .p2align 4
0:      ld1b    z0.b, p1/z, [x0]                /* read bytes in vector */

        incb    x0                              /* speculate increment */
        dup     z9.h, 0                         /* 32-bit count for the iteration */
        cmpeq   p3.b, p1/z, z0.b, 0             /* search for 0 */
        b.ne    1f

        /* no end of string */
        cmpeq   p2.b, p1/z, z0.b, z1.b          /* search for 'p' */
        sel     z10.b, p2, z5.b, z3.b           /* for each match, select -1s from z5 */
        cmpeq   p3.b, p1/z, z0.b, z2.b          /* search for 's' */
        sel     z10.b, p3, z4.b, z10.b          /* for each match, select 1s from z4 */
        sadalp  z9.h, p1/m, z10.b               /* widen to 16 bit by adding pairwise elements*/
        sadalp  z8.s, p1/m, z9.h                /* add to the total count, widening to 32 bit */
        b       0b

        /* end of string */
1:      brka    p4.b, p1/z, p3.b                /* find first 0 */
        cmpeq   p2.b, p4/z, z0.b, z1.b          /* search for 'p' */
        sel     z10.b, p2, z5.b, z3.b           /* for each match, select -1s from z5 */
        cmpeq   p3.b, p4/z, z0.b, z2.b          /* search for 's' */
        sel     z10.b, p3, z4.b, z10.b          /* for each match, select 1s from z4 */
        sadalp  z9.h, p1/m, z10.b               /* widen to 16 bit by adding pairwise elements*/
        sadalp  z8.s, p1/m, z9.h                /* add to the total count, widening to 32 bit */

        saddv   d0, p1, z8.s                    /* reduce counts to scalar register */
        fmov    x0, d0

        smstop
        ret

Скоростта е десетки пъти по-бавна от eqsub на Apple M4. Явно SME не е много подходящ за такива дребни алгоритми.

#128347 (ツ) BIGBUGEX
Последно редактирано на 26.11.2024 от BIGBUGEX, видяно: 509 пъти.
|

Абе BIGBUGEX, какво ще стане ако края на вектора е в невалидна памет? Ще гръмне ли програмата или AVX2 знае как да го оправи? Защото не го виждам в кода ти.

Няма да гръмне. Подравнявам старта на 32 байта и със съответните битове за валидност инвалидирам операциите които са пред масива. От там на сетне обработвам адреси кратни на 32 байта. Ако изскочи '\0' в текущия пакет от данни се излиза от цикъла като се инвалидират бройките след него.

|

Ето моята SVE имплементация, базирана на оптимизирана версия на strchr. Тества за нула.


       setffr                          /* initialize FFR */
        ptrue   p1.b                    /* all ones; loop invariant */
        mov     x1, xzr
        dup     z1.b, 'p'               /* replicate byte across vector */
        dup     z2.b, 's'               /* replicate byte across vector */

        .p2align 4
0:      ldff1b  z0.b, p1/z, [x0, xzr]
        rdffrs  p0.b, p1/z
        b.nlast 1f

        /* First fault did not fail: the whole vector is valid.
           Avoid depending on the contents of FFR beyond the branch.  */
        incb    x0                              /* speculate increment */
        cmpeq   p3.b, p1/z, z0.b, 0             /* search for 0 */
        b.ne    1f

        /* no end string */
        brka    p4.b, p1/z, p3.b                /* find first 0 */
        cmpeq   p2.b, p4/z, z0.b, z1.b          /* search for 'p' */
        decp    x1, p2.b                        /* count the predicates and subtract them from x1 */
        cmpeq   p3.b, p4/z, z0.b, z2.b          /* search for 's' */
        incp    x1, p3.b                        /* count the predicates and add them to x1 */
        b       0b

1:      /* end string or first fault */
        brka    p4.b, p0/z, p3.b                /* find first 0 */
        cmpeq   p2.b, p4/z, z0.b, z1.b          /* search for 'p' */
        decp    x1, p2.b                        /* count the predicates and subtract them from x1 */
        cmpeq   p3.b, p4/z, z0.b, z2.b          /* search for 's' */
        incp    x1, p3.b                        /* count the predicates and add them to x1 */
        mov     x0, x1
        ret

Скоростта на Graviton4 e 1.86 пъти по-бавна от eqsub.

Много добре е това. Ще го разгледам кода въпреки че не вдявам много.

|

Ето и версията ми за SME. Това е реално Streaming SVE, но имплементацията е различна защото SSVE няма FFR и incp/decp са много бавни (мързи ме да обяснявам, но ако някой е любопитен може да го направя).


        smstart
        dup     z1.b, 'p'                       /* replicate byte across vector */
        dup     z2.b, 's'                       /* replicate byte across vector */
        dup     z3.b, 0                         /* all 0s for sel */
        dup     z4.b, 1                         /* all 1s for sel */
        dup     z5.b, -1                        /* all -1s for sel */
        dup     z8.s, 0                         /* return value(s) */
        ptrue   p1.b                            /* all ones; loop invariant */

        .p2align 4
0:      ld1b    z0.b, p1/z, [x0]                /* read bytes in vector */

        incb    x0                              /* speculate increment */
        dup     z9.h, 0                         /* 32-bit count for the iteration */
        cmpeq   p3.b, p1/z, z0.b, 0             /* search for 0 */
        b.ne    1f

        /* no end of string */
        cmpeq   p2.b, p1/z, z0.b, z1.b          /* search for 'p' */
        sel     z10.b, p2, z5.b, z3.b           /* for each match, select -1s from z5 */
        cmpeq   p3.b, p1/z, z0.b, z2.b          /* search for 's' */
        sel     z10.b, p3, z4.b, z10.b          /* for each match, select 1s from z4 */
        sadalp  z9.h, p1/m, z10.b               /* widen to 16 bit by adding pairwise elements*/
        sadalp  z8.s, p1/m, z9.h                /* add to the total count, widening to 32 bit */
        b       0b

        /* end of string */
1:      brka    p4.b, p1/z, p3.b                /* find first 0 */
        cmpeq   p2.b, p4/z, z0.b, z1.b          /* search for 'p' */
        sel     z10.b, p2, z5.b, z3.b           /* for each match, select -1s from z5 */
        cmpeq   p3.b, p4/z, z0.b, z2.b          /* search for 's' */
        sel     z10.b, p3, z4.b, z10.b          /* for each match, select 1s from z4 */
        sadalp  z9.h, p1/m, z10.b               /* widen to 16 bit by adding pairwise elements*/
        sadalp  z8.s, p1/m, z9.h                /* add to the total count, widening to 32 bit */

        saddv   d0, p1, z8.s                    /* reduce counts to scalar register */
        fmov    x0, d0

        smstop
        ret

Скоростта е десетки пъти по-бавна от eqsub на Apple M4. Явно SME не е много подходящ за такива дребни алгоритми.

Може да няма лесен начин за тестване за 0 на SME. Също така е трудно без участието на генерални регистри да се инвалидират 'p' и 's' бройките идващи непосредствено след нулевия знак.

#128389 (ツ) |
Създадено на 26.11.2024 , видяно: 468 пъти.
BIGBUGEX

Няма да гръмне. Подравнявам старта на 32 байта и със съответните битове за валидност инвалидирам операциите които са пред масива. От там на сетне обработвам адреси кратни на 32 байта. Ако изскочи '\0' в текущия пакет от данни се излиза от цикъла като се инвалидират бройките след него.

А, това с подравняването в началото е хитро, не се бях сетил. Ще помисля дали/как мога да го направя за SVE/SME. SME е 512 битови вектори, така че ще чете направо цяла cache line наведнъж.

Друго нещо, което трябва да пробвам е четенето наведнъж на 4 вектора (LD4x). Ще чете 256 байта за една инструкция. Не че е задължително да е по-бързо, но ще видим.

#128931 (ツ) waldorf
Създадено на 06.12.2024 , видяно: 382 пъти.

Да не отварям нова тема. Интересна дискусия и терминология за пакетиране на троична бройна система (-1,0,1) - бит, трит, тет, tits, и т.н. Пакетирането и разпакетирането стават и с неон/симд т.е. има поле за забавление.

https://compilade.net/blog/ternary-packing

Коментарите в хакер нюз също си струват - иде реч за цици даже

https://news.ycombinator.com/item?id=42329307

#128960 (ツ) |
Създадено на 06.12.2024 , видяно: 343 пъти.
waldorf

Да не отварям нова тема. Интересна дискусия и терминология за пакетиране на троична бройна система (-1,0,1) - бит, трит, тет, tits, и т.н. Пакетирането и разпакетирането стават и с неон/симд т.е. има поле за забавление.

https://compilade.net/blog/ternary-packing

Коментарите в хакер нюз също си струват - иде реч за цици даже

https://news.ycombinator.com/item?id=42329307

Моето наивно решение би било използване на таблица (по мои сметки около 512 байта дълга и лесно събираща се дори в L1), но може би SIMD ще е по-бързо, не съм го мислил в детайли.

BIGBUGEX сигурно може да каже повече.

#128992 (ツ) BIGBUGEX
Създадено на 07.12.2024 , видяно: 313 пъти.

Аз вече не бързам да се изказвам неподготвен и да тропвам с наполовина по-къса чурка по масата. Бих ползвал vpmulhuw за екстрактване от 16 битови думи. Но трябва да помисля допълнително.

#129068 (ツ) BIGBUGEX
Създадено на 08.12.2024 , видяно: 276 пъти.

Както казва @|, доста по-добре ще стане с таблица за всички 243 варианта от байта. То ще е бързо със SIMD но няма да е подредено. За подреждането се иска доста код и няма да е елегантно.

#129108 (ツ) BIGBUGEX
Създадено на 09.12.2024 , видяно: 247 пъти.

Вариант с подредено разопаковане. Но ме съмнява да е по-бързо от просто копиране от таблица. За 5 стойности се използват 10 инструкции.

#include <immintrin.h>
#include <cstdint>
#include <cstdio>

void tr_unpack( char s[8], uint8_t val ) {
	uint16_t lval = ( ( (uint16_t)val << 8 ) + 242 ) / 243;
	__m128i xval = _mm_set1_epi16( lval << 8 ),
		xmul = _mm_setr_epi16( 1, 3, 3*3, 3*3*3, 3*3*3*3, 0,0,0 ),
		x3 = _mm_set1_epi16( 3 )
	;
	
	xval = _mm_mullo_epi16( xval, xmul );
	xval = _mm_mulhi_epu16( xval, x3 );
	xval = _mm_add_epi16( xval, _mm_set1_epi16( '0' ) );
	xval = _mm_packus_epi16( xval, _mm_setzero_si128() );
	_mm_storeu_si64( s, xval );
	s[5] = 0;
}

uint8_t tr_pack( char s[8] ) {
	uint8_t res = 0;
	
	for( int i = 0; i < 5; i++ ) {
		res = res * 3 + ( s[i] - '0' );
	}
	
	return res;
}

int main() {
	char s[8];
	
	for( unsigned i = 0; i < 243; i++ ) {
		tr_unpack( s, i );
		
		if( tr_pack( s ) != i ) printf( "%3u fail: %s\n", i, s );
	}
	
	return 0;
}

#129110 (ツ) |
Създадено на 09.12.2024 , видяно: 242 пъти.

Не съм гледал подробно кода в ggml (който е споменат в блог поста), но според мен идеята не е да се unpack-не само един байт със SIMD, а колкото може стойности от матрицата едновременно. И резултата вероятно не е цели числа, а fp8 и не се записват някъде в паметта, а се прави dot product с тях и се записва резултата (fp16 или fp32, не знам какво точно използват).

Интуицията ми казва, че с таблица би трябвало да е по-бързо за да може FP юнитите да се използват за умножаването на векторите паралелно със зареждането на следващите стойности, но може и да греша. Не претендирам да разбирам микроархитектурата на процесорите много добре.

#129111 (ツ) waldorf
Създадено на 09.12.2024 , видяно: 229 пъти.

Аз го погледнах. По точно кода от комита който добавя поддръжка за това тритово пакетиране. Изглежда читаво написано но не съм в час с векторните инструкции за да преценя доколко е ефективно.

Според мен най ефективно би било да се заредят примерно 4 пакетирани байта в 16 битови фиксед поинт фрагменти 8:8 и след това да се вадят наведнъж с по едно умножение в паралел по един трит.

#129120 (ツ) |
Създадено на 09.12.2024 , видяно: 207 пъти.
waldorf

Аз го погледнах. По точно кода от комита който добавя поддръжка за това тритово пакетиране. Изглежда читаво написано но не съм в час с векторните инструкции за да преценя доколко е ефективно.

Според мен най ефективно би било да се заредят примерно 4 пакетирани байта в 16 битови фиксед поинт фрагменти 8:8 и след това да се вадят наведнъж с по едно умножение в паралел по един трит.

Проблемът е какво ще ги правиш тези 20 стойности след като ги разпакетираш. Основната причина да се прави куантизация на моделите е да се намали трафика на данни от/до паметта. Ако разпакетираш и записваш в паметта, правиш повече трафик. Ако разпакетираш и използваш за сметки, 20 стойности не влизат точно в никакви векторни регистри. Най-вероятно 4 от стойностите ще трябва да се изхвърлят и следващия път да се разпакетират пак, (с малко по-различен алгоритъм за да се окажат на правилното място във векторните регистри).

Затова микробенчмарките са доста малоумно занятие. Отделят някаква важна, но дребна логика и оптимизираш като улав неща, които няма да се случат по същия начин в реалния живот.

#129129 (ツ) waldorf
Създадено на 09.12.2024 , видяно: 194 пъти.
|

Проблемът е какво ще ги правиш тези 20 стойности след като ги разпакетираш. Основната причина да се прави куантизация на моделите е да се намали трафика на данни от/до паметта. Ако разпакетираш и записваш в паметта, правиш повече трафик. Ако разпакетираш и използваш за сметки, 20 стойности не влизат точно в никакви векторни регистри. Най-вероятно 4 от стойностите ще трябва да се изхвърлят и следващия път да се разпакетират пак, (с малко по-различен алгоритъм за да се окажат на правилното място във векторните регистри).

Затова микробенчмарките са доста малоумно занятие. Отделят някаква важна, но дребна логика и оптимизираш като улав неща, които няма да се случат по същия начин в реалния живот.

Т.е. това пакетиране на 5 стойности в 1 байт не решава кой знае какви проблеми и е по добре да се кодират 4 стойности по 2 бита за да не се минава през излишно междинно декомпресиране?

То тогава единствената разумна причина да компресираш по 5 стойности в байт е да спестиш едни няма и 20% при зареждането на моделите от диска когато имаш бол памет и бавна външен носител. Т.е. може и да има някакъв смисъл ако се налага непрекъснато да зареждаш модела от диск.

#129130 (ツ) |
Създадено на 09.12.2024 , видяно: 178 пъти.
waldorf

Т.е. това пакетиране на 5 стойности в 1 байт не решава кой знае какви проблеми и е по добре да се кодират 4 стойности по 2 бита за да не се минава през излишно междинно декомпресиране?

То тогава единствената разумна причина да компресираш по 5 стойности в байт е да спестиш едни няма и 20% при зареждането на моделите от диска когато имаш бол памет и бавна външен носител. Т.е. може и да има някакъв смисъл ако се налага непрекъснато да зареждаш модела от диск.

Това е моето разбиране, но може и да греша. Не знам детайли за ggml, нито съм чел статиите на хората, които са пакетирали в 1.5 бита. Един младеж от групата ми се забавлява доста време с ggml, ще го питам как точно работи куантизацията там.

#129135 (ツ) |
Създадено на 09.12.2024 , видяно: 153 пъти.
#129194 (ツ) |
Създадено на 10.12.2024 , видяно: 112 пъти.
|

Това е моето разбиране, но може и да греша. Не знам детайли за ggml, нито съм чел статиите на хората, които са пакетирали в 1.5 бита. Един младеж от групата ми се забавлява доста време с ggml, ще го питам как точно работи куантизацията там.

Питах младежа, за случая с 4 битовите стойности е load, widen, math, store result of math.

Според мен 1.5 битовите стойности са DoA ако няма хардуерен съпорт, но ще видим. :)

0 1 2 3

neon
0

AsmBB v3.0 (check-in: 7544654b24928b93); SQLite v3.47.0 (check-in: 03a9703e27c44437);
©2016..2024 John Found; Licensed under EUPL. Powered by Assembly language Created with Fresh IDE