Галоўная / Артыкулы

Навучанне маленькага GPT на індыйскіх народных казках: токенізатар, маска і страты

GPT на 9 млн параметраў з нуля: чаму токенізатар GPT-2 не спраўляецца са словамі казак, як працуюць BPE і каўзальная маска і як адзін батч дае 4096 страт на наступным токене.

Выдавец: REACTAPP.TOP16 хв чытання3644 слоў

Крыніцы: Building LLM, Part 1 — Training: Predicting the Next Token

Маленькі GPT, трэнаваныя на індыйскіх народных казках

Кожны, хто ужо стварыў Vision Transformer, знае прыблізна 70 процэнтав мовнага модэля. Эмбедынгі, атэнцыя, стак рэзыдуальных элементаў і цыкл трэніравання — гэта тыя ж элементы ў обох. Што застаецца — гэта дэльта, і ў гэтым каруселі мы розглядзім яе, стварыўшы маленькі GPT на адных прынцыпах і трэнаваўшы яго з нуля на GPU адной ноутбука. Корпус дакументаў — індыйскія народныя казкі: історыі Джатакі, байкі у стыле Панчатантры і казкі з Панджаба і Бенгаліі.

Спачатку з’являецца справжня проблема. Стандартны токенізатор GPT-2 розбівае такі слова, як Brahman і Rajah, на фрагменты, таму што ніколі не стыкаўся з ямі, таму заместа яго трэнуецца токенізатор на адповідным корпусе. У канцы ёсць мадэль з 9 мільйона параметраў, якая перайшла ад вялікага колькавіства віпадковых даных да трэнаваных ваг, і кожны можа воспараваць гэтыя параметры, запускіўшы трэнаванне, якое трывае або блізка да дзесяці хвілін.

Чаму спачатку была візуальная частка

Разгляд зображэнняў раней за тэкст можа здавацца неправільным порадкам, адколі чат-боты працуюць з словамі. Аднак трансфармер не зважае на тое, чыі ў яго ўвход — фрагменты фота чы ўрывкі рэчэнняў. Галавы атэнцыі, LayerNorm, з’еднанні тыпу skip connection, MLP і цыкл оптымізацыі не змінююцца. Лепшы вопыт — гдэ гэты спакойны механізм найлегча выучыць, і зображэння перамагаюць з чатырох прычын.

  • Токэн — гэта проста-такі фрагмент зялёна, адскладзены ў пэўную сетку з каратка, таму не трэба рашваць ні вокабуляр, ні інструменты для токэнацыі, ні проблему незнайомых слоў.
  • Няма маскі прычыннасці. Мадэль ViT бачыць увесь зялён адразу, таму простая атэнцыя выканаеся раней, чым дзіюць дапаможныя правілы мовы, якія забараняюць адгледзець будучыню.
  • Працэс мадэлі ўможліваў візуальна адразу. Фрагменты зялёна, карты атэнцыі і рэгіоны, на якія звертае ўвагу мадэль, можна паказаць на картэ, а пікселі ўжо ціха болей зрозумелы, чым тэкст.
  • Мадэль можа быць навучаная з нуля на аднай відэакарцы за калькі хвілін (на датасэтэ CIFAR-10), таму кожны рысунак — гэта цяперашняе чысло, якое вы самі стварылі, а не проста перыялаеце з іншага джэрага.

У сукупнасці, калі ужо створэны ViT, існуе цэлы трансфармер, проста ўжо адпрацаваны для працы з візуальнымі данымі. Мадэль мовы проста дадае спосаб, як тэкст ператвараецца на токены (за дапамою справакі, а не вычыслаў), каузальную маску і прогназаванне наступнага токена. Складная, абстрактная частка — сама мова — становіцца набаго простэйшай для розумення, калі машына ужо звыкла да яе.

Адучэнне — гэта проста гра: запазнаць наступны токен

У час адучэння все, што робіць мадэль мовы, сводзіцца да чытання ўжо наяўных токенаў і прогназавання наступнага з яных.

На графіку паказана працэс прыбліжнага вычыслення наступнага токена на адказ прынедбаных раней токэнаў. Калі падаць яго ў вхідны тэкст «У аднай з вёсак жыў бедны Брахман», то будзе выдана верыгоднасць кожнага магчымага наступнага токена. Канец фразы, запятыца або слова на кшталт хто чы названы ўсе будуць граматычна правільныя. Не трэба нічага анотаваць: правытая адказ — гэты токен, які ўжо быў у тэксце. Самэльга сам тэкст можа навучыць мадэль мовы, і ён являе сабою весь сігнал для навучэння. Усе іншыя элементы існуюць толькі для падтрымкі гэтага прыбліжнага вычыслення, і ViT вялікую частку з іх ужэ з’ядруў.

У кодзе гэя ідэя выражаецца ў двух рэчынках: спачатку запускаецца мадэль, а пасля яе рэзультаты нормалізуюцца ў распад наступнага токена.

logits, _ = model(ids)                     # ids = the tokens so far, shape (1, T)
probs = F.softmax(logits[0, -1], dim=-1)   # one probability per vocabulary token

У рэшце часты апісваецца, як ствараецца і трэнуецца модель, каб тыя верагатыбы былі правільнымі.

Чаму важна токенізацыя

Мадэль ViT дзеляе зображэнне на фартчыкі, тады як мовная мадэль дзеляе тэкст на токены. Фартчык вырахоўваецца на адной з піксельных значэнняў, а токен тэксту бераецца з фіксаванага слоўніку. Каму-небудзь трэба загадзя стварыць гэты слоўнік, і той, хто яго стварае, вяршыць, калі для мадэлі будзе лёгка ўжываць тыя чы ілі іншыя элементы.

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

Brahman дзеліцца на Brah і man, Rajah — на Raj і ah, а Panchatantra рассыпаецца на пяць частакоў. Нічнае з гэтага не ёсць незрозумелым. Rajah павтараецца 709 разоў у корпусе, але токенізатор ніколі не бачыў такіх слоў достатнь часта ў свых власных даных для надання кожнаму з іх окрэмага токена. Фрагментаванне падлягае последовнасці і змушвае мадэль перыяднаць Brahman з Brah і man, пры чым man таксама выкорыстоўваецца з woman і human. З урахоўваннем таго, што ёсць лішаеся апошні мільйон токенаў дадзеных для навчання, гэта ёсць справжнія перашкода.

Ліком ёсць токенайзер, навчэній на цых оповіданнях за допомогою кодування пар байтыв (BPE). GPT вікорыстовуе той самій алгорытм, проце адаптованій під більш відповідній корпус даных. Джэрэлом матэрыалу ёсць дванадцяць збірок індыйськіх народніх казок з праекту Project Gutenberg, у тому чыслі дві кнігі Jataka, два томі індыйськіх казок, «Стародавні дні Дэкану», «Народні казкі Бенгаліі», «Розмовній дрозд», «Казкі Пенджабу», «Дваццаць два гобліні», дві збіркі казок народу Сантал і «Казкі сонця» з Півдэнноі Індыі; загальна колькасць слів становіць блізько 809 000.

Гэты працэс перэдбачае об’еднання пар байтыв, які вікорыстовуваліся для навчання токенайзера. BPE почынаецца з 256 первінніх значэнь байтыв, тож немае нічого невідомого; потым постыйно об’еднуюцца найпошырэнішы сусідні пары у нові токені. Пасля 7 935 такіх об’еднань у казках словнік містыць 8 192 элементаў, у тому чыслі цілі одыніці, такі як Rajah, Brahman і jackal.

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

На рэчыце паказана спецыяльная лексыкантная база з 8,192 токенамі. Дадзенне дапаможных токенама да існуючых 50,257 у r50k ёст калькаматны спосаб, калі трэба захаваць англійскія навыкі большога прадзэталёванага модэлю, як гэта робяць у праектах уродзе OpenHathi. Атрыбутаванне модэлю з нуля на маленькай, спецыялізаванай базе дадзеных — гэта іншая сітуацыя: лексыкантная база r50k, створаная на захадным вебе, толькі займае прастор, а історыі павярнуліся толькі 37 процэнтам яе. Новая лексыкантная база з 8 тысяч токенамі меньшая, 96,6 процэнтам яе вікорыстоўваецца, і ёна адпрацавана пад стварэнныя історыі, таму замена ёй — гэта найлепшы варыянт.

Атрыбутаванне токенізатора адбываецца за дапамою калькаматных рэчынакаў бібліятэкі HuggingFace tokenizers. Скрыпт чытае кожную кнігу у сыром фармаце, адзьяўляе заголовак і падзаголовак праекту Project Gutenberg і атрыбутавае модэль BPE.

import glob
from tokenizers import Tokenizer, models, trainers, pre_tokenizers
from llm_transformer.prep_tales import strip_gutenberg, RAW_DIR

# read every raw .txt book and strip its Project Gutenberg header/footer,
# leaving just the story text - one big string per book
tales_texts = [strip_gutenberg(open(p, encoding="utf-8", errors="ignore").read())
               for p in sorted(glob.glob(f"{RAW_DIR}/*.txt"))]   # 12 books, ~809k words
tok = Tokenizer(models.BPE())
tok.pre_tokenizer = pre_tokenizers.ByteLevel(add_prefix_space=False)   # start from bytes
trainer = trainers.BpeTrainer(vocab_size=8192, special_tokens=["<|endoftext|>"],
                              initial_alphabet=pre_tokenizers.ByteLevel.alphabet())
tok.train_from_iterator(tales_texts, trainer)   # learn the 7,935 merges
tok.save("tales_bpe.json")

Файл выходных дадзеных .json — гэта готовы токенізатор, фіксаваныя спісы з’еднанняў. Ён не трэнуецца знову. Ён завантажаецца і выкарыстоўваецца для перакладу тэксту ў ідэнтыфікаторы і назад з ідэнтыфікатораў у тэкст. Одна вызовка .encode() спаявае неапрануты тэкст з нейральной сеткай.

tok = Tokenizer.from_file("tales_bpe.json")   # reload the trained tokenizer
ids = tok.encode("Once upon a time").ids      # -> [412, 987, 15, 733]   (integers)
idx = torch.tensor(ids).unsqueeze(0)          # (1, 4) — a batch of one sequence

vocab_size = tok.get_vocab_size()             # 8192 - sets the NUMBER OF ROWS below

У сетку практычна толькі гэтыя цэлыя чысла, а самыя первасныя слова для яе больш не існуе. Кожны id — гэта проста пазіцыя ў табліце, а vocab_size вызначае, сколькі елементаў зарэзервуе шар умяшчэння.

З ids да вектораў

Калі тэкст стае спісам ids, кожны з іх ператвараецца на вектор. Цэя ўсё тая частка, дзе LLM простэйшы за ViT.

На рэчысле паказвана звязка між індэксам токенайзера і векторам умяшчэння. У ViT кожны токен пачынаецца з маленькага множэння матрыц, якае вырахоўвае яго на адной з пазловых частак з пікселей. У LLM токен — гэта дыскурдны id, таму вектор — проста номер рэядка ў навучанай табліцы, звычная апытка. Токен 789 выяўляе рэядак 789, які ёсць 256-вымерным векторам, а дадаўшы позіцыйнае умяшчэння, як у ViT, завершаецца стадыя вхідных даных.

У кодзе слой nn.Embedding являецца таблой, і калі ўжоўваецца, яны выконваець пошук па індэксу, а не па правілу множэння. Яго .weight — гэта навчальны тэнзор размерам 8192 на 256, які мае 2,1 мільйона ваг, якія трэнуюцца за дапамою алгорытму backprop, прычым конкрэтны радок зменяецца толькі тады, калі ў яму з’яўляецца відпаведны токен.

self.token_embed = nn.Embedding(vocab_size, n_embed)   # the 8,192 x 256 table
# .weight IS a learnable (8192, 256) parameter tensor — 2.1M weights, trained by backprop;
# each id's row is nudged only on steps where that token appears in the batch
...
tok_emb = self.token_embed(idx)     # idx (B, T)  ->  vectors (B, T, 256), one row per id

Усе, што настаўляе пасля гэтага момента – тыя самыя прыемы звернення увагі, залішковы поток і блакіты MLP – адпавядае структурам Vision Transformer, за аднам выключэнням.

Прычынна-наследны маск

ViT дазваляе кожнаму фрагменту даных звертаць увагу на ўсе іншыя фрагменты. Мадэль мовы не можа гэтага зробіць, адколі яе задача – спрабаваць здагадацца, што будзе далей. Якбы позіцыя t бачыла пазначку t+1, яна бы проста скопіявала адпаведны адказ. Рашэнням ёсць маскуванне працэсу звернення увагі, так што кожны токен бачыць толькі сябе і пазначкі, якія знаходзяцца раней.

Маска на рэчысле мае форму ніжнего троекутніка. Перад вычысленням функцыі softmax усім будучым пазначкам прызначаецца значэнне „мінус несканчатна“, якое пасля застосавання softmax ператвараецца на вагу, равную абсолютна нулю; такім чынам заблакаваныя токены не дадаюць жадных дапамог. Гэты троекутнік ёсць едынай архітэктурной разлікам межу прыемам звернення увагі у ViT і LLM. Продукты скалярнага дзьявлення, масштабаванне па квадратнай корэні з розмеру блаката, функцыя softmax і сума значэнняў з узважэннем на вагі застаюцца незменнымі.

У кодзе гэта ўсьмо толькі адна дапаможная лінія пад звычным механізам увагі. Спачатку ствараецца матрыца з елементамі 1 у ніжньо-трэугольнай форме, потым вычысляюцца балы як зазвычай, а пазыціі, заполненыя нулямі, перад выконаннем функцыі softmax заполняюцца значэнняамі мінус-бесканечнасці.

# tril = torch.tril(torch.ones(T, T))  — a lower-triangular matrix of 1s, made once
attn = q @ k.transpose(-2, -1) / math.sqrt(head_size)          # scores, (B, T, T)
attn = attn.masked_fill(self.tril[:T, :T] == 0, float('-inf')) # blank out the future
attn = F.softmax(attn, dim=-1)                                  # -inf -> weight 0
out  = attn @ v

Адзін праход трэнуе кожную дужыню кантэксту

Маска з адной лініяй скрывае тое, што патрабуе болейшага аналізу. Люды частая прыпускаюць, што мадэль мовы працуе так, як генеруе тэкст: дадзеўнаючы токен у растучы кантэкст і занова выконваючы атэнцыю на кожным кроку. Гэта правда пад час інферэнцыі, але не падчас трэніравання. Пад час трэніравання рэчыця вядома цалкам, таму Q, K і V для всіх T-токенав вычысляюцца разам, ствараючы цэлую матрыцу атэнцыі T на T за адзіны крок.

Гэты адзін праход ўсунаменны толькі завярога казальнай маскай. Рассмотрзім радок t маскуванай матрыцы: усі элементы права ад дыягоналі ўжо маюць значэнне «мінус безканечнасць», таму пасля вычыслення softmax яны стаюць нулем. Вычысленне out = attn @ V дае радку t суму з вагамі толькі для першых t вектораў значэнняў. Такім чынам кожная дужына кантэксту обрабляецца адразу.

На рэсунке паказаны разныя запускі і форма маскі пад трэнаваннем. Чытайце яго радкамі па радку:

  • У 1-му радку уважаецца толькі токен 1, таму яго выхад здатны працаваць з токенам 2 на аднам токене кантэксту.
  • У 3-му радку уважаюцца токены 1, 2 і 3, і прогназуецца токен 4 на трох токенах кантэксту.
  • У 6-му радку уважаюцца всі шысьце токенаў, і прагназуецца токен 7 на всім кантэксце.

Ёсць ўжо адна вёскапа. Візуабельны кантэкст кожнай рэядка стварае матрыцу ніжньага троекутніка, якая з кожным разам павялічваецца на адну елемента: 1 на 1 для першага токена, потым 2 на 2 і 3 на 3. Гэтыя блакіты ў верхнь-левай частцы вялікай матрыцы, накрасленыя аранжавым колерам, таму ўжо одна процедура 6 на 6 паводзіцца з усіма меньшымі фрагментамі, а смуга ў ніжней частцы іх розкрывае. Усё гэта ствараецца ўнутрь адного attn @ V, і ў Python няма жадных цыклів па пазіцыях. Гэта пояснюе эфектыўнасць навчання трансформероў: последоваць з T токенамі адпавядае T прыкладам навчання, пры чым кантэксты маюць дужыну ад 1 да T і оцінююцца паралельна. Тая ж троекутная маска, якая задае гэтыя прыклады, таксама не дазволяе жадной пазіцыі сягнуць да наступных элементаў пад час генеравання, пры чым працэс адбываецца рэядкамі, а кожны крок застаецца дыявальным завдзяк кэшаванню паказваных раней ключоў і значэнняў (KV-кэш).

Рэальныя чысла праз атэнцыю

Даўжыню кожнай клеткі ўсё даўна паводзілася як «прыняць удзел» або «не прыняць удзел». Адкрываючы можлівасць множэння, паказваюцца рэальныя цыфры для тых самых трох працаванняў, взятые з блока 0, галавы 0 натрэнаванага мадэлі на рэчэнні «Калісьці жыў хітры ёжак».

У графіцы паказваны вычысленні Q, K, V для кожнага з працаванняў. У межах адной рэядкі вагі атэнцыі ўзятыя як пазітывныя і дадаюць 1, таму выходны рэзультат — гэта выпуклая сумешчанне вектараў значэнняў, што ёсць вагавым сярэднім.

  • У першым працаванні ёсць адзін токен з вагой 1,00, таму out1 = V1. Першая пазіцыя копіюе сваё сабеўласны значэнне, адколі ўсё яшчо няма чаго сумешваць.
  • У трэцім працаванні выходны рэзультат сумешчае тры значэнняя з вагамі апошней час 0,20 для першага токена, 0,06 для другога і 0,74 для трэціго. Ён найбольш паслугваецца сабеўласным токенам lived, але таксама прыймае пяту частку з Once, таму гэта справжняя сумешчанне.
  • У шостым запуску вага распадаецца на ўсе шысьце значэнняў: по 0,26 на кожна з there і jackal. Шырокі 32-вымерны вектар падсумовывае ўсю інфармацыю прыкладзеную да галавы, а пазнейшыя шары ператвараюць яго ў прагноз.
  • Гэта пакрывае ўсё, што робіць механізм увагі: разлікі з запытамі і ключамі задаюць вагі, а вагі сумешчаюць значэння. Маскаванне гарантуе, што сумешчанне ніколі не выходзіць за межы тэкушчага токена, таму кожны рад ёсць справедлівым падсумкам усьго, што было прачытана ўжо. Рэальныя галавы маюць 32 вымеры значэнняў, а мадэль мае восем галав у кожным з шысьця шароў. Стрэлкі паказваюць першыя 8 вымераў адной галавы, хоць той самы механізм праіснуе ў всіх віпадках.

    Прагнозы T, патырэбныя втраты T

    Прыгненне наступнага токэна дае прыянос, якога не было у ViT. ViT рабіў адна прыгненне на кожны зразок — атрыбут класу. Каузальная маска дазволяе мовнаму модэлю адгадваць наступны токэн у всіх пазычынках адразу, таму секвэнцы дае столькі ж сігналаў для навучэння, сколькі і токэнаў у яй.

    На рысунку паказана сярэдняе значэнне T прыгненняў і значэнняя патырэй для вхіднае секвэнцы з T токэнаў за кожны запуск. Кожная пазычынка отрымае патырэй крос-ентрапі, які мерыць, насколькі модэль быў здзівлёны рэальным наступным токэнам. Дзеякія з іх ёсць простымі, напрыклад a пасля lived, з значэнням 0,58. Іншыя ёсць складнымі, напрыклад аднацвеча clever, з значэнням 7,17, адтуды што могло пасасты багато слоў. Сярэдняе з шасці дае 3,59, а бэкпрапагацыя зменшае гэта значэнне, выкарыстоўваючы тую ж схему правіла ланцужка, як і ViT.

    У кодзе всі втраты T выкаліваюцца з адной-ўсёй вызову cross_entropy. Сітка пазіцыяў (B, T) прыменшваецца да аднаго дзьяўнага спісу, і кожны елемент оцэнюецца па адносу да яго справжньага наступнага токена.

    logits = self.lm_head(x)                       # (B, T, vocab)
    B, T, C = logits.shape
    loss = F.cross_entropy(logits.reshape(B*T, C), # every position ...
                           targets.reshape(B*T))   # ... vs the token that actually followed
    

    Кожная падыння ў розрахунках — гэта класыфікацыя слоўніку

    Важна точна вказваць, што поручае cross_entropy, таму ўсё-такі прычына — прогноз і мета ёсць векторы, а не адзінкі словы. У кожной пазіцыі lm_head стварае вектор лоджытов з 8,192 елементамі, па аднаму балу на кожны токен слоўніку, а функцыя softmax ператварае яго ў распад верыятнасей для всіх іх. Мета — гэта вектор у фармаце one-hot таго ж розміру, які мае 1 для слова, якое насправды выйшла пасля, і 0 у всіх іншых месцах. Тады прогназаванне наступнага токена стае задачай класыфікацыі, дзе словы слоўніку ёсць класамі.

    На графіку прагматы LLM і ViT паравярнуюцца. У ям выявляецца адна тонкая дэталь: стрэлкі распараджаны па верыятносцях толькі для лёгкага чытання, тады калі фактычны выхід — гэта вектар з фіксаваным фарматам, дзе пазулка i завжды належыць ідэнтыфікатору токена i. Слова "son" застаецца пад індэксам 498 у кожным працэсе обработкі, чыя бы то ні было — трэніраванне або вычыслення, а функцыя softmax зменяе толькі высоту пазулак, а не ўпорадкуванне іх. Гэта пояснюе, чаму ўдзельны 1 у фармаце one-hot знаходзіцца пад індэксам 498, а не на першай пазулцы: токенізатор асignmentаваў кожнаму токану яго ідэнтыфікатор як пазулку.

    Гэта самэ ўсё – галава класыфікатора ViT, якая прыменяе функцыю softmax да рэйтынгаў класаў і оцінюе іх за дапамою крос-ентропіі па адно-гэтовым пазначэнні. Разлікаецца два аспекты: колькасць класаў (8 192 токены слоўніку протыяпош 10 пазначэнняў для зразкаў) і частота выкарыстоўвання (у всіх позыціях T протыяпош аднажды на кожны зразак). Працуючы з адно-гэтовым цэльным пазначэннем, крос-ентропія таксама спраўляецца як мінус логарым з верыятнасці, прызначанай праваму слову; таму губіцы кожной пазыцыі выглядаюць як рэйтынг неспакою. Слова „сын“ з верыятнасцю 9,8 процэнта дае губіцы 2,32, хоця модэль сільна спадзявалася, што губіцы будуць значна меньшыя, а справжні шок даў бы гораздка большыя губіцы.

    Як ствараецца пакет

    Функцыя reshape(B*T) проста пазначае, што пакет складаецца з B секвэнцый, кожная з якіх дапамагае вылічыць губіцы для T пазыцый. Тады звідзе беруцца гэтыя B секвэнцый, і чыі ўсе яны аднаковай дужыны?

    Людзі частаўка паспяхваюць пра адну дэталь: последованасці прадзеныя не падкладаюцца і ўсе яны маюць абавесну дыяпазону кантэкста. Корпус з’едынаецца ў адны дзейні струмэн токенав, пры чым межы дакументаў пазначаюцца токенам <|endoftext|>, а вікна фіксаванай дыяпазону вырываюцца на адныя-другія пазычкі. Усе вікна выйшлі з аднаго неперывнага струмэна, таму жадна з іх не можа быць короткай, і падкладачых токенав не трэба.

    Этот алгорытм выконваець маршрут ад корпуса для трэніравання да пакетаў даных, а потым да показніка збытку. Яным спецыяльным токенам у цыкле ёсь <|endoftext|> (EOT), які служыць разлінювачам узамен падпісу і паказвае, дзе заканчваецца адна народная казка і пачынаецца наступная. Вікно можа перасягнуць такую межу і мець EOT у сваім складзе, і самэ гэта дапамагае модэлю зрозумець, што казка заканчылася і пачалася новая. Падпіс выкорыстоўваецца пазней, пад час тонкай наладкі, калі прыклады разніцацца за дужыною, і позыцыі з падпісамі маскуюцца ў расчытанні збытку; гэта іншая настройка, чым пакетаванне даных падчас прадзейсневага трэніравання.

    Збыткі — гэта другая сфера, дзе LLM адрасуецца ад ViT, і гэта разліка ўсё ж пасляўгодная. ViT выдаваў адна прыкметка і адны збытак на кожныя зображэння. Секвенція з 128 токенаў дае 128 збыткаў, якія усерэднююцца і ствараюць збытак для таго прыкладу (3,78 для першага прыкладу на рэчыце), а 32 збыткаў прыкладаў у пакеце усерэднююцца і ствараюць збытак пакета 3,92, які мінімізуецца за дапамой алгорытма backprop. За допамогою гэтых двух ціклав усерэднення адзін крок зніжвае сярэгу 32 на 128, тыяму 4 096 збыткаў на наступны токен, што дае значна больш сігналу за крок, чым адзін збытак на кожныя зображэння.

    Спагляданне спаду збыткаў

    Працягніце гэты збытак да оптымайзера і павторыце процэс. Гэта і ёсць справжнія працэс навчання на корпусе тэлаў.

    Графік паказвае значэння падтрымальных і перавярочных збыткаў працэсу навчання за эпохамі. Навчанне пачынаецца з 9,2, што адпавядае ln(8192) — збытку ад хаотычных спадзяванняў у слоўніку з 8 192 токенамі, а найкрашчы перавярочны збытак досягае 4,73. Крывыя застаюцца блізкімі, адстаячы адзін ад другога на апошнія 1,0, што значыць, што за меркаванням у вузкай сфэрэ мадэль генералізуе, а не проста запамячае. Сама мадэль мае невялікі размер.

    На графіку паў’язаны працэс навчання з колькасцю параметраў, якія можна навучыць. З 9 мільйонаў параметраў мадэль навчаецца за апошнія дзесяць хвілін на GPU ноутбука з 6 ГБ памяці. Адносна большая частка параметраў знаходзіцца ў шасці блоках трансфармера, а рэшта належыць табліцы эмбедынгаў і выходнай частцы. Навчанне складаецца ад паўтаральнай корэктывы гэтых 9 мільйонаў значэнняў, таму верыгатуты наступных токенав ставяць большы акцэнт на той токен, які дзе-небудзь раней выйшаў.

    У кодзе цыкл выглядае так, як і можна было спачатку чакаць, і мае той самы фармат, як у ViT: беруцца адзінаковыя прыклады вэлікасці кантэксту і вакна наступных токэнаў, адбываецца вычысленне для атрымання значэння сярэжнай пазыркі, градыенты ставяцца у нуль, адбываецца зворачная праця градыентаў і крок у напрамку падбору параметраў.

    for step in range(train_steps):
        xb, yb = get_batch(train_data)     # random (context, next-token) windows
        _, loss = model(xb, yb)            # forward: T predictions -> one mean loss
        opt.zero_grad(); loss.backward()   # backprop the loss into all 9M parameters
        opt.step()                         # nudge them downhill
    

    У повній версіі скрыпту додано графік зміні швідкосты навчання за формулою косінуса і механізм оцінкі.

    Што нового і што залішылося незміннім

    Параўнано з Vision Transformer у LLM додано лішэ незначні зміні: вікорыстовуецца ембеддынг токенів у вігляды табліці пошуку, каузальна маска і функція втрат для наступного токену на кожній позіціі, якая класіфікуе даныя за повнім словніковім запасом. Таксама враховуецца практычній аспект — токенайзер належыць до мадэлі, тому яго спеціалізована версія компенсуе додаткові зусілля. Функціі увагі, позіційні ембеддынгі, потык залішків, LayerNorm, блокі MLP і повній цыкл backpropagation і оптымізатор залішаюцца без змін.

    Рэальныя чысла адкрываюць внутрэнія механізмы: як каузальная маска дазволяе адзіну працэю прайсаў пасчытаць усі дужыні кантэкста разам, што прадае attn @ V (кожны рад выходных даных сумешчае вектары значэнняў, відныя для таго токэна), як пакеты для трэнавання ствараюцца па прынцыпе запакавання, а не дапылкавання, і як губернанс адной пазіцыі, класыфікацыя ў межах слоўніку, спаўнаецца з іншымі ў адзіны сярэдній значэнні.

    Вагі ўжо вылічаны, але трэнаваны модэль нічога не робіць пакуль не запускаецца. Далей йдзе працэс аналізу: цыкл генеравання, KV-кэш, які спрыяе шырокай швальносці, і што пішае маленькі модэль, калі яму даюць пачатак історыі.