File size: 18,888 Bytes
a3d8a06
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
---
language:
- ru
tags:
- wiki
- ru_wiki
- wikipedia
---
# SunboxDevLLM - Русскоязычная языковая модель

Модель доступна для использования: **[chat.sunbox.dev](https://chat.sunbox.dev)**

Репозиторий: **[GitHub](https://github.com/it-efrem/llm)**

Decoder-only Transformer языковая модель, построенная полностью с нуля — BPE-токенизатор, архитектура модели, цикл обучения и генерация — на Python + PyTorch (~2500 строк кода).

## Архитектура

| Компонент    | Решение                             | Обоснование                                         |
| ------------ | ----------------------------------- | --------------------------------------------------- |
| Нормализация | **RMSNorm** (pre-norm)              | Стандарт LLaMA/Mistral, ~10-15% быстрее LayerNorm   |
| Позиции      | **RoPE** (split + cat, без complex) | Лучшая экстраполяция длины, совместимость с MPS/AMP |
| FFN          | **SwiGLU** (3 проекции)             | Выше качество при том же вычислительном бюджете     |
| Attention    | **F.scaled_dot_product_attention**  | Flash Attention на CUDA, math fallback на MPS       |
| Генерация    | **KV cache** (prefill + decode)     | O(S) на новый токен вместо O(S²)                    |
| Веса         | **Weight tying** (embed ↔ lm_head)  | Экономия ~24.6M параметров                          |
| AMP          | autocast + GradScaler               | CUDA и MPS (PyTorch 2.2+)                           |

## Конфигурации модели

| Вариант  | d_model | heads | layers | ff_dim | Параметры |
| -------- | ------- | ----- | ------ | ------ | --------- |
| **Full** | 768     | 12    | 12     | 2048   | ~110M     |

## Установка

```bash
# Клонировать репозиторий и перейти в корень проекта (имя папки может отличаться)
cd llm

# Создать виртуальное окружение
python3 -m venv .venv
source .venv/bin/activate

# Установить зависимости
pip install -r requirements.txt
```

Чекпоинты весов (`models/*.pt`) не коммитятся (см. `.gitignore`). Для примеров ниже положите файлы `base_step_15000.pt` и `chat_step_500.pt` в каталог `models/` или передайте свой путь в `--checkpoint`. Для BPE нужен `data/vocab.json` из того же обучения (или `--vocab`), если путь из чекпоинта на этой машине не существует.

### Зависимости

| Пакет                | Обязательный | Назначение                                          |
| -------------------- | :----------: | --------------------------------------------------- |
| `torch>=2.2.0`       |      да      | PyTorch (CUDA / MPS / CPU)                          |
| `numpy>=1.20`        |      да      | Memmap для pretokenized-корпусов                    |
| `regex>=2023.0`      |      да      | Unicode-aware pre-tokenization (GPT-2 паттерн)      |
| `transformers>=4.20` |     нет      | Адаптер HuggingFace-токенизатора (`--tokenizer`)    |
| `tokenizers>=0.15`   |     нет      | Быстрое BPE-обучение через Rust (`train_bpe_hf.py`) |
| `gensim>=4.0`        |     нет      | Парсинг дампа Wikipedia (`data/wiki_download.py`)   |

## Быстрый старт

### Полный пайплайн одной командой

```bash
bash run_train.sh
```

Скрипт выполнит: обучение BPE-токенизатора → pre-training на Wikipedia → fine-tuning на чат-корпусе.

### Пошаговый запуск

#### 1. Обучение BPE-токенизатора

```bash
# Собственная реализация (чистый Python, ~20-50x быстрее наивного алгоритма)
python train_bpe.py --corpus data/wiki_text/corpus.txt --vocab-size 32000

# Или через HuggingFace tokenizers (Rust, ~100-1000x быстрее на больших корпусах)
python train_bpe_hf.py --corpus data/wiki_text/corpus.txt --vocab-size 32000
```

#### 2. Pretokenization корпуса (для больших датасетов)

Для корпусов >1 GB рекомендуется pretokenization — после неё обучение стартует мгновенно:

```bash
python pretokenize_corpus.py \
  --corpus data/wiki_text/corpus_15gb.txt \
  --output data/corpus_15gb.bin \
  --workers 8
```

#### 3. Pre-training

```bash
# На текстовом корпусе (токенизация при старте)
python train.py --corpus data/wiki_text/corpus.txt \
  --batch-size 32 --max-steps 100000 --save-every 5000

# На pretokenized-корпусе (мгновенный старт)
python train.py --pretokenized data/corpus_15gb.bin \
  --batch-size 64 --grad-accum 4 --max-steps 30000

# С ранней остановкой по perplexity
python train.py --pretokenized data/corpus_15gb.bin \
  --stop-at-perplexity 10.0
```

#### 4. Fine-tuning на чат-корпусе

```bash
# --init-from загружает только веса модели (свежий optimizer/scheduler)
python train.py --corpus data/wiki_text/corpus_chat.txt --chat \
  --init-from models/base_step_15000.pt \
  --lr 1e-4 --batch-size 16 --max-steps 2000 --save-every 250

# --resume загружает полное состояние (продолжение прерванного обучения)
python train.py --corpus data/wiki_text/corpus_chat.txt --chat \
  --resume models/chat_step_500.pt \
  --max-steps 3000
```

## Генерация текста

### Рекомендованные параметры

Параметры подобраны тестированием на `base_step_15000` (pre-trained) и `chat_step_500` (chat fine-tuned)
на 8 различных промптах по 9 конфигурациям каждый. Значения по умолчанию в коде (`GenerateConfig`)
установлены как универсальный компромисс: `temperature=0.5`, `repetition_penalty=1.15`.

| Режим                       | temperature | repetition_penalty | top_p | Комментарий                                  |
| --------------------------- | :---------: | :----------------: | :---: | -------------------------------------------- |
| **Текст** (pre-trained)     |     0.3     |        1.2         |   —   | Максимально связные дополнения, минимум шума |
| **Чат** (fine-tuned)        |     0.5     |        1.1         |   —   | Лучший баланс точности и разнообразия        |
| **Универсальный** (default) |     0.5     |        1.15        |   —   | Разумный компромисс для обоих режимов        |
| Креативный                  |     0.7     |        1.15        |  0.9  | Больше разнообразия, выше риск галлюцинаций  |

Примеры запуска с оптимальными параметрами:

```bash
# Дополнение текста (pre-trained checkpoint)
python generate.py --checkpoint models/base_step_15000.pt \
  --prompt "Москва — столица" --temperature 0.3 --repetition-penalty 1.2 --stream

# Чат (fine-tuned checkpoint) — оптимальные параметры
python generate.py --checkpoint models/chat_step_500.pt \
  --chat --temperature 0.5 --repetition-penalty 1.1

# Без явных параметров — используются defaults из GenerateConfig
python generate.py --checkpoint models/chat_step_500.pt \
  --prompt "Искусственный интеллект — это" --stream
```

### Одиночный prompt

```bash
# Streaming (токены выводятся по одному)
python generate.py --checkpoint models/chat_step_500.pt \
  --prompt "Москва — столица" --stream

# Полный ответ сразу (без --stream)
python generate.py --checkpoint models/chat_step_500.pt \
  --prompt "Москва — столица" --max-tokens 100
```

### Prompt из файла

```bash
python generate.py --checkpoint models/chat_step_500.pt \
  --prompt-file my_prompt.txt --stream
```

### Интерактивный чат

```bash
# Базовый режим (каждый вопрос независимый)
python generate.py --checkpoint models/chat_step_500.pt \
  --chat --temperature 0.5 --repetition-penalty 1.1

# С сохранением контекста (sliding window по границам реплик)
python generate.py --checkpoint models/chat_step_500.pt \
  --chat --keep-history
```

Пример сессии:

```
=== Chat Mode (type 'quit' to exit) ===

You: Что такое машинное обучение?
Assistant: Машинное обучение — раздел ИИ: алгоритмы, которые улучшают своё поведение
на основе данных без явного программирования каждого шага.

You: Что такое фотосинтез?
Assistant: Фотосинтез — процесс образования органических веществ из CO₂ и воды
на свету при участии хлорофилла. Выделяется кислород.

You: quit
```

### Параметры генерации

| Параметр               | По умолчанию | Описание                                                  |
| ---------------------- | :----------: | --------------------------------------------------------- |
| `--temperature`        |     0.5      | Температура сэмплирования (ниже = детерминированнее)      |
| `--top-k`              |      —       | Ограничение по top-k токенам                              |
| `--top-p`              |      —       | Nucleus sampling (top-p)                                  |
| `--max-tokens`         |     256      | Максимум новых токенов                                    |
| `--repetition-penalty` |     1.15     | Штраф за повторения (1.0 = выкл, рекомендуется 1.1-1.2)   |
| `--stream`             |     off      | Потоковый вывод токенов                                   |
| `--chat`               |     off      | Интерактивный чат-режим                                   |
| `--keep-history`       |     off      | Сохранение контекста диалога между репликами              |
| `--vocab`              |     auto     | Путь к vocab.json (если чекпоинт обучен на другой машине) |

## Параметры обучения

| Параметр CLI           | Конфиг               | По умолчанию | Описание                           |
| ---------------------- | -------------------- | :----------: | ---------------------------------- |
| `--max-steps`          | `max_steps`          |    23000     | Максимум шагов                     |
| `--batch-size`         | `batch_size`         |      32      | Размер батча                       |
| `--lr`                 | `learning_rate`      |     6e-4     | Learning rate                      |
| `--grad-accum`         | `grad_accum_steps`   |      4       | Gradient accumulation              |
| `--warmup`             | `warmup_steps`       |     2000     | Шаги warmup                        |
| `--save-every`         | `save_every_steps`   |     2000     | Частота сохранения                 |
| `--seq-len`            | `max_seq_len`        |     1024     | Длина контекста                    |
| `--no-amp`             | `use_amp`            |     True     | Отключить AMP                      |
| `--stop-at-loss`       | `stop_at_loss`       |      —       | Ранняя остановка по loss           |
| `--stop-at-perplexity` | `stop_at_perplexity` |      —       | Ранняя остановка по val perplexity |

LR scheduler: linear warmup → cosine annealing (single cycle).

## Подготовка данных

### Скачивание Wikipedia

```bash
python data/wiki_download.py --max-mb 500 --corpus-file data/wiki_text/corpus.txt
```

### Очистка корпуса

Удаляет блоки EasyTimeline, галереи, лишние пустые строки:

```bash
python clean_corpus.py --input data/wiki_text/corpus.txt \
  --output data/wiki_text/corpus_cleaned.txt --remove-gallery

# Только статистика без записи
python clean_corpus.py --dry-run
```

### Обрезка по размеру

```bash
python trim_corpus.py --input data/wiki_text/corpus_cleaned.txt \
  --output data/wiki_text/corpus_500mb.txt --mb 500
```

### Генерация чат-корпуса

```bash
python data/wiki_text/generate_chat_corpus.py
```

Создаёт `corpus_chat.txt` из пула Q&A-пар на основе seed-данных (столицы, события, персоны и т.д.).

## Структура проекта

```
llm/
├── config.py                   # ModelConfig, TrainConfig, GenerateConfig, PathsConfig
├── train.py                    # CLI обучения
├── generate.py                 # CLI генерации и интерактивного чата
├── train_bpe.py                # Обучение BPE (собственная реализация)
├── train_bpe_hf.py             # Обучение BPE (HuggingFace Rust backend)
├── pretokenize_corpus.py       # Pretokenization корпуса в .bin (multiprocessing)
├── clean_corpus.py             # Очистка wiki-корпуса
├── trim_corpus.py              # Обрезка корпуса по размеру
├── run_train.sh                # Полный пайплайн одной командой

├── model/
│   ├── transformer.py          # TransformerLM (блоки + lm_head + weight tying)
│   ├── block.py                # DecoderBlock (pre-norm) + SwiGLUFFN
│   ├── attention.py            # MultiHeadAttention (RoPE + SDPA + KV cache)
│   ├── rope.py                 # Rotary positional embeddings
│   ├── norm.py                 # RMSNorm
│   └── kv_cache.py             # KV cache для генерации

├── tokenizer/
│   ├── vocab.py                # Словарь (tuple pair keys, без overflow)
│   ├── bpe.py                  # BPETokenizer (pre-tokenize + merge ranks + LRU)
│   ├── bpe_train_fast.py       # Быстрое обучение BPE (doubly-linked list + heap)
│   ├── pretokenizer.py         # GPT-2 regex word splitter
│   └── hf_adapter.py           # Адаптер HuggingFace-токенизатора

├── data/
│   ├── wiki_dataset.py         # WikiChunkDataset + PretokenizedDataset (memmap)
│   ├── chat_dataset.py         # ChatChunkDataset (маскирование loss на user-части)
│   ├── wiki_download.py        # Скачивание/парсинг дампа Wikipedia
│   └── wiki_text/              # Корпуса и генераторы чат-данных

├── training/
│   ├── trainer.py              # AMP, gradient accumulation, валидация, логирование
│   ├── checkpoint.py           # Сохранение/загрузка полного состояния
│   └── scheduler.py            # Warmup + CosineAnnealingWarmRestarts

├── models/                     # *.pt в .gitignore — веса кладутся локально
│   ├── base_step_15000.pt      # Pre-trained checkpoint (Wikipedia 15GB)
│   └── chat_step_500.pt        # Fine-tuned checkpoint (chat corpus)

├── inference/
│   └── generator.py            # KV cache prefill/decode, streaming, sampling

└── utils/
    ├── device.py               # Детекция CUDA/MPS/CPU и платформенные настройки
    └── log_setup.py            # Конфигурация логирования
```

## Совместимость

| Платформа                     | Поддержка | Особенности                                            |
| ----------------------------- | :-------: | ------------------------------------------------------ |
| **CUDA GPU** (A100, V100, T4) |  полная   | Flash Attention, GradScaler, pin_memory, num_workers=4 |
| **Apple MPS** (M1/M2/M3/M4)   |  полная   | SDPA math fallback, unified memory, num_workers=0      |
| **CPU**                       |  базовая  | bfloat16 autocast где поддерживается                   |

## Отличия от оригинального llm

1. **CUDA-совместимость**: единый `get_device()` с приоритетом CUDA > MPS > CPU
2. **BPE без overflow**: tuple-ключи `(id_a, id_b)` вместо bit-shift (vocab > 65536)
3. **Быстрый BPE encode**: pre-tokenization (GPT-2 regex) + merge rankings + LRU cache
4. **RoPE вместо sinusoidal**: лучшая экстраполяция, без отдельного embedding-слоя
5. **RMSNorm вместо LayerNorm**: быстрее, соответствует LLaMA/Mistral
6. **SwiGLU вместо GELU FFN**: выше качество при том же compute
7. **KV cache при генерации**: O(S) на токен вместо O(S²) полного re-forward
8. **Weight tying**: embed и lm_head разделяют веса, экономия ~24.6M параметров
9. **Полное состояние чекпоинтов**: модель + optimizer + scheduler + scaler + step
10. **Grad norm до clipping**: точный мониторинг здоровья градиентов
11. **Pre-tokenized datasets**: O(1) `__getitem__`, корректное маскирование loss для чата
12. **GPT-2 инициализация**: `N(0, 0.02)` + residual scaling `0.02 / sqrt(2N)`