Перейти к содержимому

Feed-forward сеть и активации

Второй подслой каждого блока декодера — feed-forward сеть (FFN, иногда MLP): два-три линейных слоя с нелинейностью между ними. Она проще attention, но в ней сосредоточено около двух третей параметров блока. За шесть лет от GPT-1 до Gemma FFN менялась сильнее всех остальных частей: ReLU → GELU → gated-варианты SwiGLU и GeGLU, а скрытый размер из «всегда 4d4d» стал отдельным гиперпараметром.

  • Что делает FFN в блоке и почему её называют памятью «ключ–значение».
  • Классический FFN Vaswani: формулу, размеры матриц и число параметров 8d2+5d8d^2 + 5d.
  • Активации ReLU, GELU (точную и tanh-аппроксимацию — откуда константы 2/π\sqrt{2/\pi} и 0.0447150.044715 и какова её ошибка), SiLU/Swish.
  • Семейство GLU: SwiGLU и GeGLU, три матрицы вместо двух и роль гейта.
  • Как выбирают скрытый размер: 23⋅4d\tfrac{2}{3} \cdot 4d с округлением у LLaMA, 3.5d3.5d у Mistral, 8d8d у Gemma.
  • Как всё это реализовано в FeedForward, SwiGLU, GeGLU, GELU, SiLU.

Attention — единственное место блока, где токены обмениваются информацией: выход позиции tt — смесь значений других позиций. FFN, наоборот, применяется к каждой позиции отдельно и одинаково — в статье трансформера она так и называется: position-wise feed-forward network (Vaswani et al., 2017, разд. 3.3):

FFN(X)t=f(xt),t=0,…,T−1\mathrm{FFN}(X)_t = f(\mathbf{x}_t), \qquad t = 0, \dots, T-1

где:

  • X∈RT×dX \in \mathbb{R}^{T \times d} — вход подслоя (после нормализации), xt∈Rd\mathbf{x}_t \in \mathbb{R}^{d} — его строка для позиции tt;
  • f:Rd→Rdf : \mathbb{R}^d \to \mathbb{R}^d — одна и та же функция с одними и теми же весами для всех позиций.

Поэтому в коде FFN — это просто nn.Linear, применённые к тензору [B, T, d]: линейный слой PyTorch действует на последнюю ось, а оси батча и позиции для него — «номер примера». Схема работы блока: attention собирает для токена контекст, FFN перерабатывает собранное.

Запишем классический FFN (см. ниже) покомпонентно. Пусть ki∈Rd\mathbf{k}_i \in \mathbb{R}^d — ii-й столбец первой матрицы, vi∈Rd\mathbf{v}_i \in \mathbb{R}^d — ii-я строка второй (жирные ki,vi\mathbf{k}_i, \mathbf{v}_i — векторы «ключа» и «значения» нейрона; не путать с числом экспертов kk, размером словаря VV и матрицами K,VK, V из attention):

FFN(x)=∑i=1dffϕ(x⋅ki+bi) vi+b2\mathrm{FFN}(\mathbf{x}) = \sum_{i=1}^{d_{ff}} \phi\big(\mathbf{x} \cdot \mathbf{k}_i + b_i\big)\, \mathbf{v}_i + \mathbf{b}_2

где:

  • dffd_{ff} — число скрытых нейронов;
  • ϕ\phi — активация (ReLU, GELU, …);
  • bib_i — ii-я компонента сдвига b1\mathbf{b}_1 первого слоя, b2∈Rd\mathbf{b}_2 \in \mathbb{R}^d — сдвиг второго слоя;
  • x⋅ki+bi\mathbf{x} \cdot \mathbf{k}_i + b_i — насколько вход «похож» на ключ ki\mathbf{k}_i;
  • vi\mathbf{v}_i — вектор, который добавляется в residual-поток, если ключ сработал.

Это похоже на attention без softmax, где ключи и значения — не токены контекста, а обученные параметры. Geva et al., 2021 показали, что это не только аналогия: ключи многих нейронов срабатывают на понятные человеку шаблоны входа (в нижних слоях — поверхностные, например конкретное окончание слова, в верхних — смысловые), а значения, спроецированные на словарь, повышают вероятность токенов, которые обычно следуют за этим шаблоном (особенно в верхних слоях). FFN — основное хранилище «знаний» модели, и чем больше dffd_{ff}, тем больше таких пар ключ–значение.

FFN(x)=ϕ(xW1+b1) W2+b2\mathrm{FFN}(\mathbf{x}) = \phi(\mathbf{x} W_1 + \mathbf{b}_1)\, W_2 + \mathbf{b}_2

где:

  • x∈Rd\mathbf{x} \in \mathbb{R}^{d} — вход (строка; для всего тензора — [B, T, d]);
  • W1∈Rd×dffW_1 \in \mathbb{R}^{d \times d_{ff}}, b1∈Rdff\mathbf{b}_1 \in \mathbb{R}^{d_{ff}} — расширяющий слой;
  • ϕ\phi — поэлементная нелинейность; у Vaswani — ReLU, max⁡(0,z)\max(0, z);
  • W2∈Rdff×dW_2 \in \mathbb{R}^{d_{ff} \times d}, b2∈Rd\mathbf{b}_2 \in \mathbb{R}^{d} — сжимающий слой;
  • выход — Rd\mathbb{R}^{d}, той же размерности, что вход, чтобы его можно было прибавить к residual-потоку.

В трансформере d=512d = 512, dff=2048d_{ff} = 2048, т. е. dff=4dd_{ff} = 4d; это соотношение унаследовали GPT-1 и GPT-2 (d=768d = 768, dff=3072d_{ff} = 3072).

flowchart LR
    X(["x · [B, T, d]"]):::io --> L1["Linear W1<br/>d → 4d"]:::gray --> Act["активация"]:::purple --> L2["Linear W2<br/>4d → d"]:::gray --> Drop["Dropout"]:::gray --> Out(["out · [B, T, d]"]):::io

    classDef io fill:#ffffff,stroke:#999999,color:#1a1a1a;
    classDef add fill:#ffffff,stroke:#666666,color:#1a1a1a;
    classDef blue fill:#dae8fc,stroke:#6c8ebf,color:#1a1a1a;
    classDef blueHl fill:#dae8fc,stroke:#2f5f9e,stroke-width:3px,color:#1a1a1a;
    classDef purple fill:#e1d5e7,stroke:#9673a6,color:#1a1a1a;
    classDef purpleHl fill:#e1d5e7,stroke:#6a3d85,stroke-width:3px,color:#1a1a1a;
    classDef gray fill:#f5f5f5,stroke:#666666,color:#1a1a1a;
    classDef grayHl fill:#f5f5f5,stroke:#333333,stroke-width:3px,color:#1a1a1a;
    classDef gold fill:#fff2cc,stroke:#d6b656,color:#1a1a1a;
    classDef rope fill:#d5f0ec,stroke:#3a9e8f,color:#1a1a1a;
    classDef ropeHl fill:#d5f0ec,stroke:#1f6f63,stroke-width:3px,color:#1a1a1a;
    classDef dim fill:#f5f5f5,stroke:#bbbbbb,color:#999999,stroke-dasharray:4 3;

Зачем нелинейность. Без ϕ\phi два слоя схлопываются в один: (xW1+b1)W2+b2=x(W1W2)+(b1W2+b2)(\mathbf{x}W_1 + \mathbf{b}_1)W_2 + \mathbf{b}_2 = \mathbf{x}(W_1 W_2) + (\mathbf{b}_1 W_2 + \mathbf{b}_2) — линейное отображение с матрицей W1W2W_1 W_2 размера d×dd \times d. Расширение до dffd_{ff} стало бы бессмысленным.

Зачем расширение. Каждый скрытый нейрон — одна пара ключ–значение. Широкий скрытый слой даёт много «детекторов», а сжатие обратно до dd складывает их вклады.

NFFN=d⋅dff+dff⏟W1, b1+dff⋅d+d⏟W2, b2=2d dff+dff+dN_{\mathrm{FFN}} = \underbrace{d \cdot d_{ff} + d_{ff}}_{W_1,\ \mathbf{b}_1} + \underbrace{d_{ff} \cdot d + d}_{W_2,\ \mathbf{b}_2} = 2 d\, d_{ff} + d_{ff} + d

При dff=4dd_{ff} = 4d: NFFN=8d2+4d+d=8d2+5dN_{\mathrm{FFN}} = 8d^2 + 4d + d = 8d^2 + 5d.

Для сравнения, attention с Hdh=dH d_h = d имеет 4d2+4d4d^2 + 4d параметров (четыре матрицы d×dd \times d: WQ,WK,WV,WOW_Q, W_K, W_V, W_O), поэтому FFN — около 812=23\tfrac{8}{12} = \tfrac{2}{3} параметров блока.

Примеры.

  • Учебный конфиг, d=256d = 256: 8⋅65 536+5⋅256=525 5688 \cdot 65\,536 + 5 \cdot 256 = 525\,568.
  • GPT-2 small, d=768d = 768: 8⋅589 824+3 840=4 722 4328 \cdot 589\,824 + 3\,840 = 4\,722\,432 на блок, ≈56.7\approx 56.7 млн на 12 блоков.

Вычисления: на каждый токен примерно 2 FLOP на вес (умножение и сложение), т. е. ≈16d2\approx 16 d^2 FLOP — FFN доминирует и в стоимости прямого прохода при небольших TT.

ReLU(z)=max⁡(0,z),ReLU′(z)={1,z>00,z<0\mathrm{ReLU}(z) = \max(0, z), \qquad \mathrm{ReLU}'(z) = \begin{cases} 1, & z > 0 \\ 0, & z < 0 \end{cases}

где z∈Rz \in \mathbb{R} — одна компонента предактивации xW1+b1\mathbf{x}W_1 + \mathbf{b}_1.

Широкое распространение получила после работы Nair & Hinton (2010); в трансформере — у Vaswani. Плюсы: дёшево, градиент 1 на положительной полуоси. Минусы: излом в нуле и нулевой градиент при z<0z < 0 — нейрон, который для всех входов оказался в отрицательной области, перестаёт обучаться («мёртвый» нейрон). В репозитории — вариант activation="relu" в FeedForward.

GELU (Gaussian Error Linear Unit, Hendrycks & Gimpel, 2016):

GELU(x)=x Φ(x)=x2(1+erf ⁣(x2))\mathrm{GELU}(x) = x\, \Phi(x) = \frac{x}{2}\left(1 + \mathrm{erf}\!\left(\frac{x}{\sqrt{2}}\right)\right)

где:

  • x∈Rx \in \mathbb{R} — вход (одна компонента);
  • Φ(x)=P(Z≤x)\Phi(x) = P(Z \le x), Z∼N(0,1)Z \sim \mathcal{N}(0, 1) — функция распределения стандартного нормального закона;
  • erf(u)=2π∫0ue−s2ds\mathrm{erf}(u) = \frac{2}{\sqrt{\pi}} \int_0^u e^{-s^2} ds — функция ошибок; связь Φ(x)=12(1+erf(x/2))\Phi(x) = \tfrac{1}{2}\big(1 + \mathrm{erf}(x/\sqrt{2})\big).

Интуиция (разд. 2 статьи). ReLU умножает вход на 0 или 1 в зависимости от знака. GELU умножает его на «вероятность пропуска» Φ(x)\Phi(x): представим случайную маску m∼Bernoulli(Φ(x))m \sim \mathrm{Bernoulli}(\Phi(x)) — чем больше xx относительно других входов (которые после нормализации примерно N(0,1)\mathcal{N}(0,1)), тем вероятнее он пройдёт. Математическое ожидание E[mx]=xΦ(x)\mathbb{E}[m x] = x\Phi(x) — это и есть GELU. Получается гладкий аналог ReLU: при x→+∞x \to +\infty — xx, при x→−∞x \to -\infty — 0, в районе нуля — плавный переход и небольшой отрицательный «провал» (минимум ≈−0.170\approx -0.170 при x≈−0.752x \approx -0.752).

Производная:

GELU′(x)=Φ(x)+x φ(x),φ(x)=12πe−x2/2\mathrm{GELU}'(x) = \Phi(x) + x\, \varphi(x), \qquad \varphi(x) = \frac{1}{\sqrt{2\pi}} e^{-x^2/2}

где φ\varphi — плотность стандартного нормального распределения. Значения: GELU′(0)=0.5\mathrm{GELU}'(0) = 0.5, GELU′(1)≈1.083\mathrm{GELU}'(1) \approx 1.083, GELU′(−1)≈−0.083\mathrm{GELU}'(-1) \approx -0.083. В отличие от ReLU, градиент при x<0x < 0 не ровно ноль, и «мёртвых» нейронов нет.

Функция erf\mathrm{erf} не выражается через элементарные функции. Hendrycks и Gimpel предложили быстрое приближение через tanh⁡\tanh (разд. 2):

GELU(x)≈x2(1+tanh⁡ ⁣(2π (x+0.044715 x3)))\mathrm{GELU}(x) \approx \frac{x}{2}\left(1 + \tanh\!\left(\sqrt{\frac{2}{\pi}}\,\big(x + 0.044715\, x^3\big)\right)\right)

где 2/π≈0.7978846\sqrt{2/\pi} \approx 0.7978846. Именно её использовали OpenAI в коде GPT-1 и GPT-2, поэтому для совместимости с их весами нужна она, а не точная версия (см. бэклог, пункт 13).

Откуда берутся константы

Нужно приблизить erf(x/2)\mathrm{erf}(x/\sqrt{2}) функцией tanh⁡(⋅)\tanh(\cdot) — обе нечётные, растут от −1-1 до 11. Ищем приближение вида tanh⁡(c (x+ax3))\tanh\big(c\,(x + a x^3)\big) и подбираем cc и aa так, чтобы совпали ряды Тейлора в нуле.

Ряд для erf\mathrm{erf}: erf(u)=2π(u−u33+… )\mathrm{erf}(u) = \frac{2}{\sqrt{\pi}}\left(u - \frac{u^3}{3} + \dots\right). Подставим u=x/2u = x/\sqrt{2}:

erf ⁣(x2)=2π(x2−x33⋅22+… )=2π(x−x36)+O(x5)\mathrm{erf}\!\left(\frac{x}{\sqrt{2}}\right) = \frac{2}{\sqrt{\pi}}\left(\frac{x}{\sqrt{2}} - \frac{x^3}{3 \cdot 2\sqrt{2}} + \dots\right) = \sqrt{\frac{2}{\pi}}\left(x - \frac{x^3}{6}\right) + O(x^5)

Ряд для tanh⁡\tanh: tanh⁡(v)=v−v33+O(v5)\tanh(v) = v - \frac{v^3}{3} + O(v^5). Подставим v=c(x+ax3)v = c(x + a x^3); с точностью до x3x^3:

tanh⁡(c(x+ax3))=cx+cax3−c3x33+O(x5)\tanh\big(c(x + a x^3)\big) = c x + c a x^3 - \frac{c^3 x^3}{3} + O(x^5)

Приравниваем коэффициенты при xx: c=2/πc = \sqrt{2/\pi} — отсюда первая константа.

Приравниваем коэффициенты при x3x^3:

ca−c33=−c6⟹a=c23−16=23π−16≈0.045540c a - \frac{c^3}{3} = -\frac{c}{6} \quad\Longrightarrow\quad a = \frac{c^2}{3} - \frac{1}{6} = \frac{2}{3\pi} - \frac{1}{6} \approx 0.045540

Ряд Тейлора даёт a≈0.04554a \approx 0.04554 — точное совпадение около нуля. Константа 0.0447150.044715 из статьи немного меньше: она подобрана численно так, чтобы приближение было лучше на всей прямой, а не только около нуля. Сравнение максимальной абсолютной ошибки ∣GELUtanh⁡(x)−GELU(x)∣|\mathrm{GELU}_{\tanh}(x) - \mathrm{GELU}(x)| по сетке x∈[−20,20]x \in [-20, 20] с шагом 10−510^{-5} (float64):

aaМаксимальная ошибкаГде достигается
00 (без кубического члена)3.4⋅10−23.4 \cdot 10^{-2}x≈±2.07x \approx \pm 2.07
0.0455400.045540 (ряд Тейлора)7.5⋅10−47.5 \cdot 10^{-4}x≈±2.47x \approx \pm 2.47
0.0447150.044715 (статья)4.7⋅10−44.7 \cdot 10^{-4}x≈±2.70x \approx \pm 2.70

Ошибка одинакова при ±x\pm x: и точный GELU, и приближение удовлетворяют g(x)−g(−x)=xg(x) - g(-x) = x.

Точность. Максимальная ошибка tanh-аппроксимации — 4.7⋅10−44.7 \cdot 10^{-4} (при ∣x∣≈2.70|x| \approx 2.70), что меньше шага float16 около этих значений. Это посчитано так:

import math, torch
x = torch.linspace(-20, 20, 4_000_001, dtype=torch.float64)
exact = 0.5 * x * (1 + torch.erf(x / math.sqrt(2)))
approx = 0.5 * x * (1 + torch.tanh(math.sqrt(2 / math.pi) * (x + 0.044715 * x**3)))
err = (approx - exact).abs()
print(err.max().item(), x[err.argmax()].item()) # 0.000473... 2.6989...

Модель, обученная с одной версией, работает и с другой, но выходы расходятся: для GPT-2 логиты с «не той» GELU отличались от эталона на ∼10−4\sim 10^{-4} (бэклог, пункт 13). В статье есть и ещё более грубое приближение x σ(1.702x)x\,\sigma(1.702 x) (максимальная ошибка 2.0⋅10−22.0 \cdot 10^{-2}) — оно родственно SiLU из следующего раздела.

Кто что использует.

МодельВариантИмя в HFВ репозитории
GPT-1tanhafn="gelu" в openai-gpt, внутри modeling_openai — это gelu_newactivation="gelu_tanh" по умолчанию в GPT
GPT-2tanhactivation_function="gelu_new""gelu_tanh", зашито в Gpt2Decoder
Gemma (в GeGLU)tanhgelu_pytorch_tanhGELU из core/gelu.py внутри GeGLU

В PyTorch точный GELU — nn.GELU(), tanh-вариант — nn.GELU(approximate="tanh"); в репозитории tanh-вариант реализован отдельным классом GELU (см. ниже) и совпадает с approximate="tanh" до 2.4⋅10−72.4 \cdot 10^{-7} во float32.

SiLU(x)=x σ(x)=x1+e−x,Swishβ(x)=x σ(βx)\mathrm{SiLU}(x) = x\,\sigma(x) = \frac{x}{1 + e^{-x}}, \qquad \mathrm{Swish}_\beta(x) = x\,\sigma(\beta x)

где:

  • σ(x)=1/(1+e−x)\sigma(x) = 1/(1 + e^{-x}) — логистическая сигмоида;
  • β\beta — параметр (константа или обучаемый); SiLU — частный случай β=1\beta = 1.

История. Функцию xσ(x)x\sigma(x) под именем SiLU (Sigmoid-weighted Linear Unit) предложили Elfwing et al., 2017 для обучения с подкреплением. Ramachandran et al., 2017 нашли её же автоматическим поиском по пространству активаций и назвали Swish. При β→∞\beta \to \infty Swish стремится к ReLU, при β=0\beta = 0 — к x/2x/2. В LLM используется β=1\beta = 1, и имена SiLU и Swish употребляются как синонимы.

Идея та же, что у GELU, — вход, умноженный на «вероятность пропуска», только вместо Φ(x)\Phi(x) — сигмоида. Функция немонотонна: минимум ≈−0.278\approx -0.278 при x≈−1.278x \approx -1.278.

Производная (правило произведения и σ′=σ(1−σ)\sigma' = \sigma(1-\sigma)):

SiLU′(x)=σ(x)+x σ(x)(1−σ(x))=σ(x)(1+x(1−σ(x)))\mathrm{SiLU}'(x) = \sigma(x) + x\,\sigma(x)\big(1 - \sigma(x)\big) = \sigma(x)\big(1 + x(1 - \sigma(x))\big)

Значения: SiLU′(0)=0.5\mathrm{SiLU}'(0) = 0.5, SiLU′(1)≈0.928\mathrm{SiLU}'(1) \approx 0.928, SiLU′(−1)≈0.072\mathrm{SiLU}'(-1) \approx 0.072, SiLU′(2)≈1.091\mathrm{SiLU}'(2) \approx 1.091 — производная может быть больше 1.

Вместо графиков — таблица значений (GELU и GELU-tanh различаются только в четвёртом знаке):

xxReLUGELU (erf)GELU (tanh)SiLUσ(x)\sigma(x)
−30−0.0040−0.0036−0.14230.0474
−20−0.0455−0.0454−0.23840.1192
−10−0.1587−0.1588−0.26890.2689
−0.50−0.1543−0.1543−0.18880.3775
000000.5
0.50.50.34570.34570.31120.6225
110.84130.84120.73110.7311
221.95451.95461.76160.8808
332.99602.99642.85770.9526

Что видно из таблицы:

  • все три гладкие функции при больших ∣x∣|x| ведут себя как ReLU;
  • SiLU «мягче» GELU: дольше отстаёт от xx на положительной полуоси и глубже уходит в минус на отрицательной;
  • SiLU(x)=x σ(x)\mathrm{SiLU}(x) = x\,\sigma(x): столбец SiLU — произведение xx на последний столбец.

Gated Linear Unit (Dauphin et al., 2017) — две параллельные линейные проекции, одна из которых через сигмоиду «открывает» или «закрывает» другую:

GLU(x)=(xW+b)⊗σ(xV+c)\mathrm{GLU}(\mathbf{x}) = (\mathbf{x}W + \mathbf{b}) \otimes \sigma(\mathbf{x}V + \mathbf{c})

где:

  • x∈Rd\mathbf{x} \in \mathbb{R}^{d} — вход;
  • W,V∈Rd×hW, V \in \mathbb{R}^{d \times h}, b,c∈Rh\mathbf{b}, \mathbf{c} \in \mathbb{R}^{h} — две независимые проекции в скрытое пространство размера hh (буквы W,VW, V — как в статье; здесь VV — матрица, а не размер словаря);
  • ⊗\otimes — поэлементное умножение;
  • σ(xV+c)∈(0,1)h\sigma(\mathbf{x}V + \mathbf{c}) \in (0, 1)^h — гейт (gate): для каждой компоненты решает, какая доля сигнала пройдёт.

У Dauphin et al. GLU применялся в свёрточной языковой модели. Аргумент авторов: градиент ∇[a⊗σ(g)]=∇a⊗σ(g)+a⊗σ′(g)∇g\nabla[\mathbf{a} \otimes \sigma(\mathbf{g})] = \nabla\mathbf{a} \otimes \sigma(\mathbf{g}) + \mathbf{a} \otimes \sigma'(\mathbf{g})\nabla\mathbf{g} содержит линейный путь ∇a⊗σ(g)\nabla\mathbf{a} \otimes \sigma(\mathbf{g}) без производной нелинейности, и градиент через открытый гейт не затухает.

Shazeer, 2020 заменил первый слой FFN на GLU с разными функциями в гейте и убрал bias:

FFNGLU(x)=(σ(xW)⊗xV) W2FFNGEGLU(x)=(GELU(xW)⊗xV) W2FFNSwiGLU(x)=(Swish1(xW)⊗xV) W2\begin{aligned} \mathrm{FFN}_{\mathrm{GLU}}(\mathbf{x}) &= \big(\sigma(\mathbf{x}W) \otimes \mathbf{x}V\big)\, W_2 \\ \mathrm{FFN}_{\mathrm{GEGLU}}(\mathbf{x}) &= \big(\mathrm{GELU}(\mathbf{x}W) \otimes \mathbf{x}V\big)\, W_2 \\ \mathrm{FFN}_{\mathrm{SwiGLU}}(\mathbf{x}) &= \big(\mathrm{Swish}_1(\mathbf{x}W) \otimes \mathbf{x}V\big)\, W_2 \end{aligned}

где:

  • W∈Rd×dffW \in \mathbb{R}^{d \times d_{ff}} — проекция гейта (gate; в HF — gate_proj, у Meta — w1);
  • V∈Rd×dffV \in \mathbb{R}^{d \times d_{ff}} — линейная «полезная» проекция (up; up_proj, w3); как и в GLU выше, это матрица, а не размер словаря;
  • W2∈Rdff×dW_2 \in \mathbb{R}^{d_{ff} \times d} — сжимающая проекция (down; down_proj, w2);
  • Swish1=SiLU\mathrm{Swish}_1 = \mathrm{SiLU}.

В статье есть и другие варианты (ReGLU с ReLU, билинейный без функции). В экспериментах на T5 GEGLU и SwiGLU дали лучшую перплексию среди всех вариантов FFN при одинаковом числе параметров и вычислений. Теоретического объяснения автор не предлагает — выбор чисто эмпирический.

SwiGLU используют LLaMA, Mistral и Mixtral (в каждом эксперте), GeGLU (с tanh-GELU) — Gemma.

flowchart LR
    X(["x · [B, T, d]"]):::io --> G["Linear W (gate)<br/>d → d_ff"]:::gray --> Act["SiLU или GELU"]:::purple --> Mul(("×")):::add
    X --> U["Linear V (up)<br/>d → d_ff"]:::gray --> Mul
    Mul --> D["Linear W2 (down)<br/>d_ff → d"]:::gray --> Drop["Dropout"]:::gray --> Out(["out · [B, T, d]"]):::io

    classDef io fill:#ffffff,stroke:#999999,color:#1a1a1a;
    classDef add fill:#ffffff,stroke:#666666,color:#1a1a1a;
    classDef blue fill:#dae8fc,stroke:#6c8ebf,color:#1a1a1a;
    classDef blueHl fill:#dae8fc,stroke:#2f5f9e,stroke-width:3px,color:#1a1a1a;
    classDef purple fill:#e1d5e7,stroke:#9673a6,color:#1a1a1a;
    classDef purpleHl fill:#e1d5e7,stroke:#6a3d85,stroke-width:3px,color:#1a1a1a;
    classDef gray fill:#f5f5f5,stroke:#666666,color:#1a1a1a;
    classDef grayHl fill:#f5f5f5,stroke:#333333,stroke-width:3px,color:#1a1a1a;
    classDef gold fill:#fff2cc,stroke:#d6b656,color:#1a1a1a;
    classDef rope fill:#d5f0ec,stroke:#3a9e8f,color:#1a1a1a;
    classDef ropeHl fill:#d5f0ec,stroke:#1f6f63,stroke-width:3px,color:#1a1a1a;
    classDef dim fill:#f5f5f5,stroke:#bbbbbb,color:#999999,stroke-dasharray:4 3;

В классическом FFN скрытый нейрон ii выдаёт ϕ(x⋅ki)\phi(\mathbf{x}\cdot\mathbf{k}_i): один вектор ключа решает и «сработал ли нейрон», и «насколько сильно». В gated-варианте это разделено: гейт SiLU(x⋅wi)\mathrm{SiLU}(\mathbf{x}\cdot\mathbf{w}_i) решает, включён ли нейрон, а x⋅vi\mathbf{x}\cdot\mathbf{v}_i — какое значение (со знаком) он передаёт. Выход нейрона — произведение двух линейных функций входа (с мягким «выключателем»), т. е. квадратичная форма от x\mathbf{x}: такой слой может выражать мультипликативные взаимодействия признаков, которые классическому FFN даются дороже.

Численный пример. Пусть dff=3d_{ff} = 3 и после проекций для одного токена гейт g=xW=(2, −1, 0)\mathbf{g} = \mathbf{x}W = (2,\ -1,\ 0), полезная часть u=xV=(1, 3, 5)\mathbf{u} = \mathbf{x}V = (1,\ 3,\ 5).

Компонента 1Компонента 2Компонента 3
gg2−10
SiLU(g)\mathrm{SiLU}(g)1.7616−0.26890
uu135
SwiGLU: SiLU(g) u\mathrm{SiLU}(g)\,u1.7616−0.80680
GELUtanh⁡(g)\mathrm{GELU}_{\tanh}(g)1.9546−0.15880
GeGLU: GELU(g) u\mathrm{GELU}(g)\,u1.9546−0.47640

Третья компонента несёт самое большое значение u=5u = 5, но гейт закрыт (g=0g = 0), и она ничего не передаёт. Вторая пропущена с малым отрицательным коэффициентом. Результат (строка из трёх чисел) затем умножается на W2W_2 и возвращается в размерность dd.

У gated-FFN три матрицы вместо двух. Чтобы сравнение с классическим FFN было честным (одинаковые параметры и FLOP), скрытый размер уменьшают. Без bias:

2⋅d⋅4d⏟классический FFN=3⋅d⋅dff⏟SwiGLU⟹dff=8d3=23⋅4d\underbrace{2 \cdot d \cdot 4d}_{\text{классический FFN}} = \underbrace{3 \cdot d \cdot d_{ff}}_{\text{SwiGLU}} \quad\Longrightarrow\quad d_{ff} = \frac{8d}{3} = \frac{2}{3} \cdot 4d

где слева — W1W_1 и W2W_2 классического FFN с dff=4dd_{ff} = 4d, справа — W,V,W2W, V, W_2 gated-FFN. Так делал уже Shazeer (скрытый размер 2048 вместо 3072 при d=768d = 768), и так делает LLaMA (Touvron et al., 2023, разд. 2.2).

8d/38d/3 обычно не целое и неудобно для GPU: матричные умножения быстрее, когда размеры кратны большой степени двойки. Поэтому в коде Meta результат округляют вверх до кратного multiple_of (256 для LLaMA), с необязательным множителем ffn_dim_multiplier (LLaMA 2 70B). В репозитории это llama_intermediate_size в models/llama/llama.py:

def llama_intermediate_size(embed_dim: int, multiple_of: int = 256, ffn_dim_multiplier: float = None) -> int:
hidden = int(2 * 4 * embed_dim / 3) # ⌊8d/3⌋
if ffn_dim_multiplier is not None:
hidden = int(ffn_dim_multiplier * hidden) # необязательный множитель
return multiple_of * ((hidden + multiple_of - 1) // multiple_of) # округление вверх

Формула:

dff=m⋅⌈⌊8d/3⌋m⌉d_{ff} = m \cdot \left\lceil \frac{\lfloor 8d/3 \rfloor}{m} \right\rceil

где mm — multiple_of, ⌈⋅⌉\lceil\cdot\rceil и ⌊⋅⌋\lfloor\cdot\rfloor — округление вверх и вниз. Выражение (hidden + m - 1) // m — стандартный приём целочисленного деления с округлением вверх.

Пример, LLaMA 7B (d=4096d = 4096):

  1. 8⋅4096/3=10922.678 \cdot 4096 / 3 = 10922.67, int → 1092210922.
  2. 10922/256=42.6610922 / 256 = 42.66, вверх → 4343.
  3. dff=43⋅256=11008d_{ff} = 43 \cdot 256 = 11008.

Параметров FFN на слой: 3⋅4096⋅11008=135 266 3043 \cdot 4096 \cdot 11008 = 135\,266\,304 против 8⋅40962=134 217 7288 \cdot 4096^2 = 134\,217\,728 у классического FFN — на 0.8 % больше из-за округления. С тем же dff=4d=16384d_{ff} = 4d = 16384 было бы 201 326 592201\,326\,592 — в 1.5 раза больше (бэклог, пункт 23). Другие значения: d=5120d = 5120 (LLaMA 13B) → 1382413824; d=8192d = 8192, ffn_dim_multiplier=1.3, multiple_of=4096 (LLaMA 2 70B) → 2867228672.

from llm.models.llama import llama_intermediate_size
print(llama_intermediate_size(4096)) # 11008
print(llama_intermediate_size(288, multiple_of=32)) # 768 (llama2.c stories15M)

Не все модели следуют правилу ⅔:

  • Mistral 7B и эксперты Mixtral 8x7B: dff=14336=3.5dd_{ff} = 14336 = 3.5d при d=4096d = 4096 (mistral.md). FFN в 3⋅3.5/8=1.31253 \cdot 3.5 / 8 = 1.3125 раза тяжелее классического FFN с dff=4dd_{ff} = 4d при том же dd.
  • Gemma: dff=8dd_{ff} = 8d на каждую из матриц gate и up — 16384 при d=2048d = 2048 (2B) и 24576 при d=3072d = 3072 (7B) (gemma.md). Это 24d224d^2 параметров — втрое больше классического FFN. В табл. 1 статьи Gemma указаны вдвое большие «feedforward hidden dims» (32768 и 49152) — это сумма gate и up (бэклог, пункт 44).
МодельТип FFNАктивацияdddffd_{ff}dff/dd_{ff}/dПараметров FFN на слой
Transformer (Vaswani)классическийReLU512204842 099 7122\,099\,712 (с bias)
GPT-1классическийGELU (tanh)768307244 722 4324\,722\,432 (с bias)
GPT-2 smallклассическийGELU (tanh)768307244 722 4324\,722\,432 (с bias)
LLaMA 7BSwiGLUSiLU409611008≈ 2.69135 266 304135\,266\,304
Mistral 7BSwiGLUSiLU4096143363.5176 160 768176\,160\,768
Mixtral 8x7B8 экспертов SwiGLU, top-2SiLU409614336 (у каждого)3.58×176 160 7688 \times 176\,160\,768, активно 2×2 \times
Gemma 2BGeGLUGELU (tanh)2048163848100 663 296100\,663\,296
Gemma 7BGeGLUGELU (tanh)3072245768226 492 416226\,492\,416

У LLaMA и последующих моделей bias в FFN нет. В репозитории dffd_{ff} задаётся ключом intermediate_size (по умолчанию 4d4d), bias — ключом bias (по умолчанию true); классический FeedForward GPT всегда 4d4d с bias. Mixture-of-Experts разбирается в следующей главе — Mixture-of-Experts.

Dropout (Srivastava et al., 2014) при обучении обнуляет каждую компоненту с вероятностью pp и масштабирует оставшиеся на 1/(1−p)1/(1-p); при инференсе (model.eval()) ничего не делает. В трансформере Vaswani dropout применяется к выходу каждого подслоя перед сложением с residual (разд. 5.4). В GPT это resid_pdrop.

В репозитории все FFN-модули устроены одинаково: один dropout на выходе, после сжимающей проекции, перед residual-сложением. Внутри (на скрытых активациях) dropout нет.

  • FeedForward, SwiGLU, GeGLU: return self._dropout(out) в конце forward; вероятность — параметр dropout конструктора (из config["dropout"]).
  • MoE: эксперты-SwiGLU создаются с dropout=0.0, dropout — один, на выходе всего слоя, иначе выход прорежался бы дважды (core/moe.py).

Современные LLM (LLaMA, Mistral, Gemma) предобучаются на огромных корпусах почти за одну эпоху и dropout не используют: переобучение там не главная проблема. В репозитории он есть везде и отключается "dropout": 0 в конфиге — см. mistral.md, gemma.md.

КлассФайлФормулаГде используется
FeedForwardcore/feed_forward.pyϕ(xW1+b1)W2+b2\phi(\mathbf{x}W_1 + \mathbf{b}_1)W_2 + \mathbf{b}_2, dff=4dd_{ff} = 4dGPT-1 (GptDecoder), GPT-2 (Gpt2Decoder)
SwiGLUcore/swi_glu.py(SiLU(xW)⊗xV)W2(\mathrm{SiLU}(\mathbf{x}W) \otimes \mathbf{x}V)W_2LLaMA, Mistral, эксперты Mixtral
GeGLUcore/geglu.py(GELUtanh⁡(xW)⊗xV)W2(\mathrm{GELU}_{\tanh}(\mathbf{x}W) \otimes \mathbf{x}V)W_2Gemma
GELUcore/gelu.pytanh-аппроксимацияFeedForward(activation="gelu_tanh"), GeGLU
SiLUcore/silu.pyx σ(x)x\,\sigma(x)SwiGLU

FeedForward(emb_size, dropout=0.1, activation="relu"):

self._layer1 = nn.Linear(emb_size, emb_size * 4) # W1, b1: d → 4d
if activation == "relu":
self._activation = nn.ReLU()
elif activation == "gelu":
self._activation = nn.GELU() # точный, через erf
elif activation == "gelu_tanh":
self._activation = GELU() # tanh-аппроксимация (HF: gelu_new)
else:
raise ValueError(f"Unknown activation: {activation}")
self._layer2 = nn.Linear(emb_size * 4, emb_size) # W2, b2: 4d → d
self._dropout = nn.Dropout(dropout)

forward — ровно формула классического FFN: _layer1 → _activation → _layer2 → _dropout. Особенности:

  • dffd_{ff} зашит как emb_size * 4, параметра для него нет; bias есть всегда;
  • значение activation по умолчанию у самого класса — "relu", но декодеры передают другое: GptDecoder(activation="gelu_tanh") по умолчанию (модель GPT читает config.get("activation", "gelu_tanh")), Gpt2Decoder жёстко задаёт "gelu_tanh";
  • forward не приводит веса к dtype входа: dtype модели меняют снаружи — model.to(torch.bfloat16) или torch.autocast.

SwiGLU(emb_size, dropout=0.1, hidden_dim=None, bias=True); GeGLU — с той же сигнатурой.

hidden_dim = 4 * emb_size if hidden_dim is None else hidden_dim # d_ff; <= 0 → ValueError
self._gate = nn.Linear(emb_size, hidden_dim, bias=bias) # W (gate)
self._up = nn.Linear(emb_size, hidden_dim, bias=bias) # V (up)
self._down = nn.Linear(hidden_dim, emb_size, bias=bias) # W2 (down)
self._activation = SiLU() # в GeGLU — GELU()

forward:

gate_out = self._gate(x) # xW
activation_out = self._activation(gate_out) # SiLU(xW)
up_out = self._up(x) # xV
out = up_out * activation_out # ⊗ — поэлементно
out = self._down(out) # (…) W2
return self._dropout(out)

Активация применяется к ветви _gate, а не _up, — как у Shazeer и в HF (down_proj(act(gate_proj(x)) * up_proj(x))); поэтому при загрузке весов gate_proj → _gate, up_proj → _up. Отличия от статей по умолчанию: dff=4dd_{ff} = 4d вместо 23⋅4d\tfrac{2}{3}\cdot 4d / 3.5d3.5d / 8d8d и bias во всех трёх проекциях; оба включаются ключами intermediate_size и bias (см. llama.md).

Параметры для d=256d = 256:

Модульdffd_{ff}biasПараметров
FeedForward(256)1024да525 568525\,568
SwiGLU(256)1024да788 736788\,736 (3⋅256⋅1024+2⋅1024+2563 \cdot 256 \cdot 1024 + 2 \cdot 1024 + 256)
SwiGLU(256, hidden_dim=768, bias=False)768 = llama_intermediate_size(256)нет589 824589\,824

GELU в core/gelu.py — формула tanh-аппроксимации буквально:

self.sqrt_2_over_pi = torch.sqrt(torch.tensor(2.0) / math.pi)
...
return 0.5 * x * (1 + torch.tanh(self.sqrt_2_over_pi * (x + 0.044715 * torch.pow(x, 3))))

Параметров нет; константа 2/π\sqrt{2/\pi} — обычный атрибут-тензор (0-мерный, поэтому при умножении действует как скаляр и не меняет dtype и устройство входа). Результат совпадает с nn.functional.gelu(x, approximate="tanh") до 2.4⋅10−72.4 \cdot 10^{-7}.

SiLU в core/silu.py — torch.sigmoid(x) * x, совпадает с nn.functional.silu до ошибок округления float32.

import torch
from llm.core.gelu import GELU
from llm.core.silu import SiLU
from llm.core.swi_glu import SwiGLU
x = torch.tensor([-1.0, 0.0, 1.0, 2.0])
print(GELU()(x)) # tensor([-0.1588, 0.0000, 0.8412, 1.9546])
print(SiLU()(x)) # tensor([-0.2689, 0.0000, 0.7311, 1.7616])
ffn = SwiGLU(emb_size=256, dropout=0.0, hidden_dim=768, bias=False)
print(ffn(torch.randn(2, 10, 256)).shape) # torch.Size([2, 10, 256])
  • Точный GELU вместо tanh (или наоборот) при загрузке весов GPT-2 или Gemma: модель работает, но логиты расходятся с эталоном на ∼10−4\sim 10^{-4}. В HF имя "gelu" означает разное в разных моделях — смотрите реализацию.
  • Перепутанные gate и up: SiLU(xV)⊗xW\mathrm{SiLU}(\mathbf{x}V) \otimes \mathbf{x}W — другая функция; веса HF в такой модуль загрузятся без ошибки, но результат будет неверным.
  • Неверный dffd_{ff}: 4d4d в SwiGLU делает FFN в 1.5 раза тяжелее, чем в LLaMA, и сравнение с GPT той же ширины нечестным. Для загрузки весов intermediate_size должен совпадать с чекпоинтом — иначе не совпадут формы.
  • Округление вниз вместо вверх в multiple_of: для d=4096d = 4096 получится 1075210752 вместо 1100811008.
  • Dropout при инференсе: FFN-модули содержат nn.Dropout; перед генерацией или сравнением логитов нужен model.eval() (или dropout: 0 в конфиге).
  • Gemma: 8d — на каждую матрицу, а не суммарно; число из таблицы статьи — сумма gate и up.
  • FFN применяется к каждой позиции независимо; её можно читать как память «ключ–значение», где ключи — столбцы W1W_1, значения — строки W2W_2.
  • Классический FFN: ϕ(xW1+b1)W2+b2\phi(\mathbf{x}W_1 + \mathbf{b}_1)W_2 + \mathbf{b}_2, dff=4dd_{ff} = 4d, 8d2+5d8d^2 + 5d параметров, около ⅔ параметров блока.
  • GELU =xΦ(x)= x\Phi(x); tanh-аппроксимация с 2/π\sqrt{2/\pi} (совпадение ряда Тейлора) и 0.0447150.044715 (подбор) ошибается не более чем на 4.7⋅10−44.7 \cdot 10^{-4}; её используют GPT-1, GPT-2 и Gemma.
  • SiLU =xσ(x)= x\sigma(x), производная σ(x)(1+x(1−σ(x)))\sigma(x)(1 + x(1 - \sigma(x))).
  • SwiGLU и GeGLU: (act(xW)⊗xV)W2(\mathrm{act}(\mathbf{x}W) \otimes \mathbf{x}V)W_2 — три матрицы, гейт отделяет «включён ли нейрон» от «что он передаёт».
  • При равенстве параметров dff=23⋅4dd_{ff} = \tfrac{2}{3}\cdot 4d, в LLaMA округляется вверх до кратного 256 (11008 при d=4096d = 4096); Mistral — 3.5d3.5d, Gemma — 8d8d.
  • В репозитории: FeedForward (GPT), SwiGLU (LLaMA, Mistral, Mixtral), GeGLU (Gemma); размер — ключ intermediate_size.
  1. Посчитайте число параметров классического FFN (с bias, dff=4dd_{ff} = 4d) для d=512d = 512 и сравните с attention той же ширины (4d2+4d4d^2 + 4d).
Ответ

FFN: 8⋅5122+5⋅512=2 097 152+2 560=2 099 7128 \cdot 512^2 + 5 \cdot 512 = 2\,097\,152 + 2\,560 = 2\,099\,712. Attention: 4⋅262 144+2 048=1 050 6244 \cdot 262\,144 + 2\,048 = 1\,050\,624. FFN почти вдвое больше — около ⅔ параметров блока (без учёта нормализаций).

  1. Вычислите вручную GELU(1)\mathrm{GELU}(1) точно (Φ(1)≈0.8413\Phi(1) \approx 0.8413) и через tanh-аппроксимацию. Какова разница?
Ответ

Точно: 1⋅0.8413=0.84131 \cdot 0.8413 = 0.8413. Аппроксимация: аргумент 2/π (1+0.044715)=0.79788⋅1.044715≈0.83356\sqrt{2/\pi}\,(1 + 0.044715) = 0.79788 \cdot 1.044715 \approx 0.83356, tanh⁡(0.83356)≈0.68238\tanh(0.83356) \approx 0.68238, 0.5⋅(1+0.68238)≈0.841190.5 \cdot (1 + 0.68238) \approx 0.84119. Разница ≈1.5⋅10−4\approx 1.5 \cdot 10^{-4}.

  1. Выведите производную SiLU\mathrm{SiLU} и найдите, при каком условии она равна нулю. Проверьте, что точка минимума x≈−1.278x \approx -1.278 ему удовлетворяет.
Ответ

SiLU′(x)=σ(x)(1+x(1−σ(x)))\mathrm{SiLU}'(x) = \sigma(x)(1 + x(1 - \sigma(x))). Так как σ>0\sigma > 0, ноль — когда 1+x(1−σ(x))=01 + x(1 - \sigma(x)) = 0. При x=−1.278x = -1.278: σ(−1.278)≈0.2178\sigma(-1.278) \approx 0.2178, 1−σ≈0.78221 - \sigma \approx 0.7822, 1+(−1.278)(0.7822)≈1−0.9995≈01 + (-1.278)(0.7822) \approx 1 - 0.9995 \approx 0.

  1. Найдите llama_intermediate_size(512) и llama_intermediate_size(2048) (с multiple_of=256). Сколько параметров у SwiGLU без bias с этим размером и у классического FFN без bias с 4d4d?
Ответ

d=512d = 512: ⌊4096/3⌋=1365\lfloor 4096/3 \rfloor = 1365, ⌈1365/256⌉=6\lceil 1365/256 \rceil = 6, dff=1536d_{ff} = 1536. SwiGLU: 3⋅512⋅1536=2 359 2963 \cdot 512 \cdot 1536 = 2\,359\,296; классический: 8⋅5122=2 097 1528 \cdot 512^2 = 2\,097\,152 (+12.5 % из-за округления).

d=2048d = 2048: ⌊16384/3⌋=5461\lfloor 16384/3 \rfloor = 5461, ⌈5461/256⌉=22\lceil 5461/256 \rceil = 22, dff=5632d_{ff} = 5632. SwiGLU: 3⋅2048⋅5632=34 603 0083 \cdot 2048 \cdot 5632 = 34\,603\,008; классический: 8⋅20482=33 554 4328 \cdot 2048^2 = 33\,554\,432 (+3.1 %).

  1. Выведите, каким должен быть dffd_{ff} у gated-FFN, чтобы его параметры равнялись параметрам классического FFN шириной r⋅dr \cdot d. Во сколько раз FFN Gemma (8d8d) больше классического с 4d4d?
Ответ

2⋅d⋅rd=3⋅d⋅dff2 \cdot d \cdot rd = 3 \cdot d \cdot d_{ff} ⇒ dff=23rdd_{ff} = \tfrac{2}{3} r d. Gemma: 3⋅d⋅8d=24d23 \cdot d \cdot 8d = 24d^2 против 8d28d^2 — в 3 раза; эквивалентная ширина классического FFN — r=12r = 12.

  1. Для SwiGLU с гейтом g=(−2, 1, 3)\mathbf{g} = (-2,\ 1,\ 3) и u=(4, −1, 0.5)\mathbf{u} = (4,\ -1,\ 0.5) посчитайте скрытый вектор SiLU(g)⊗u\mathrm{SiLU}(\mathbf{g}) \otimes \mathbf{u}.
Ответ

SiLU(−2)≈−0.2384\mathrm{SiLU}(-2) \approx -0.2384, SiLU(1)≈0.7311\mathrm{SiLU}(1) \approx 0.7311, SiLU(3)≈2.8577\mathrm{SiLU}(3) \approx 2.8577. Произведение: (−0.9536, −0.7311, 1.4289)(-0.9536,\ -0.7311,\ 1.4289).

  1. Почему FFN без активации бесполезна, даже если dff≫dd_{ff} \gg d? Каков максимальный ранг её матрицы?
Ответ

xW1W2\mathbf{x}W_1W_2 — линейное отображение с матрицей W1W2∈Rd×dW_1W_2 \in \mathbb{R}^{d \times d}, ранг не больше dd (и не больше dffd_{ff}). Такое отображение выражается одним слоем d×dd \times d, расширение ничего не добавляет, а параметров в 2dff/d2d_{ff}/d раз больше.

  1. (Обсуждение) В GLU гейт и значение считаются из одного и того же x\mathbf{x}. Покажите, что выход одного нейрона SwiGLU при больших положительных x⋅w\mathbf{x}\cdot\mathbf{w} — квадратичная функция x\mathbf{x}, и объясните, чем это отличается от классического FFN.
Ответ

При z=x⋅w≫0z = \mathbf{x}\cdot\mathbf{w} \gg 0 SiLU(z)≈z\mathrm{SiLU}(z) \approx z, и нейрон выдаёт (x⋅w)(x⋅v)=x (wv⊤) x⊤(\mathbf{x}\cdot\mathbf{w})(\mathbf{x}\cdot\mathbf{v}) = \mathbf{x}\,(\mathbf{w}\mathbf{v}^\top)\,\mathbf{x}^\top — квадратичная форма. Нейрон классического FFN при z≫0z \gg 0 линеен: GELU(z)≈z\mathrm{GELU}(z) \approx z. Gated-нейрон напрямую моделирует произведения признаков, классическому FFN для этого нужно несколько нейронов.

  • Vaswani et al. Attention Is All You Need. 2017. arXiv:1706.03762
  • Geva, Schuster, Berant, Levy. Transformer Feed-Forward Layers Are Key-Value Memories. 2021. arXiv:2012.14913
  • Nair, Hinton. Rectified Linear Units Improve Restricted Boltzmann Machines. ICML, 2010 (на arXiv не публиковалась)
  • Hendrycks, Gimpel. Gaussian Error Linear Units (GELUs). 2016. arXiv:1606.08415
  • Elfwing, Uchibe, Doya. Sigmoid-Weighted Linear Units for Neural Network Function Approximation in Reinforcement Learning. 2017. arXiv:1702.03118
  • Ramachandran, Zoph, Le. Searching for Activation Functions. 2017. arXiv:1710.05941
  • Dauphin, Fan, Auli, Grangier. Language Modeling with Gated Convolutional Networks. 2017. arXiv:1612.08083
  • Shazeer. GLU Variants Improve Transformer. 2020. arXiv:2002.05202
  • Touvron et al. LLaMA: Open and Efficient Foundation Language Models. 2023. arXiv:2302.13971
  • Srivastava, Hinton, Krizhevsky, Sutskever, Salakhutdinov. Dropout: A Simple Way to Prevent Neural Networks from Overfitting. JMLR, 2014. PDF