# Строим GPT с нуля: пошаговое руководство по архитектуре Transformer

> Спикер: Андрей Карпати | Длительность: 1:56:20

## Ключевые идеи

1. **ChatGPT — это языковая модель** — система предсказывает следующий токен в последовательности. Это вероятностная модель: один и тот же запрос может дать разные ответы.

2. **Transformer — единственная архитектура, которая имеет значение** — из статьи «Attention Is All You Need» (2017), с минимальными изменениями захватила все области ИИ.

3. **Токенизация: текст → числа** — существуют разные схемы (посимвольная, subword, BPE). Компромисс между размером словаря и длиной последовательности.

4. **Данные подаются батчами случайных чанков** — в одном чанке длиной T+1 упаковано T обучающих примеров с контекстами от 1 до T символов.

5. **Биграммная модель — простейший baseline** — предсказание только по последнему токену, без контекста. Loss ~2.5 на Shakespeare.

6. **Self-attention через Query, Key, Value** — каждый токен генерирует Q (что ищу), K (что содержу), V (что сообщу). Скалярное произведение Q·K даёт веса, по которым агрегируются V.

7. **Матричный трюк: нижнетреугольная маска** — маскирование будущих позиций через -∞ + softmax. Параллельные вычисления вместо циклов.

8. **Multi-head attention** — несколько параллельных каналов коммуникации. 4 головы по 8 измерений вместо 1 головы на 32.

9. **Transformer-блок = attention + feedforward** — attention собирает информацию, feedforward обрабатывает собранное. Блоки повторяются N раз.

10. **Residual connections + LayerNorm** — обязательны для обучения глубоких сетей. Residual создают «магистраль» для градиентов, LayerNorm стабилизирует активации.

11. **Scaled attention** — деление на √d_k сохраняет дисперсию ~1 при инициализации, предотвращая схлопывание softmax в one-hot.

12. **Dropout** — регуляризация через случайное зануление активаций. Применяется в attention-весах и feedforward.

## Транскрипт

### Введение: ChatGPT и Transformer

Андрей Карпати начинает с демонстрации ChatGPT — системы, которая генерирует текст слева направо, токен за токеном. Это языковая модель: она моделирует последовательности слов и знает, как слова следуют друг за другом в языке. Под капотом работает архитектура Transformer из статьи «Attention Is All You Need» (2017).

Цель лекции — построить Transformer-модель с нуля, обучить её на корпусе Шекспира (tiny Shakespeare, ~1 МБ) и научить генерировать «шекспироподобный» текст посимвольно. Весь код доступен в репозитории nanoGPT.

### Подготовка данных и токенизация

Датасет — все произведения Шекспира в одном файле (~1 млн символов). Словарь — 65 уникальных символов (буквы, пробелы, знаки препинания). Создаются функции encode (строка → список чисел) и decode (обратно).

Обсуждаются альтернативные токенизаторы: SentencePiece (Google), tiktoken (OpenAI, ~50 000 токенов для GPT-2). Компромисс: маленький словарь = длинные последовательности и наоборот.

Данные разбиваются на train (90%) и val (10%) для контроля переобучения.

### DataLoader: батчи чанков данных

Вводятся ключевые параметры: block_size (максимальная длина контекста, начинаем с 8) и batch_size (количество параллельных последовательностей, 4). В чанке из 9 символов упаковано 8 обучающих примеров — модель учится предсказывать по контексту от 1 до 8 символов.

Функция get_batch генерирует случайные смещения в массиве данных, вырезает чанки и складывает в тензор (B, T). Итого 32 независимых примера в одном батче.

### Биграммная языковая модель

Простейшая модель: таблица эмбеддингов vocab_size × vocab_size. Каждый токен по своему индексу извлекает строку из таблицы — это logits (оценки) для следующего символа. Токены не взаимодействуют.

Loss измеряется через cross-entropy. Начальный loss ~4.87 (теоретический при равномерном распределении: -ln(1/65) ≈ 4.17). После 10 000 шагов обучения с AdamW loss падает до ~2.5. Генерация — авторегрессивная: softmax → multinomial sampling → конкатенация.

### Математический трюк: матричное умножение как взвешенное усреднение

Три версии одной операции — усреднение предыдущих токенов:

**Версия 1 (цикл for):** Для каждой позиции t вычисляем среднее всех предыдущих векторов. Работает, но медленно.

**Версия 2 (матричное умножение):** Создаём нижнетреугольную матрицу (T×T), нормализуем строки, умножаем на X. Результат идентичен, но параллельный.

**Версия 3 (softmax):** Начинаем с нулей, заполняем верхний треугольник значением -∞, применяем softmax. Получаем те же нормализованные веса. Этот вариант обобщается: нули можно заменить на data-dependent значения — и это уже self-attention.

### Self-Attention: ключевой механизм

Каждый токен генерирует три вектора через линейные проекции:
- **Query (Q):** «что я ищу»
- **Key (K):** «что я содержу»
- **Value (V):** «что я сообщу, если вы меня найдёте»

Афинность между токенами = Q · Kᵀ. Масштабирование: делим на √head_size, чтобы сохранить дисперсию ~1. Маскирование будущих позиций через -∞. Softmax → нормализованные веса. Агрегация: weights · V.

Важные замечания:
- Attention — механизм коммуникации на направленном графе
- Нет понятия пространства — нужны позиционные эмбеддинги
- Батчи обрабатываются независимо
- Encoder-блоки (полная связность) vs decoder-блоки (маскирование будущего)
- Self-attention (Q, K, V из одного источника) vs cross-attention (K, V из внешнего источника)

### Multi-Head Attention

Вместо одной «головы» с head_size=32 используются 4 головы по 8. Каждая голова ищет свой тип паттернов. Результаты конкатенируются обратно в 32-мерный вектор. Loss снижается с 2.4 до 2.28.

### FeedForward и Transformer-блок

FeedForward — это MLP: Linear → ReLU → Linear. Применяется к каждому токену независимо (per-node computation). Attention собрал информацию, feedforward её обработал.

Полный блок: MultiHeadAttention → residual + LayerNorm → FeedForward → residual + LayerNorm. Блоки повторяются N раз (в финальной модели — 4-6).

### Residual Connections и LayerNorm

Residual connections: x = x + block(x). Создают прямой путь для градиентов через глубокую сеть. Без них глубокие Transformer-ы не обучаются.

LayerNorm нормализует активации по последнему измерению. Используется pre-norm формулировка (LayerNorm до attention/feedforward), что отличается от оригинальной статьи.

### Dropout и финальная модель

Dropout добавляется после softmax в attention и в feedforward-слоях. Регуляризация через случайное зануление нейронов во время обучения.

Финальная модель: n_embd=384, 6 блоков, 6 голов, dropout=0.2. Val_loss ~1.48. Генерация выдаёт текст, стилистически похожий на Шекспира с правильной структурой диалогов.

### Масштабирование до GPT и ChatGPT

Путь от nanoGPT до ChatGPT:
1. **Pre-training:** обучение на интернет-данных (предсказание следующего токена)
2. **Fine-tuning:** обучение на примерах «вопрос → ответ»
3. **RLHF:** обучение с подкреплением на основе человеческих оценок

nanoGPT — это только первый этап. Fine-tuning и RLHF превращают «генератор текста» в «полезного ассистента».

## Практические задания

### Задание 1: Реализовать посимвольный токенизатор
Напишите с нуля функции encode() и decode() для посимвольной токенизации. Возьмите любой текстовый файл, постройте словарь уникальных символов, создайте маппинг символ→число и число→символ. Проверьте, что decode(encode(text)) == text. Сравните длину закодированной последовательности с результатом tiktoken.

### Задание 2: Реализовать взвешенное усреднение через матричное умножение
Создайте случайный тензор X размера (B, T, C) = (4, 8, 2). Реализуйте три версии усреднения предыдущих токенов: наивный цикл, умножение на нижнетреугольную матрицу и masked_fill + softmax. Убедитесь через torch.allclose, что все три версии дают идентичный результат.

### Задание 3: Построить и обучить биграммную модель
Реализуйте BigramLanguageModel как подкласс nn.Module. Обучите на tiny Shakespeare с batch_size=32 и lr=1e-3 на 10 000 итераций. Достигните loss ~2.5. Сгенерируйте 500 символов и оцените качество.

### Задание 4: Реализовать single-head self-attention
Добавьте head самовнимания с линейными проекциями Q, K, V. Реализуйте масштабирование, маскирование, softmax и агрегацию V. Обучите и убедитесь в снижении loss с ~2.5 до ~2.4.

### Задание 5: Собрать полный Transformer-блок
Объедините MultiHeadAttention + FeedForward + residual + LayerNorm в TransformerBlock. Сложите 4 блока, добавьте dropout. Целевой val_loss ~1.48.

### Задание 6: Эксперименты с гиперпараметрами
Измените количество голов (1, 4, 8), глубину (2, 4, 6), n_embd (32, 64, 128), dropout (0.0, 0.2, 0.4). Постройте таблицу результатов.

### Задание 7: Перенести на собственный датасет
Возьмите текст на русском языке (≥500 КБ). Адаптируйте токенизатор, обучите Transformer, оцените качество генерации.

## Лучшие цитаты

> «Attention is really just a communication mechanism. You can think about it as nodes in a directed graph, where every node has some vector of information and it gets to aggregate information via a weighted sum from all of the nodes that point to it.» — Андрей Карпати

> «The query vector roughly speaking is 'what am I looking for' and the key vector roughly speaking is 'what do I contain'.» — Андрей Карпати

> «These tokens have a lot to talk about — they want to find the consonants, the vowels, vowels from certain positions — and so it helps to create multiple independent channels of communication.» — Андрей Карпати

> «Self-attention does the communication, the feedforward does the computation. The tokens look at each other, but then they need to think about what they found.» — Андрей Карпати

> «We don't want these values to be too extreme especially at initialization, otherwise softmax will be way too peaky and you're basically aggregating information from a single node — that's not what we want.» — Андрей Карпати

> «Residual connections are these skip connections — instead of just going through the block, you fork off, do some computation, and come back. This is extremely important for training deep networks.» — Андрей Карпати

> «In a chunk of nine characters there's actually eight individual examples packed in there.» — Андрей Карпати

> «If you have unit Gaussian inputs and you just do the dot product naively, your variance will be on the order of head_size. But if you multiply by one over square root of head_size, the variance will be preserved at one.» — Андрей Карпати