diff --git a/llm/src/llm/core/gelu.py b/llm/src/llm/core/gelu.py index b04aa55..5d4592c 100644 --- a/llm/src/llm/core/gelu.py +++ b/llm/src/llm/core/gelu.py @@ -1,22 +1,45 @@ import torch from torch import nn - +import math class GELU(nn.Module): """ - Гауссовская Эрф-активация (GELU, Gaussian Error Linear Unit). + GELU (Gaussian Error Linear Unit) — современная сглаженная функция активации для нейросетей. - Научная суть: - - Одна из самых популярных smooth активаций для трансформеров. - - Дает более гибкие аппроксимации, чем ReLU/SiLU, улучшает flow градиентов для больших LLM. - - Используется в BERT, GPT, GPT2 и почти всех современных NLP-моделях. - Формула: - GELU(x) = 0.5 * x * (1 + tanh(\sqrt{2/π} * (x + 0.044715 x³))) - Подробнее: Hendrycks & Gimpel, "Gaussian Error Linear Units (GELUs)", arXiv:1606.08415 - Пример: + Мотивация и назначение: + ----------------------- + - GELU используется во всех современных трансформерах (BERT, GPT, Llama) вместо ReLU, поскольку лучше передает градиенты и даёт более "мягкое" обучение. + - Формирует плавный переход между активированным и неактивированным состоянием, что улучшает устойчивость и общую производительность больших моделей. + - Дает возможность обучению «решать», насколько сильно и в каких диапазонах нужно передавать сигнал (в отличие от жёсткого ReLU). + + Математическая формула: + ----------------------- + GELU(x) = 0.5 * x * (1 + tanh( sqrt(2/pi) * (x + 0.044715 * x^3) )) + - Статья (Hendrycks & Gimpel, 2016): https://arxiv.org/abs/1606.08415 + - В PyTorch с версии 1.4+ встроена как torch.nn.functional.gelu и torch.nn.GELU. + + Как это работает: + ----------------- + - Для каждого входного значения x: + - x при больших значениях (большие положительные) почти полностью передается дальше. + - x при малых (или сильно отрицательных) "заглушается" к нулю. + - На промежуточных значениях — плавный переход. + - Является аппроксимацией случайного бинома с гауссовским шумом. + + Args: + ----- + Нет learnable параметров — GELU работает одинаково для всех входов. + + Пример использования: + --------------------- >>> gelu = GELU() - >>> y = gelu(torch.tensor([-1.0, 0.0, 1.0])) - >>> print(y) + >>> x = torch.tensor([-2.0, 0.0, 2.0]) + >>> print(gelu(x)) # тензор из плавно переходящих значений + + References: + ----------- + - Hendrycks & Gimpel: https://arxiv.org/abs/1606.08415 + - BERT, GPT-2 papers (везде используется GELU) """ def __init__(self): @@ -24,6 +47,24 @@ class GELU(nn.Module): self.sqrt_2_over_pi = torch.sqrt(torch.tensor(2.0) / math.pi) def forward(self, x: torch.Tensor) -> torch.Tensor: + """ + Прямой проход через GELU-активацию. + + Args: + ----- + x : torch.Tensor + Любой входной тензор. + + Returns: + -------- + torch.Tensor — тензор той же формы, где к каждому элементу применён GELU. + + Пример: + ------- + >>> gelu = GELU() + >>> x = torch.linspace(-3, 3, 7) + >>> y = gelu(x) + """ return ( 0.5 * x diff --git a/llm/tests/core/test_gelu.py b/llm/tests/core/test_gelu.py new file mode 100644 index 0000000..d384eaa --- /dev/null +++ b/llm/tests/core/test_gelu.py @@ -0,0 +1,46 @@ +import torch +import pytest +from llm.core.gelu import GELU + +def test_gelu_shapes_and_dtype(): + gelu = GELU() + x = torch.randn(4, 16, 8) + y = gelu(x) + assert y.shape == x.shape + assert y.dtype == x.dtype + +def test_gelu_known_values(): + gelu = GELU() + x = torch.tensor([-3.0, 0.0, 3.0]) + y = gelu(x) + # Сравнение с PyTorch F.gelu (которая использует точный алгоритм) + y_ref = torch.nn.functional.gelu(x) + diff = (y - y_ref).abs().max().item() + assert diff < 5e-3, f"Max difference {diff} exceeds threshold" + +def test_gelu_is_smooth_and_monotonic(): + gelu = GELU() + x = torch.linspace(-5, 5, 100) + y = gelu(x) + dy = y[1:] - y[:-1] + # Проверяем, что функция GELU хотя бы локально монотонна на большинстве промежутков + assert (dy.mean() > 0 or dy.mean() < 0) + +def test_gelu_gradients(): + gelu = GELU() + x = torch.randn(3, 5, requires_grad=True) + y = gelu(x) + loss = y.sum() + loss.backward() + assert x.grad is not None + assert x.grad.shape == x.shape + +def test_gelu_large_vs_small(): + gelu = GELU() + x_pos = torch.tensor([100.0]) + x_neg = torch.tensor([-100.0]) + y_pos = gelu(x_pos) + y_neg = gelu(x_neg) + # Для больших положительных GELU(x) ~ x, для больших отрицательных ~0 + assert torch.allclose(y_pos, x_pos, rtol=1e-4, atol=1e-4) + assert torch.allclose(y_neg, torch.zeros_like(x_neg), rtol=1e-4, atol=1e-4)