Перейти до вмісту
9/30Розділ 9 з 30

Attention і transformer block, виведені із середнього

Почніть із найдешевшого підсумку context — середнього — виміряйте його провал і отримайте формулу attention з виправлення.

На цій сторінці

Ви приходите сюди з tokenizer із розділу 7, таблицею embedding з розділу 8 і відповідною ціллю: маючи tokens дотепер, поставити ймовірність на наступний.

Бракує середини. Щоб передбачити token tt, моделі потрібен один vector, який підсумовує все перед ним, а ніщо з уже побудованого такого vector не дає. Embedding token t1t-1 — не він: це біграмна модель, і вона не може знати, що речення почалося із запитання. Конкатенація всіх попередніх embeddings теж не підходить: їхня кількість змінюється на кожному кроці, а фіксована матриця ваг не може приймати вхід змінної довжини.

Отже: один vector фіксованого розміру, який підсумовує змінну кількість vectors. Це вся задача, а attention — те, що ви отримуєте, коли розв’язуєте її найлінивішим можливим способом, а потім ремонтуєте дві речі, які ламаються.

Відповідь, яку вже мала галузь, і чому ми її не будуємо

Посилання на розділ: Відповідь, яку вже мала галузь, і чому ми її не будуємо

З 1997-го приблизно до 2017-го підсумком був рекурентний стан: тримати vector h\mathbf{h} і оновлювати його на кожному token, ht=f(ht1,xt)\mathbf{h}_t = f(\mathbf{h}_{t-1}, \mathbf{x}_t). Фіксований розмір, змінний вхід, саме потрібна форма.

Він провалився трьома способами, і архітектура цього розділу відповідає на всі три. Backpropagation через TT кроків множить TT якобіани, тому gradient зникає або вибухає — це та сама хвороба, яку розділ 5 вимірював усередині одного вузла tanh\tanh. LSTM1 була спроєктована саме проти цього й розширила корисний діапазон із десятків кроків до сотень, не змінюючи факту, що інформація з token 5 доходить до token 500 лише якщо переживе 495 послідовних оновлень. Усе джерело мало вміститися в одному vector: у перекладі sequence-to-sequence2 encoder стискає вхід у свій фінальний стан. Bahdanau, Cho і Bengio назвали це вузьке місце й виправили його у 2014 році, за три роки до transformer, дозволивши decoder брати зважену суму всіх станів encoder з вагами, які він обчислював сам.3 Усе нижче — та сама ідея, застосована послідовністю до самої себе, з видаленою рекурентністю. І оновлення послідовне за конструкцією: ht\mathbf{h}_t потребує ht1\mathbf{h}_{t-1}, і GPU з десятьма тисячами ядер нічого не може з цим зробити. Архітектура, яка перемогла, не очевидно розумніша; це та, чий дорогий крок є множенням матриць.

Інший класичний індуктивний ухил, згортка — ковзати одним малим фільтром по всьому входу, щоб ознака, виявлена будь-де, виявлялася всюди, — ми тут теж не будуємо; він майже точно підходить для зображень і делегований курсу з комп’ютерного зору. Ні рекурентність, ні згортка після цієї сторінки не повертаються, тому жодна не отримує розділу: розділ 1 обіцяв, що пропуски будуть оголошені, а не замовчані.

Найочевидніша функція від змінної кількості vectors, що повертає один vector, — це середнє:

ct=1ti=1txi\mathbf{c}_t = \frac{1}{t}\sum_{i=1}^{t} \mathbf{x}_i

Будь-яка кількість входів, фіксований розмір виходу, диференційовність, безкоштовно. Таблиця embedding плюс це середнє плюс лінійний шар до словника — повна мовна модель у п’ятнадцять рядків. Вона ще й жахлива, а те, як саме вона жахлива, і є всім виведенням.

Корпус нижче — один мегабайт Shakespeare, 1 115 394 символи, через byte-level BPE tokenizer того типу, який будувався в розділі 7, зі словником 1024: 459 760 tokens по 2,43 символа кожен, поділ 90/10. Кожна модель має ширину 128, бачить 128 tokens і тренується 3000 кроків AdamW при 10310^{-3} з batch 64. Perplexity виміряна на відкладеній частині.4

modelparametersvalidation perplexity
лише поточний token, взагалі без context263,16859.71
плюс рівномірне середнє всього перед ним263,168248.07
плюс learned position embeddings279,552245.93
рівномірне середнє додане до token замість заміни ним263,16860.45

Прочитайте другий рядок двічі. Усереднення context не допомагає трохи; воно робить модель у чотири рази гіршою, ніж повне ігнорування context. Є дві причини, обидві доводяться, а не просто спостерігаються емпірично.

Середнє не бачить порядку. Додавання комутативне, тому перемішування window лишає підсумок незмінним — не приблизно:

order.pyPYTHON
A = torch.tril(torch.ones(T, T))
A = A / A.sum(1, keepdim=True)          # rows of the averaging matrix
y = x[torch.randperm(T)]                # the same tokens, shuffled
print((A[-1] @ x - A[-1] @ y).abs().max().item())
TEXT
2.9802322387695312e-08

Шум floating-point у перевпорядкованій сумі: два підсумки — той самий vector. Модель, чий єдиний погляд на context є середнім, не може відрізнити the dog bit the man від the man bit the dog. Третій рядок доводить, що це не виправляється додаванням позицій до входів — learned position embedding на кожному token перед усередненням дав 2,14 пункта з 188. Позиції входять у суму, і сума їх забуває.

І середнє топить теперішнє. На позиції 100 поточний token становить одну соту підсумку. У цього є дешеве виправлення, яке ви вже маєте: зберегти token і додати до нього підсумок — residual connection із розділу 6, і четвертий рядок показує, що вона робить. Коли розбавлення виправлене, рівномірне середнє не додає нічого: 60.45 проти baseline 59.71. Кожен token там є, з однаковою вагою, а однакова вага — те саме, що відсутність інформації.

Проблема не в усередненні. Вона у вагах.

Середнє — це множення матриць, а маска — це softmax

Посилання на розділ: Середнє — це множення матриць, а маска — це softmax

Усереднення по зростаючому префіксу виглядає як цикл. Насправді це одне множення на нижньотрикутну матрицю, рядки якої сумуються до одиниці, — і також, точно, softmax:

mechanics.pyPYTHON
loop = torch.stack([x[:t + 1].mean(0) for t in range(T)])   # the obvious version

A = torch.tril(torch.ones(T, T))
A = A / A.sum(1, keepdim=True)
mat = A @ x                                                  # the same thing

S = torch.zeros(T, T).masked_fill(torch.tril(torch.ones(T, T)) == 0, float("-inf"))
soft = F.softmax(S, dim=-1) @ x                              # and the same thing again
TEXT
loop vs matmul   max |diff| = 5.960464477539063e-08
loop vs softmax  max |diff| = 5.960464477539063e-08

the averaging matrix A (rows sum to 1, upper triangle is zero):
  1.000 0.000 0.000 0.000 0.000 0.000
  0.500 0.500 0.000 0.000 0.000 0.000
  0.333 0.333 0.333 0.000 0.000 0.000
  0.250 0.250 0.250 0.250 0.000 0.000
  0.200 0.200 0.200 0.200 0.200 0.000
  0.167 0.167 0.167 0.167 0.167 0.167

Три названі компоненти transformer вже на екрані. Трикутник — це causal mask, продиктована ціллю: якби позиція tt могла бачити позицію t+1t{+}1, відповідь була б у вході — той витік, який розділ 6 просив вас перевіряти, тільки всередині архітектури. Softmax — це спосіб реалізувати маску: встановлення заборонених елементів у -\infty відправляє їх точно в нуль і нормалізує те, що лишилося, тож маскування й нормалізація є однією операцією. (Використовуйте -\infty, а не -1e9: це значення, яке маскування означає, воно переживає приведення до float16 як -\infty і позбавляє вас рішення, чи константа, яку ви вибрали, достатньо велика для діапазону, в якому ви опинилися, — це скринька floating-point із розділу 2, що ставить питання, на яке вам не потрібно відповідати.) А scores — це вільний параметр. Рівномірне середнє — те, що ви отримуєте, коли кожен дозволений score є тим самим числом; поставте там будь-які числа, і softmax перетворить їх на допустимі ваги.

Решта цього розділу — одне питання: звідки беруться ці числа?

Вони не можуть бути простими параметрами. Learned матриця T×TT \times T була б однаковою для кожного речення — вона могла б закодувати «дивись на чотири tokens назад», але ніколи «дивись на іменник, до якого належить цей займенник». Вага, що зв’язує позицію tt із позицією ii, має залежати від того, що стоїть в обох позиціях, бо релевантність — це відношення, а не властивість: слово it не є релевантним саме по собі, воно релевантне до чогось.

Найдешевша функція двох vectors, що повертає число, — dot product із розділу 1. Оцініть позицію ii для позиції tt як xtxi\mathbf{x}_t \cdot \mathbf{x}_i, і механізм працює — погано, двома способами, що змушують з’явитися все інше. Dot product vector із самим собою — це квадрат його норми, тож кожен token здебільшого звертав би attention на себе. І відношення було б симетричним: якщо it сильно звертає attention на animal, тоді animal сильно звертає attention на it, що неправда для мови, де прикметнику його іменник потрібен значно більше, ніж іменнику прикметник.

Тому дайте кожному token дві ролі як дві learned лінійні карти від нього: що ця позиція шукає, qt=Wqxt\mathbf{q}_t = W_q\mathbf{x}_t, query; і що вона пропонує для знаходження, ki=Wkxi\mathbf{k}_i = W_k\mathbf{x}_i, key. Score qtki\mathbf{q}_t \cdot \mathbf{k}_i, і симетрія зникає, бо WqWkW_q \neq W_k: token може рекламувати одне, а шукати інше.

Одна річ усе ще неправильна. Зважена сума була по самих xi\mathbf{x}_i, що змушує те, що копіюється, бути тим самим, що зіставляється. Зіставленню потрібні ознаки, які ідентифікують token; копіюванню потрібні ознаки, корисні нижче за потоком. Тому вивчаємо третю карту, vi=Wvxi\mathbf{v}_i = W_v\mathbf{x}_i, value, і сумуємо їх.

Формула тепер — бухгалтерія:

Attention(Q,K,V)=softmax ⁣(QKdk+M)V\mathrm{Attention}(Q, K, V) = \mathrm{softmax}\!\left(\frac{QK^\top}{\sqrt{d_k}} + M\right)V

де MM — causal mask, нуль на діагоналі й нижче та -\infty вище. У коді це тридцять рядків, із яких двадцять — форми:

attention.pyPYTHON
class Head(nn.Module):
    """One head of causal self-attention."""

    def __init__(self, d_model, d_head, block):
        super().__init__()
        self.q = nn.Linear(d_model, d_head, bias=False)      
        self.k = nn.Linear(d_model, d_head, bias=False)      
        self.v = nn.Linear(d_model, d_head, bias=False)      
        self.d_head = d_head
        self.register_buffer("mask", torch.tril(torch.ones(block, block)).bool())

    def forward(self, x):
        T = x.shape[1]
        q, k, v = self.q(x), self.k(x), self.v(x)
        s = q @ k.transpose(-2, -1) / math.sqrt(self.d_head)          
        s = s.masked_fill(~self.mask[:T, :T], float("-inf"))          
        w = F.softmax(s, dim=-1)                                      
        return w @ v                                                  

Score, mask, normalise, mix. Усе інше — projection.

Ділення на квадратний корінь і від чого воно захищає

Посилання на розділ: Ділення на квадратний корінь і від чого воно захищає

Майже кожне пояснення dk\sqrt{d_k} каже «щоб softmax не насичувався», що правда й нічого не пояснює. Аргумент — два рядки дисперсії з розділу 2. Якщо елементи q\mathbf{q} і k\mathbf{k} незалежні, із нульовим середнім і дисперсією один, кожен добуток qjkjq_j k_j має дисперсію один, а дисперсії незалежних величин додаються:

Var(qk)=j=1dkVar(qjkj)=dk\mathrm{Var}(\mathbf{q}\cdot\mathbf{k}) = \sum_{j=1}^{d_k}\mathrm{Var}(q_j k_j) = d_k

Отже, scores мають стандартне відхилення dk\sqrt{d_k}. Виміряно на двадцяти тисячах випадкових пар:

TEXT
     d     Var(q.k)         std   sqrt(d)
     4        3.975       1.994     2.000
    16       16.071       4.009     4.000
    64       64.249       8.016     8.000
   256      253.065      15.908    16.000
  1024     1015.562      31.868    32.000

Чому це важливо: softmax чутливий до масштабу так, як лінійний шар — ні. Подвоєння входу лінійного шару подвоює його вихід; множення scores на десять перед softmax перетворює м’яку суміш на жорсткий вибір. Один рядок із 64 scores, із діленням і без:

dkd_klargest weight, undividedentropyeffective tokenslargest weight, dividedentropyeffective tokens
40.2052.94419.00.0813.75842.9
160.4381.6925.40.0753.84946.9
640.4890.8742.40.0853.67339.4
2560.99990.00071.00.1433.54734.7
10241.00000.00001.00.1323.64438.3

«Effective tokens» — це експонента entropy: скільки позицій рядок справді усереднює. Без ділення, при dk=256d_k = 256, щойно ініціалізована head звертає attention рівно на один token із 64, вибраний нічим, крім випадкового жереба.

Це погано в forward і ще гірше в backward, у формі, яку розділ 5 уже виміряв на tanh\tanh. Softmax, що зафіксувався на одному елементі, має майже нульову похідну: діагональ його якобіана — wi(1wi)w_i(1-w_i), нуль на обох кінцях. На двох тисячах випадкових рядків:

dkd_kiwi(1wi)\sum_i w_i(1-w_i) undivideddividedrows saturated (largest weight above 0.99)
40.84270.95680.2 % → 0.0 %
640.29400.960917.9 % → 0.0 %
2560.14060.960949.1 % → 0.0 %
10240.06810.961170.4 % → 0.0 %

При dk=1024d_k = 1024 сім рядків із десяти заморожені ще до початку навчання, а head, що стартує замороженою, не може навчитися, куди дивитися. З діленням ця величина рівна 0.96 на кожній ширині, і нічого не насичується.

Тепер частина, яку ніхто не публікує: чи змінює це фінальну perplexity? Видаліть ділення й навчіть, на чотирьох ширинах head:

head widthundivideddivided by dk\sqrt{d_k}divided by dkd_k
four heads, dk=32d_k = 3237.2938.0737.89
one head, dk=128d_k = 12848.5146.1045.99
one head, dk=256d_k = 25665.3747.53
one head, dk=512d_k = 51267.0649.15
one head, dk=1024d_k = 102476.6959.17

Перші два рядки походять із бюджету 3000 кроків вище; останні три — коротший запуск: 1500 кроків, batch 32, одна head, без нормалізації перед projections, з обома варіантами в ідентичних налаштуваннях.

При dk=32d_k = 32 ділення не варте нічого, і запуск без нього трохи попереду. Це не ліцензія його викидати, бо при 256 воно варте 18 пунктів perplexity, а при 1024 — 17. Механізм видно в самих scores:

dkd_kscore std at initafter 1500 steps, undividedafter 1500 steps, dividedrows saturated, undivideddivided
25610.49121.672.1391.9 %0.8 %
51215.13836.852.6698.7 %1.3 %
102421.155147.463.4499.9 %16.5 %

Head без ділення не відновлюється. Вона тікає в рознос: стандартне відхилення її scores зростає з 21 при ініціалізації до 5147, entropy attention падає до нуля, і 99,9 % рядків кладуть понад 0.99 своєї ваги на один token. Коли head стає жорстким selector, її gradient майже нульовий і ніщо не тягне її назад, тож колапс стабільний. Head із діленням після того самого навчання сидить на стандартному відхиленні scores 3.44, тобто це м’яка суміш, яку ще можна змінювати.

Vaswani et al. кажуть саме це й не більше — вони підозрюють, що добутки «стають великими за модулем для великих значень dkd_k», і ділять.5 Слово великими несе навантаження, і таблиці кажуть, де починається велике: нічого при 32, усе при 256.

Більше ніж одна думка і дві третини, про які ніхто не говорить

Посилання на розділ: Більше ніж одна думка і дві третини, про які ніхто не говорить

Одна head — це один рядок softmax на позицію, тож вона тримає одну відповідь на «що тут релевантне». Передбачити слово після the у the animal that crossed the wet street потребує синтаксичного слота, підмета й попереднього token одночасно, а один розподіл імовірностей не може бути сконцентрований у трьох місцях. Тому запускаємо кілька heads паралельно, кожну ширини dmodel/hd_{\text{model}}/h, конкатенуємо й змішуємо ще однією матрицею WoW_o: ви поділили ширину, а не додали до неї.

Attention також робить рівно одну річ — переносить інформацію між позиціями. Кожна операція в коді вище лінійна вздовж осі ознак, а розділ 5 довів, чим є стек лінійних карт. Тому кожен block також містить малий MLP, застосований до кожної позиції незалежно, який розширює ширину вчетверо й повертає назад, із GELU посередині. Поділ праці варто запам’ятати: attention змішує між позиціями, feed-forward network обчислює всередині позиції.

Повна драбина, де кожен рядок додає одну частину до рядка над ним:

modelparametersvalidation perplexity
uniform average, added279,55260.45
one attention head, replacing the token328,70455.47
one attention head, added328,70446.10
four heads instead of one345,21643.21
plus the feed-forward network476,92839.87
plus LayerNorm — the complete block477,69638.07

Learned ваги перемагають рівномірні на 14 пунктів perplexity — це весь аргумент розділу в одному рядку. Чотири heads купують ще 3 за 16 512 додаткових параметрів. І та сама head варта на 9 пунктів більше, коли вона додається, ніж коли замінює: attention приносить інформацію всередину, а не вирішує, чим є позиція.

Тепер де насправді сидять параметри — це дивує людей, які бачили лише діаграму:

widthheadsattentionfeed-forwardtotal per block
128465,664 (33.2 %)131,712 (66.6 %)197,888
768122,360,064 (33.3 %)4,722,432 (66.6 %)7,085,568
40963267,112,960 (33.3 %)134,238,208 (66.7 %)201,367,552

Дві третини кожного transformer block — це feed-forward network, на кожному масштабі, бо attention має чотири матриці d×dd \times d, а MLP має еквівалент восьми. Що б модель не знала, більшість параметрів, які це тримають, перебувають у per-position MLP.

LayerNorm був побудований і виміряний у розділі 6, і цей розділ використовує його таким, яким він там залишився; residual connections були названі й абльовані там, і тут вони вбудовані. Рядки «added, not replacing» вище — це residual connections, варті 188 пунктів perplexity для середнього й 9 для однієї head. LayerNorm7 нормалізує кожен приклад по його ознаках, а розділ 6 дав причини, чому саме він, а не BatchNorm, вижив тут — відсутність залежності від batch, відсутність running statistics, однакова поведінка в training та inference, байдужість до довжини послідовності, — і кожна з них стає вимогою, коли ви генеруєте один token за раз для одного користувача, куди зрештою приходить розділ 13. Він коштує 768 параметрів і купує 1,8 пункта perplexity.

block.pyPYTHON
class Block(nn.Module):
    def forward(self, x):
        x = x + self.att(self.ln1(x))     
        x = x + self.ff(self.ln2(x))      
        return x

Подивіться, де стоїть нормалізація: на вході кожного sub-layer, а residual path від входу до виходу ніколи не нормалізується. Це pre-norm. Стаття 2017 року робить навпаки, x = LayerNorm(x + Att(x))post-norm, що ставить LayerNorm на сам residual path.

Xiong et al. пояснили різницю через gradient при ініціалізації, який у post-norm мережі погано масштабується з глибиною — причина, чому оригінальному transformer взагалі потрібен був learning-rate warmup, щоб навчатися.8 Дванадцять blocks, 1000 кроків, learning rate 3×1033 \times 10^{-3}:

TEXT
gradient norm per block at initialisation, before any step
  pre-norm    block 1 0.0498 ... block 12 0.0657   ratio last/first  1.32
  post-norm   block 1 0.0977 ... block 12 0.1613   ratio last/first  1.65

  pre-norm,  no warmup          perplexity   37.82
  pre-norm,  200-step warmup    perplexity   37.62
  post-norm, no warmup          perplexity  308.05
  post-norm, 200-step warmup    perplexity   37.88

Post-norm без warmup у вісім разів гірший, а post-norm із warmup точно збігається з pre-norm. Warmup тут не загальна добра практика; це латка для конкретного розташування нормалізації, і перенесення LayerNorm прибирає потребу в ній. Саме тому практично кожна модель після 2019 року є pre-norm, а діаграму 2017 року слід читати як історію, а не як специфікацію.

Видаліть position embeddings, і модель усе ще тренується; вона просто не може сказати, де що є, і це симетрія, а не провал навчання. Ніщо в score attention не згадує самі tt або ii, тому перестановка входу переставляє вихід: self-attention є permutation-equivariant. Це сліпота середнього до порядку в кращому маскуванні — causal mask відновлює частину порядку, бо кожна позиція бачить інший префікс, але всередині префікса всі порядки однакові.

Чотири способи ін’єктувати позицію, навчені на 64-token windows і оцінені на 64, 128 і 256 — за межами будь-якої довжини, яку вони бачили:

positionsperplexity at 64at 128at 256
none at all48.7952.6357.52
learned absolute embeddings38.63108.47181.94
fixed sinusoids42.9695.26152.25
RoPE44.1250.5284.84
ALiBi44.9543.5142.49

Learned absolute embeddings — один vector на позицію, доданий до token, — перемагають на навченій довжині, а потім падають зі скелі, бо позиція 100 ніколи не була в batch, і її embedding усе ще той випадковий vector, з якого вона почалася. Sinusoids, початковий вибір, обчислюються, а не вивчаються, із синусів і косинусів на геометрично рознесених частотах; стаття 2017 року сподівалася, що це екстраполюватиме, а таблиця каже, що ні — функція визначена в позиції 200, але модель ніколи не навчилася читати її там. RoPE9 нічого не додає, а натомість обертає query і key на кут, пропорційний позиції, у двовимірних зрізах; оскільки однаковий поворот обох сторін dot product лишає його незмінним, score зрештою залежить тільки від tit - i, тож позиція безкоштовно стає відносною, і немає таблиці, яка може закінчитися. Він деградує, але деградує. ALiBi10 — найпростіший і найдивніший результат тут: лінійний штраф на score, пропорційний відстані, з різним нахилом для кожної head. Його perplexity покращується, коли window зростає за межі навчальної довжини, з 44.95 до 42.49, бо штраф визначений на будь-якій відстані, і кожна head продовжує робити те, чого її навчали.

Урок живе довше за таблицю: архітектура, яка не може представити щось, — інша проблема, ніж та, що просто ніколи не вивчила цей діапазон, і кусає саме друга. Це також механіка за кожним оголошенням «ми розширили context до 128K» — майже завжди це re-scaling rotary encoding, і саме тому розділ 16 каже, що межа context рухається, а не зникає.

Dropout успадкований так само: він з’являється на вагах attention після softmax, на виході кожного sub-layer перед residual addition і на сумі embeddings, роблячи саме те, що описував розділ 6. У великих pretraining-запусках його часто ставлять у нуль, бо модель, яка бачить кожен token один раз, не в позиції overfit.

Два tensors у шарі мають форму n×nn \times n, де nn — кількість tokens: scores і ваги після softmax. Усе інше — кожна projection, весь MLP — лінійне за nn.

Один attention layer, ширина 512, 8 heads, batch один, float32, на GPU ноутбука. Читайте дві мілісекундні колонки лише за їхніми співвідношеннями: це wall clock на 8 GB ноутбучній карті, яка throttles із 1 785 MHz до менш ніж 300 MHz, коли нагрівається, тому холодний запуск того самого коду повертається у сім-десять разів швидше, а зайнятий — ще повільніше. Колонки megabyte — це byte counts allocator, і вони не рухаються.

TEXT
  tokens   ms total    ms x4   ms projections   attn matrix MB    peak MB    MB x4
     128      2.246        -            1.324              0.5       14.6        -
     256      2.855     1.27            2.113              2.0       19.2     1.31
     512      5.761     2.02            3.105              8.0       34.4     1.79
    1024     16.414     2.85            4.008             32.0       89.1     2.59
    2048     51.573     3.14            9.989            128.0      296.1     3.32
    4096    225.432     4.37           20.176            512.0     1100.1     3.72
    8192    832.838     3.69           40.106           2048.0     4300.1     3.91
   16384   OUT OF MEMORY                                 8192.0

fitted exponent (log-log slope, last four rows):  time ~ n^1.91   memory ~ n^1.87

Колонки x4 — це відношення до рядка вище, і подвоєння nn сходиться рівно до 4 і для часу, і для пам’яті — 3.91 на останньому кроці проти теоретичних 4. Колонка projections — контроль: 4.0 ms на 1024 tokens до 40.1 ms на 8192, фактор десять для фактора вісім. Лінійно, як і обіцяно.

Потім останній рядок. Один attention layer, одна послідовність, без моделі навколо нього, вичерпує пам’ять на 8 GB GPU при 16 384 tokens — сама матриця scores була б 8 GB: 8 heads × 16 384 × 16 384 × 4 bytes. Не модель; один проміжний tensor в одному шарі.

Це фізичний факт під трьома наступними розділами. Саме тому context window взагалі має межу, яку розділ 16 перетворює на ціну. Саме тому існує FlashAttention, що обчислює той самий результат плитками, ніколи не зберігаючи матрицю, — оптимізація пам’яті до того, як вона є оптимізацією швидкості.11 І це арифметика за ціною довгого prompt, яку розділ 24 платить в agent loop, — окрема справа від іншого висновку того розділу: що модель ще й гірше використовує довгий context, що він вимірює й відмовляється звинувачувати в цій формулі.

Показати подробиці

Два варіанти зменшення cache, названі тут і оплачені в розділі 13.

Generation кешує keys і values уже оброблених tokens — один key і один value на token, на head на layer. Multi-query attention12 тримає hh query projections, але одну key і value projection, спільну для всіх heads, ділячи цей cache на hh. Grouped-query attention13 інтерполює: heads групуються, кожна група ділить один key і value, тож g=hg = h — звичайний attention, а g=1g = 1 — multi-query. Майже кожна відкрита модель із 2023 року використовує це з 4 або 8 групами. Жодне не існує заради якості; обидва існують заради розміру цього cache, і розділ 13 робить арифметику, яка перетворює це на «яка модель вміщується у ваш GPU».

Стаття 2017 року описує encoder-decoder: один стек читає джерело з unmasked attention, другий генерує ціль causal, а посередині третій вид attention, де queries decoder зустрічаються з keys encoder. Це правильно для перекладу, де вхід і вихід — дві послідовності.

Перемогла decoder-only половина — один стек, causal всюди, вхід і вихід в одній послідовності, — і причина не в елегантності. «Передбачити наступний token» працює на будь-якому тексті, тож training set — інтернет, а не паралельний корпус, і все стає одним завданням: переклад — це документ, що містить джерело, потім ціль; питання та відповідь — документ; розмова з tool call посередині — документ. Розділ 11 про те, як виготовляють останнє. Encoders не зникли — один бачить увесь вхід одразу, що й потрібно, коли задача — представити текст, а не продовжити його; тому retrieval embeddings у розділі 19 походять з encoders, а не з моделі, яка чатиться.

Коли block визначений, розмір моделі — арифметика. На block, із шириною dd і чотириразовим розширенням: 4d2+4d4d^2 + 4d для Wq,Wk,Wv,WoW_q, W_k, W_v, W_o з biases на всіх чотирьох, як у GPT-2, — таблиця вище лишає bias вимкненим на трьох із них, тому на 2 304 менше на block при d=768d = 768; 8d2+5d8d^2 + 5d для MLP; 4d4d для двох LayerNorms — 12d2+13d12d^2 + 13d, плюс таблиця tokens V×dV \times d і, для absolute positions, nctx×dn_{\text{ctx}} \times d. Для форми GPT-2 small — d=768d = 768, 12 blocks, словник 50 257, context 1024, output layer ділить ваги з embedding:

TEXT
  token embeddings     50,257 x 768 = 38,597,376
  position embeddings   1,024 x 768 =    786,432
  one block                             7,087,872
  12 blocks                            85,054,464
  final LayerNorm         2 x 768 =        1,536
  total (weights tied)                124,439,808

Це опублікований розмір цієї моделі. Формула не наближення; це модель. Також зауважте, що майже третина малої моделі — це embedding table, тому розмір словника є архітектурним рішенням, а не preprocessing — trade-off, який підготував розділ 7.

Perplexity — число про корпус. Що робить одна head — інше питання, і модель, навчена на мегабайті Shakespeare, — неправильний інструмент для нього: чесно про attention map моделі на 500 000 параметрів можна сказати, що вона здебільшого неінтерпретована. Тож: мова, де питання має правильну відповідь.

Класична ілюстрація — the animal did not cross the street because it was too tired, де it — це animal, на противагу …because it was too wet, де одне слово переносить референт на street. Це Winograd schemas14 — пари речень, ідентичні, крім одного слова, де це слово вирішує, до чого належить займенник.

Вони також розв’язні шахрайством, і це частина, яку tutorials пропускають. Якщо два кандидати — тварина й місце, tired і wet ідентифікують референт за категорією, і модель, що знає лише, які слова присутні, відповідає правильно, нічого не знаючи про порядок. Виміряно на цій версії задачі, з відкладеними парами animal/place:

TEXT
uniform causal average           held-out referent accuracy 100.0 %
one transformer block            held-out referent accuracy  91.7 %

Bag of words перемагає transformer. Будь-яка демонстрація, побудована на цьому реченні, нічого не доводить про attention.

Тож закриймо діру: беремо обох кандидатів з одного пулу шістнадцяти іменників, кожен із яких може з’явитися в будь-якому слоті, і ділимо прикметники за роллю, а не категорією — чотири роблять it тим, хто перетинає (tired, scared, slow, weak), чотири — тим, що перетинають (wet, wide, busy, steep).

TEXT
the {x} did not cross the {y} because it was too {adj} , so the {ref} waited .

Навчайте як звичайний next-token predictor, оцінюйте одну позицію — слово після so the — і будуйте held-out set із пар іменників, чий зворотний порядок був у training, тож усе, що знає, які два іменники присутні, але не знає, який був першим, має відповісти навпаки.

modelparametersheld-outnames the other noun
current token only5,7965.2 %5.2 %
uniform causal average5,79627.9 %50.0 %
one head of learned attention18,08435.4 %64.6 %
four heads22,24475.0 %15.6 %
one transformer block55,71692.7 %4.2 %
two transformer blocks105,508100.0 %0.0 %

Випадковий вибір серед двох присутніх іменників — 50 %. Рівномірне середнє доходить до 27.9 % і відповідає неправильним іменником із пари рівно в половині випадків — підпис чогось, що знає, які слова є, і нічого про їхній порядок, як shuffle test передбачив три секції тому.

Тепер карта: attention у позиції, яка має назвати референт, усереднений по чотирьох heads кожного block, для двох речень, що відрізняються одним словом. Рівномірне середнє поставило б 0.067 на кожен із п’ятнадцяти видимих tokens.

TEXT
the animal did not cross the street because it was too tired , so the animal waited .
  blk 1  the:0.00 animal:0.70 did:0.00 not:0.00 cross:0.00 the:0.00 street:0.06
         because:0.00 it:0.00 was:0.00 too:0.00 tired:0.00 ,:0.05 so:0.00 the:0.19
  blk 2  the:0.00 animal:0.00 did:0.00 not:0.00 cross:0.00 the:0.00 street:0.00
         because:0.00 it:0.00 was:0.00 too:0.00 tired:1.00 ,:0.00 so:0.00 the:0.00

the animal did not cross the street because it was too wet , so the street waited .
  blk 1  the:0.00 animal:0.70 did:0.00 not:0.00 cross:0.00 the:0.00 street:0.06
         because:0.00 it:0.00 was:0.00 too:0.00   wet:0.00 ,:0.05 so:0.00 the:0.19
  blk 2  the:0.00 animal:0.00 did:0.00 not:0.00 cross:0.03 the:0.00 street:0.49
         because:0.00 it:0.00 was:0.00 too:0.20   wet:0.03 ,:0.00 so:0.00 the:0.25

Block 1 ідентичний в обох реченнях — 0.70 на першому іменнику, яким би не був прикметник. Це не провал, а доказ: у першому layer query у позиції є функцією власного token та індексу цієї позиції, а the у позиції 14 — той самий token в обох реченнях. Head першого layer не може залежати від слова, якого вона ще не витягла. Тому block 1 робить єдине корисне, що доступне, і тягне перший іменник уперед.

Block 2 — місце, де речення розходяться, і той самий рядок на всіх восьми прикметниках показує правило, яке знайшла модель:

adjectiveblock 2 on animalon streeton the adjectiveanswer
tired, scared, slow, weak0.0000.0001.000animal
wet, wide, busy, steep0.0000.4910.00–0.03street

Для прикметника crosser другий block витрачає всю вагу на прикметник, бо відповідь уже в residual stream — block 1 її туди поклав, — і все, що потрібно, це підтвердження. Для прикметника crossed він іде й витягає інший іменник. Це двострибковий circuit: одна head переносить кандидата вперед, head у пізнішому layer читає token, який вирішує, чи залишити його. Композиція між layers — механізм, і саме тому один block досяг 92.7 %, а два — 100 %.

Це також форма найкраще задокументованого circuit у реальних моделях. Induction heads — previous-token head, що живить head у наступному layer, яка завершує патерн [A][B] … [A] → [B], — це те, що робота Anthropic з interpretability ідентифікує за великою частиною in-context learning, і вони формуються в упізнаваний момент під час pretraining. Цей розділ не намагається виконати такий аналіз: він делегований, із двома статтями в джерелах, бо читання circuits із реальної моделі — дослідницька галузь, а не секція.

Нарешті, реалізація. Тридцять рядків вище, з вагами, скопійованими з власних ваг PyTorch:

TEXT
ours vs nn.MultiheadAttention           max |diff| = 1.7881393432617188e-07
ours vs F.scaled_dot_product_attention  max |diff| = 1.7881393432617188e-07

1.8×1071.8 \times 10^{-7} на виходах, чия середня величина 0.159: та сама арифметика в іншому порядку, з точністю float32.

У вас є архітектура, з якої побудована кожна модель у решті курсу, і вона менша за свою репутацію: зважене середнє, чиї ваги learned, per-position MLP, що тримає дві третини параметрів, дві нормалізації й два додавання, складені в стек.

Чого у вас немає — це моделі, яка щось знає, і саме стекування цього не виправить. Два blocks на цьому корпусі досягають training perplexity 14.49 і validation perplexity 40.57 проти 18.77 і 38.07 для одного block — більше capacity, краще на тому, що вона бачила, гірше на тому, чого не бачила; це таблиця розділу 6 із transformer усередині. Відстань між цією моделлю і тими, з якими говорять розділи 14–30, не архітектурна. Це той самий block, повторений більше разів, на незрівнянно більшій кількості тексту.

А отже, це бухгалтерська задача, і бухгалтерія дивніша, ніж здається. Скільки тексту, і звідки хтось його бере? Скільки арифметики, і як оцінити її до того, як витрачені гроші? За фіксованого бюджету краще зробити модель більшою чи показати їй більше даних — і чи є правильна відповідь, або лише мода? Розділ 10 відповідає на всі три вимірюванням і ставить ціну на найдешевшу корисну форму питання: скільки сьогодні коштує навчити модель на кшталт GPT-2 з нуля?


Три пояснення цього матеріалу кращі за це у своїх завданнях, і цей розділ написаний, щоб читатися поруч із ними. The Illustrated Transformer Jay Alammar — найкраща картина потоку даних із будь-коли намальованих. The Annotated Transformer Harvard NLP — це стаття 2017 року з робочим кодом, вставленим рядок за рядком. Let's build GPT: from scratch, in code, spelled out Andrej Karpathy будує ту саму модель наживо за дві години, а драбина ablations вище — той самий хребет, виміряний на іншому корпусі. Для питання interpretability, якого цей розділ лише торкається, первинні джерела — Elhage et al., A Mathematical Framework for Transformer Circuits (2021) і Olsson et al., In-context Learning and Induction Heads (2022), обидва від групи interpretability Anthropic.

  1. Hochreiter, S. and Schmidhuber, J. Long Short-Term Memory. Neural Computation 9(8), pp. 1735–1780 (1997).

  2. Sutskever, I., Vinyals, O. and Le, Q. V. Sequence to Sequence Learning with Neural Networks. arXiv:1409.3215 (2014). Encoder-decoder, чий єдиний context vector є вузьким місцем.

  3. Bahdanau, D., Cho, K. and Bengio, Y. Neural Machine Translation by Jointly Learning to Align and Translate. arXiv:1409.0473 (2014). Attention, за три роки до transformer.

  4. Perplexity — це експонента середньої cross-entropy на token, з розділу 8. Кожне число тут використовує той самий tokenizer і той самий validation split, що є єдиною умовою, за якої дві perplexities взагалі можна порівнювати.

  5. Vaswani, A., Shazeer, N., Parmar, N., Uszkoreit, J., Jones, L., Gomez, A. N., Kaiser, Ł. and Polosukhin, I. Attention Is All You Need. arXiv:1706.03762 (2017). Section 3.2.1 — це одне речення про dk\sqrt{d_k}, яке цей розділ витрачає секцію, щоб виміряти.

  6. Shazeer, N., Mirhoseini, A., Maziarz, K., Davis, A., Le, Q., Hinton, G. and Dean, J. Outrageously Large Neural Networks: The Sparsely-Gated Mixture-of-Experts Layer. arXiv:1701.06538 (2017).

  7. Ba, J. L., Kiros, J. R. and Hinton, G. E. Layer Normalization. arXiv:1607.06450 (2016). Введено й виміряно в розділі 6; тут використовується без змін.

  8. Xiong, R., Yang, Y., He, D., Zheng, K., Zheng, S., Xing, C., Zhang, H., Lan, Y., Wang, L. and Liu, T.-Y. On Layer Normalization in the Transformer Architecture. arXiv:2002.04745 (2020). Gradient analysis за pre-norm і аргумент, що warmup — симптом.

  9. Su, J., Lu, Y., Pan, S., Murtadha, A., Wen, B. and Liu, Y. RoFormer: Enhanced Transformer with Rotary Position Embedding. arXiv:2104.09864 (2021).

  10. Press, O., Smith, N. A. and Lewis, M. Train Short, Test Long: Attention with Linear Biases Enables Input Length Extrapolation. arXiv:2108.12409 (2021). Результат екстраполяції, відтворений вище.

  11. Dao, T., Fu, D. Y., Ermon, S., Rudra, A. and Ré, C. FlashAttention: Fast and Memory-Efficient Exact Attention with IO-Awareness. arXiv:2205.14135 (2022).

  12. Shazeer, N. Fast Transformer Decoding: One Write-Head is All You Need. arXiv:1911.02150 (2019).

  13. Ainslie, J., Lee-Thorp, J., de Jong, M., Zemlyanskiy, Y., Lebrón, F. and Sanghai, S. GQA: Training Generalized Multi-Query Transformer Models from Multi-Head Checkpoints. arXiv:2305.13245 (2023).

  14. Levesque, H. J., Davis, E. and Morgenstern, L. The Winograd Schema Challenge. KR (2012). Конструкція за реченням animal / street, яке використовує кожен tutorial про attention.

Готові довірити вибір моделі LIA?

Створюйте з усіма моделями ШІ в одному місці — почніть безкоштовно вже сьогодні.