dimhold.by
← Артыкулы

Студзеньскі і снежаньскі venv далі GPT-2 тыя ж 20 000 токенаў. Лагіты паміж імі змяніліся

Выбарка выключаная або seed зафіксаваны, значыць на той жа промпт прыйдзе той жа адказ. Так я да гэтага ставіўся ўвесь год і ні разу не правяраў. Калі лакальная генерацыя не супадала з тым, што я захаваў раней, я казаў сабе “выбарка” і запускаў яшчэ раз, не пытаючыся, ці не змянілася асяроддзе мадэлі. А асяроддзе ў мяне мяняецца раз на некалькі тыдняў, бо я раблю pip install -U кожны раз, калі ў новым рэлізе transformers ёсць патрэбная мне праўка. Аднаго канкрэтнага зламанага вываду, які прымусіў бы мяне палезці ўнутр, не было. Проста канчаўся год і мне хацелася зразумець, што зрабіў з вывадам год такіх абнаўленняў.

Студзеньскія вывады я не захаваў, таму сабраў студзеньскі venv нанова з версій, што былі свежымі тады. Гэта torch 1.10.1 ад 15 снежня 2021 года і transformers 4.15.0 ад 22 снежня 2021 года. У снежаньскім venv стаяць torch 1.13.1 з мінулага чацвярга і transformers 4.25.1. Абодва працуюць на Python 3.9, толькі на CPU, у float32, на 4 vCPU віртуальнай машыны з AMD EPYC 7713. Вагі ўзятыя з GPT-2 small на 124 млн параметраў, адзін і той жа файл pytorch_model.bin ва ўсіх прагонах.

Тэкст не зрушыўся

Я ўзяў 8 промптаў, ад “The capital of France is” да “Q: What is 17 times 23?”, па 60 новых токенаў на кожны. Кожны промпт я прагнаў у трох рэжымах: прагна ў сваім цыкле з argmax, прагна праз generate() і з выбаркай (top_k 50, torch.manual_seed(1234) перад кожным промптам). Я чакаў, што першай зламаецца выбарка: яна залежыць ад усяго размеркавання, а не толькі ад таго, які токен на першым месцы.

Нічога не зламалася. Студзень і снежань далі тыя ж токены на ўсіх 8 промптах ва ўсіх рэжымах, уключна з выбаркай. На арыфметычнае пытанне GPT-2 адказвае ” 17 times 23.” і задае пытанне зноў, па крузе, у абодвух venv.

Лагіты пры гэтым змяніліся. Паўторны запуск у тым жа venv даў тыя ж біты. Снежань разыходзіцца са студзенем у 6 промптах з 8, на першым кроку не больш чым на 0,000107.

Тады я стаў мяняць па адным пакеце. transformers з 4.15.0 на 4.25.1 пры тым жа torch не змяніў ніводнага біта. Код увагі GPT-2 за гэты час у ім мяняўся, значэнне маскі і дзяленне на маштаб, але ніводная з гэтых правак тут біты не зрушыла. torch 1.11.0 дае роўна тыя ж біты, што 1.10.1, а 1.12.1 роўна тыя ж, што 1.13.1. Версію 1.12.0 я не спрабаваў, так што ўся розніца за год хаваецца недзе паміж 1.11.0 і 1.12.1.

супраць студзеньскага venv: torch 1.10.1, transformers 4.15.0 умова лагіты, 8 промптаў макс. зрух тэкст студзень, яшчэ раз 0 24/24 студзень, 1 паток 0,000023 24/24 студзень, універсальныя ядры 0,000061 24/24 torch 1.11.0 0 24/24 torch 1.12.1 0,000107 24/24 torch 1.13.1, transformers з 4.15 па 4.22 0,000107 24/24 снежань: 1.13.1 і 4.25.1 0,000107 24/24 снежань, універсальныя ядры 0,000061 24/24 біты тыя ж біты іншыя
Лагіты першага кроку, параўнанне па бітах. Тэкст параўноўваецца для 8 промптаў у 3 рэжымах (свой прагны цыкл, generate, выбарка з seed).

Дзе прыступка

2 промпты захавалі свае біты нават пасля гэтай прыступкі і я не разумеў чаму. Спачатку я патраціў час на MKL. З MKL_VERBOSE=1 torch паказвае выклікі SGEMM з MKL 2020.0, гэта значыць матрыцы сапраўды перамнажае MKL. Але з MKL_ENABLE_INSTRUCTIONS=SSE4_2 не змянілася наогул нічога, ніводнага біта ні ў адным venv. Чаму, я дагэтуль не ведаю.

Потым я паспрабаваў ATEN_CPU_CAPABILITY=default. З ім torch бярэ свае ўніверсальныя ядры замест ядраў AVX2, як на старым працэсары. Лагіты зрушыліся максімум на 0,000122 супраць звычайнага снежаньскага прагону. Студзень і снежань сталі пабітна аднолькавымі. Значыць, змена сядзела ў адным з ядраў AVX2.

Я выгрузіў выхад кожнага слоя на промпце з найбольшым разыходжаннем. Гэта арыфметычнае пытанне, у ім 11 токенаў. Эмбедынгі і першы LayerNorm супадаюць. Першае разыходжанне, 0,0000019, з’яўляецца на выхадзе ўвагі ў блоку 0 і да апошняга блока дарастае да 0,00032. Потым я прагнаў кожную аперацыю асобна на фіксаваным выпадковым уваходзе. Адрозніваецца толькі softmax.

У 2 промптаў, якія захавалі біты, 5 і 7 токенаў. Ва ўсіх астатніх 8 і больш. Я прагнаў softmax на радках усіх даўжынь ад 1 да 64. Даўжыні з 1 па 7 даюць тыя ж біты ў абедзвюх версіях. Пачынаючы з 8 у кожнай даўжыні ёсць радкі, якія разыходзяцца, акрамя 23. Думаю, гэта выпадковасць.

8 лікаў float гэта адзін рэгістр AVX2. Астатняе відаць у дыфе aten/src/ATen/cpu/vec/functional_base.h паміж 2 тэгамі. Softmax сумуе экспаненты праз reduce_all, а той трымае 8 частковых сум у палосах рэгістра і напрыканцы зводзіць іх у адзін лік. У 1.11.0 гэтая згортка ідзе паласа за паласой: да паласы 0 дадаецца паласа 1, потым паласа 2 і так да паласы 7. У 1.12.1 для AVX2 дадалі хутчэйшую згортку:

// 128-bit shuffle
Vec v1 = _mm256_permute2f128_ps(v, v, 0x1);
v = vec_fun(v, v1);
// 64-bit shuffle
v1 = _mm256_shuffle_ps(v, v, 0x4E);
v = vec_fun(v, v1);
// 32-bit shuffle
v1 = _mm256_shuffle_ps(v, v, 0xB1);
v = vec_fun(v, v1);

Яна складае палосы праз 4, потым праз 2, потым суседнія. Лікі тыя ж, парадак складання іншы, а ў float ад гэтага можа змяніцца апошні біт. Радкі, карацейшыя за адзін рэгістр, да новага кода не даходзяць. Ва ўніверсальных ядрах яго няма зусім. Няма яго і ў зборках кампілятарам MSVC: новая згортка закрытая праверкай на GCC, так што на Windows гэтай прыступкі, хутчэй за ўсё, няма.

torch 1.11.0: паласа за паласой a0 a1 a2 a3 a4 a5 a6 a7 + + + + + + + 1.12.1 з AVX2: праз 4, 2, 1 a0 a1 a2 a3 a4 a5 a6 a7 + + + + + + + softmax па радках даўжынёй ад 1 да 64, 1.11.0 супраць 1.12.1 1 8 23 64 тыя ж іншыя
Тыя ж 8 лікаў, іншы парадак складання. Радкі, карацейшыя за адзін рэгістр, да новай згорткі не даходзяць.

Чаму токены выжылі

Зрух на 0,0001 мае значэнне толькі тады, калі зазор паміж першым і другім токенам меншы за яго. На 8 промптах пра гэта нічога не скажаш, таму я ўзяў 200 пачаткаў сказаў з “Алісы ў Краіне цудаў” і згенераваў прагна па 100 токенаў на кожны, 20 000 крокаў на прагон.

Прагонаў было 6, але розных набораў зазораў сярод іх толькі 3. Паўторны снежаньскі прагон і прагон на 1 патоку супадаюць да біта. Абодва прагоны на ўніверсальных ядрах таксама супадаюць, а студзень гэта трэці набор. Усе 200 працягаў аказаліся аднолькавымі ва ўсіх 6 прагонах.

Найменшы зазор паміж першым і другім токенам быў 0,000366, на кроку 14 сказа, які пачынаецца з “So she set to work”. Ён супаў да апошняга біта ва ўсіх прагонах. Ніводная ўмова не зрушыла ніводны зазор больш чым на 0,000172, так што ў самага цеснага кроку быў прыкладна двайны запас.

зазор паміж першым і другім токенам, 20 000 прагных крокаў 0,0001 0,001 0,01 0,1 1 10 100 зазор, лагарыфмічная шкала 1 10 100 1000 крокаў найбольшы зрух 0,000172 самы цесны крок 0,000366 медыяна 4,02
200 пачаткаў сказаў з "Алісы ў Краіне цудаў", па 100 токенаў, снежаньскі venv. Зазор любога кроку большы за падвоены найбольшы зрух.

Прагная GPT-2 хутка зацыкліваецца і ў цыкле яна вельмі ўпэўненая ў сабе. Медыянны зазор на першых 10 кроках роўны 0,70, а на апошніх 10 ужо 6,27. У мадэлі, якая вагаецца часцей, крокаў каля нуля было б больш. Тады і той жа зрух на 0,0001 меў бы больш шанцаў перавярнуць выбар токена.

1 паток замест 4 таксама зрушыў лагіты першага кроку, не больш чым на 0,0000229 у абодвух venv. Ніводнага токена ён не змяніў. Чаму, я не разбіраўся.

Чаго я не правяраў

  • GPU і float16, дзе парадак складання залежыць ад іншых ядраў, якія я не чытаў.
  • Батчынг на серверы, з якім маюць справу карыстальнікі воблачных API. У мяне батч 1 і машына нічым больш не занятая.
  • Іншы сапраўдны працэсар. У AVX-512 у тым жа файле свая згортка. Стары працэсар я толькі імітаваў зменнай асяроддзя.
  • Сам каміт. Я чытаў дыф паміж тэгамі v1.11.0 і v1.12.1, а не сам pull request.
  • Большыя мадэлі і даўжэйшыя вывады. 20 000 крокаў зацыкленай мадэлі на 124 млн параметраў гэта не старонка тэксту ад большай мадэлі.

На гэтай машыне я магу перастаць спісваць змененыя вывады на выбарку, прынамсі з GPT-2. Але ў самага цеснага кроку запас быў толькі двайны і для мадэлі, якая зацыкліваецца радзей, я б гэтага не абяцаў.