# Byte Pair Encoding (BPE) Algorithm

> **Документ актуален для Simple-LLM v1.0 (июль 2025)**

---

**Краткое summary:**
Этот документ подробно описывает алгоритм Byte Pair Encoding (BPE) — как он используется для токенизации текста, как устроен процесс обучения словаря и как происходит энкодинг/декодинг текста. Документ предназначен для пользователей Simple-LLM и всех, кто хочет понять внутреннюю механику BPE.

---

**Структура документа:**
- Введение
- Основные понятия
- Алгоритм работы (обучение словаря)
- Псевдокод
- Пример работы
- Алгоритм энкодинга (токенизации)
- Алгоритм декодирования
- Типовые ошибки и их решения

---

## Введение

Byte Pair Encoding (BPE) - это алгоритм компрессии данных, адаптированный для токенизации текста в обработке естественного языка. В контексте языковых моделей BPE используется для создания эффективного словаря подстрок (токенов).

## Основные понятия

- **Токен** - элементарная единица текста (символ или последовательность символов)
- **Словарь** - набор уникальных токенов, используемых для представления текста
- **Частота пары** - количество раз, когда два токена встречаются вместе в тексте

## Алгоритм работы

### 1. Инициализация
```python
Исходный текст → Разбить на символы → Первоначальный словарь
```
Пример:
```
"мама" → ['м', 'а', 'м', 'а']
```

### 2. Основной цикл
```mermaid
graph TD
    A[Подсчет частот пар] --> B[Выбор наиболее частой пары]
    B --> C[Создание нового токена]
    C --> D[Обновление последовательности]
    D --> E{Достигнут лимит словаря?}
    E -->|Нет| A
    E -->|Да| F[Конец]
```

### 3. Детализация шагов

#### Шаг 1: Подсчет частот пар
Для текущей последовательности токенов подсчитываем все пары соседних токенов:
```
Текст: "мама мыла"
Токены: ['м', 'а', 'м', 'а', ' ', 'м', 'ы', 'л', 'а']
Пары: ('м','а'), ('а','м'), ('м','а'), ('а',' '), (' ','м'), ('м','ы'), ('ы','л'), ('л','а')
```

#### Шаг 2: Выбор пары для слияния
Находим пару с максимальной частотой. При равенстве частот выбираем пару, которая встречается раньше в тексте.

#### Шаг 3: Слияние
Объединяем выбранную пару в новый токен и заменяем все её вхождения в тексте:
```
Выбранная пара: ('м', 'а')
Новый токен: 'ма'
Обновленная последовательность: ['ма', 'ма', ' ', 'м', 'ы', 'л', 'а']
```

#### Шаг 4: Обновление словаря
Добавляем новый токен в словарь:
```
Словарь: ['м', 'а', ' ', 'ы', 'л', 'ма']
```

### 4. Критерии остановки

1. Достижение заданного размера словаря
2. Отсутствие пар для слияния (все возможные пары уже добавлены)
3. Достижение максимального числа итераций

## Псевдокод

```python
def train_bpe(text, vocab_size):
    # Инициализация
    tokens = list(text)
    vocab = set(tokens)
    
    while len(vocab) < vocab_size:
        # Подсчет пар
        pairs = get_pairs(tokens)
        if not pairs:
            break
            
        # Выбор наиболее частой пары
        best_pair = max(pairs, key=pairs.get)
        
        # Слияние
        new_tokens = []
        i = 0
        while i < len(tokens):
            if i < len(tokens)-1 and (tokens[i], tokens[i+1]) == best_pair:
                new_tokens.append(best_pair[0] + best_pair[1])
                i += 2
            else:
                new_tokens.append(tokens[i])
                i += 1
        tokens = new_tokens
        
        # Обновление словаря
        vocab.add(best_pair[0] + best_pair[1])
    
    return vocab
```

## Пример работы

**Исходный текст**: "мама мыла раму"

**Итерация 1**:
- Пара ('м','а') встречается 2 раза
- Новый токен: 'ма'
- Текст: ['ма', 'ма', ' ', 'м', 'ы', 'л', 'а', ' ', 'р', 'а', 'м', 'у']

**Итерация 2**:
- Пара ('ма',' ') встречается 1 раз
- Новый токен: 'ма '
- Текст: ['ма ', 'ма', 'мы', 'л', 'а', ' ', 'р', 'а', 'м', 'у']

**Результирующий словарь** (частично):
['м', 'а', ' ', 'ы', 'л', 'р', 'у', 'ма', 'ма ', 'мы']

## Алгоритм энкодинга (токенизации)

После обучения BPE-модели (создания словаря), энкодинг текста происходит по следующему алгоритму:

```mermaid
graph TD
    A[Начало] --> B[Разбить текст на символы]
    B --> C[Инициализировать пустой список токенов]
    C --> D[Установить i=0 'начало последовательности']
    D --> E{i < длины текста?}
    E -->|Нет| F[Заменить токены на их ID]
    E -->|Да| G[Найти все токены в словаре, начинающиеся с text_i]
    G --> H[Выбрать самый длинный подходящий токен]
    H --> I[Добавить токен в результат]
    I --> J[Увеличить i на длину токена]
    J --> E
    F --> K[Конец]
```

Пошаговое описание:
1. **Инициализация**:
   - Исходный текст разбивается на символы
   - Создается пустой список для результата
   - Указатель `i` устанавливается в 0

2. **Основной цикл**:
   - Для текущей позиции `i` находим все токены в словаре, которые:
     - Начинаются с символа `text[i]`
     - Совпадают с подстрокой `text[i:i+len(token)]`
   - Из подходящих токенов выбираем самый длинный
   - Добавляем найденный токен в результат
   - Сдвигаем указатель `i` на длину добавленного токена

3. **Завершение**:
   - Когда весь текст обработан, заменяем токены на их ID из словаря

Пример:
```python
Словарь: ['ма', 'мама', ' ', 'мыл', 'а', 'раму']
Текст: "мама мыла раму"

Энкодинг:
1. Находим самый длинный токен, начинающийся с 'м' -> 'мама'
2. Добавляем 'мама', i += 4
3. Токен ' ' (пробел), i += 1
4. Токен 'мыл', i += 3
5. Токен 'а', i += 1
6. Токен ' ', i += 1
7. Токен 'раму', i += 4

Результат: ['мама', ' ', 'мыл', 'а', ' ', 'раму']
```

## Алгоритм декодирования

### Принцип работы
1. Преобразование списка ID обратно в текст
2. Замена каждого ID на соответствующий токен
3. Обработка неизвестных ID (`[UNK]`)

```mermaid
flowchart TD
    A[Список ID] --> B{ID ∈ словарь?}
    B -->|Да| C[Добавить токен]
    B -->|Нет| D[Добавить UNK]
    C --> E[Следующий ID]
    D --> E
    E --> F{Конец списка?}
    F -->|Нет| B
    F -->|Да| G[Объединить токены]
```

Пример:
```python
decode([0,1,2]) → "абра"  # Для словаря {0:"а", 1:"б", 2:"ра"}
```

## Применение в языковых моделях

1. Эффективное представление редких слов
2. Снижение размерности входных данных
3. Возможность обработки OOV (Out-of-Vocabulary) слов

## Ограничения

1. Чувствительность к регистру (можно решить предварительной нормализацией)
2. Зависимость от обучающего корпуса
3. Не всегда выделяет лингвистически осмысленные морфемы

## Дополнительные материалы

1. [Original BPE paper](https://arxiv.org/abs/1508.07909)
2. [BPE in HuggingFace](https://huggingface.co/docs/transformers/tokenizer_summary)
3. [Practical guide to BPE](https://towardsdatascience.com/byte-pair-encoding-subword-based-tokenization-algorithm-77828a70bee0)
