architect/standards/local-ai/01_THEORY.md

01 — ТЕОРИЯ: Как работает локальный AI

Навигация: README | Архитектура →


Содержание

  1. Что такое LLM
  2. Токены — атомы текста
  3. Трансформер — сердце модели
  4. Матрицы — где хранятся знания
  5. Inference — как генерируется ответ
  6. Контекст — рабочая память
  7. Квантизация — как уменьшить модель
  8. Скорость — от чего зависит
  9. Системный промпт — как управлять поведением

1. Что такое LLM

LLM (Large Language Model) — математическая функция:

f(токены на входе) → вероятности следующего токена

Внутри — огромный набор чисел (веса/weights).
Qwen2.5-14B = 14 миллиардов чисел с плавающей точкой.

Модель не думает, не понимает, не знает.
Она предсказывает — какой токен идёт следующим
на основе паттернов из триллионов примеров обучения.

Но это предсказание настолько сложное,
что результат выглядит как понимание.

Аналогия:
Шахматный движок не "понимает" шахматы 
он перебирает позиции и выбирает лучший ход.
LLM не "понимает" язык  перебирает паттерны
и выбирает наиболее вероятное продолжение.

2. Токены — атомы текста

Модель не работает с буквами и не со словами.
Она работает с токенами — кусками текста.

Как работает токенизация (BPE)

BPE (Byte Pair Encoding) — алгоритм:

  1. Взять весь текст обучения
  2. Найти самые частые пары символов → объединить
  3. Повторять пока не наберётся N токенов (обычно 32K-100K)
Пример:
"привет как дела" →

Токены Qwen2.5 (примерно):
["▁при", "вет", "▁как", "▁дела"]

Каждый токен → число (ID):
"▁при" = 12847
"вет"  = 3291
"▁как" = 891
"▁дела" = 5632

Модель работает с числами: [12847, 3291, 891, 5632]

Сколько токенов в тексте

Правило большого пальца:
1 токен ≈ 0.75 слова (английский)
1 токен ≈ 0.5 слова (русский — кириллица дороже)

Примеры:
"Hello world"           = 2 токена
"Привет мир"            = 4 токена
Страница A4             ≈ 400-600 токенов
Книга 300 страниц       ≈ 120,000-180,000 токенов

Почему важно знать про токены

Контекст 8K токенов ≠ 8000 слов
На русском это ≈ 4000 слов ≈ 8-10 страниц A4

При выборе модели: context_length = максимум токенов
Qwen2.5-14B поддерживает 128K токенов ≈ 250 страниц

3. Трансформер — сердце модели

Qwen2.5 — это N слоёв трансформера (у 14B ≈ 40 слоёв).

Один шаг через один слой

Входной вектор токена [d=5120 чисел]
         
   ┌─────────────────────────────────┐
           RMSNorm                    нормализация
   └────────────┬────────────────────┘
                
   ┌─────────────────────────────────┐
         Self-Attention             
                                    
     Q = token × W_Q   "что ищу"   
     K = token × W_K   "что есть"  
     V = token × W_V   "что дать"  
                                    
     score = Q · K^T / d          
     weights = softmax(score)      
     out = weights × V             
                                    
     result = out × W_O            
   └────────────┬────────────────────┘
                
   + residual (добавляем вход)   важно! градиенты текут
                
   ┌─────────────────────────────────┐
           RMSNorm                  
   └────────────┬────────────────────┘
                
   ┌─────────────────────────────────┐
         Feed-Forward               
                                    
     h = token × W_1   [d4d]        расширение ×4
     h = SwiGLU(h)                   нелинейность
     out = h × W_2     [4dd]        сжатие обратно
   └────────────┬────────────────────┘
                
   + residual
                
   Выход слоя [d=5120 чисел]

Зачем нужен Self-Attention

Self-Attention позволяет каждому токену
"смотреть" на все другие токены в контексте.

Пример:
"Кот сидит на коврике. Он мурлычет."

Токен "Он" должен понять что он = кот, не коврик.
Self-Attention вычисляет:
- score("Он", "Кот") = 0.89   высокий
- score("Он", "коврике") = 0.12  низкий
 "Он" получает информацию от "Кот"
 модель понимает кореференцию

Зачем Feed-Forward

Feed-Forward — это "память фактов".
Attention находит КАКИЕ токены важны.
FF решает ЧТО с ними делать — применяет знания.

Исследования показали:
- Attention слои хранят синтаксис и структуру
- FF слои хранят факты и знания о мире

4. Матрицы — где хранятся знания

Что такое матрица весов

Матрица — это прямоугольная таблица чисел.

W_Q для 14B модели:
Размер: 5120 × 5120 = 26,214,400 чисел
Каждое число: float16 = 2 байта
Размер в памяти: ~50MB

Всего матриц в 14B: ~240
Суммарно: ~28GB в FP16, ~7GB в Q4

Как информация хранится в весах

Знания НЕ хранятся как база данных:

❌ W[1024][512] = "Париж — столица Франции"

Знания хранятся как геометрия пространства:

✅ Паттерн весов такой, что:
   вектор("Франция") + вектор("столица")
   → после умножения на матрицы →
   ближайший токен = "Париж"

Это распределённое представление.
Нет одного нейрона = "Париж".
Есть паттерн активации тысяч нейронов.

Размер матриц = интеллект модели

Qwen2.5:   d_model  слоёв  параметров
─────────────────────────────────────
7B:         3584      28     7B
14B:        5120      40     14B
72B:        8192      80     72B

Больше d_model → более богатое представление токенов
Больше слоёв  → более глубокие абстракции

5. Inference — как генерируется ответ

Один токен — один прогон через все слои

Шаг 1: Токенизация
"Привет!"  [14823, 1]  (2 токена)

Шаг 2: Embedding
Каждый токен  вектор [5120 чисел]
из таблицы embeddings (словарь  вектор)

Шаг 3: Прогон через 40 слоёв
[5120]  Layer_1  [5120]  Layer_2  ...  Layer_40  [5120]

Шаг 4: LM Head (языковая голова)
[5120] × W_lm_head  [152000 чисел]  (размер словаря)

Шаг 5: Softmax
[152000]  вероятности для каждого токена
"Привет" = 0.0001
"Как"    = 0.34     высокая
"дела"   = 0.28
...

Шаг 6: Sampling
Выбираем токен с учётом temperature:
temperature=0: всегда берём максимум ("Как")
temperature=1: случайно по вероятностям
temperature=2: больше рандома

Результат: токен "Как"  добавляем в контекст

Шаг 7: Повторить с новым контекстом
до токена <END> или max_tokens

Почему генерация последовательная

Каждый токен зависит от всех предыдущих.
Нельзя генерировать параллельно.
Это главное ограничение скорости LLM.


6. Контекст — рабочая память

Что такое контекстное окно

Контекст = плоский массив токенов который видит модель.

[системный промпт][история чата][текущий вопрос]
      ↑                ↑               ↑
  всегда на входе   прошлые ответы  новый запрос

Всё это умещается в контекстное окно.
Qwen2.5-14B: до 128,000 токенов.

Что происходит когда контекст заканчивается

Контекст полный → старые сообщения удаляются (sliding window)
                → или ошибка если жёсткий лимит
                → или суммаризация (если настроено)

Модель не помнит что было ДО текущего контекста.
Каждый вызов — чистый лист если не передавать историю.

KV-Cache — ускорение повторных запросов

При каждом шаге генерации нужны K и V матрицы
для всех предыдущих токенов.

Без KV-cache: пересчитывать K,V для ВСЕХ токенов на каждом шаге
              → O(n²) сложность → очень медленно

С KV-cache:   K,V вычислены один раз → сохранены в RAM
              → O(n) сложность → быстро

Цена KV-cache:
14B, 4K контекст:  ~1GB RAM
14B, 32K контекст: ~8GB RAM
14B, 128K контекст: ~32GB RAM

7. Квантизация — как уменьшить модель

Зачем нужна квантизация

Qwen2.5-14B полный (FP32): 14B × 4 байта = 56GB  ← не влезет
Qwen2.5-14B (FP16):        14B × 2 байта = 28GB  ← нужно много RAM
Qwen2.5-14B (Q4_K_M):      14B × 0.5 байта ≈ 7GB ← оптимально!

Форматы квантизации

Q8_0   — 8 бит, потеря ~1%,  ÷2 размер
Q6_K   — 6 бит, потеря ~2%,  ÷2.7 размер
Q5_K_M — 5 бит, потеря ~2%,  ÷3.2 размер
Q4_K_M — 4 бит, потеря ~3-5%, ÷4 размер  ← ЛУЧШИЙ ВЫБОР
Q3_K_M — 3 бит, потеря ~7%,  ÷5 размер
Q2_K   — 2 бит, потеря ~15%, ÷6 размер   ← плохо

Рекомендация: Q4_K_M
- ÷4 меньше памяти
- только ~3-5% потери качества
- поддерживается llama.cpp / Ollama

Как это работает

FP16 вес:  0.3847656  (16 бит, точно)
Q4 вес:    6          (4 бита, 0-15)
           + масштаб блока: 0.0256

Восстановление: 6 × 0.0256 = 0.1536  (≈ но не точно)

Потеря точности ≈ 3-5% на типичных задачах.
На практике: почти незаметна при Q4_K_M.

8. Скорость — от чего зависит

Главный фактор — пропускная способность памяти

tok/s ≈ RAM_bandwidth / model_size_in_bytes

Почему:
Для каждого токена нужно прочитать ВСЕ веса модели
(прогнать данные через все 240 матриц)

Qwen2.5-14B Q4 = 7GB
Нужно читать 7GB на каждый токен

Сравнение конфигураций

Конфигурация                  Bandwidth    tok/s (14B Q4)
─────────────────────────────────────────────────────────
Старый ноутбук DDR3 2-ch      25 GB/s      ~3 tok/s
Современный ПК DDR4 2-ch      42 GB/s      ~5 tok/s
Б/У сервер DDR4 4-ch           60 GB/s      ~8 tok/s
Б/У сервер DDR4 8-ch           85 GB/s      ~12 tok/s
Mac Mini M4 unified memory    120 GB/s      ~17 tok/s
RTX 3090 GDDR6X (24GB)        936 GB/s      ~130 tok/s ← если влезает

AVX2 — важен для CPU инференса

Без AVX2: llama.cpp использует скалярные операции
С AVX2:   vectorized операции, ×1.5-2 быстрее

AVX2 появился в:
- Intel Haswell (2014) — 4-е поколение Core, Xeon v3
- AMD Ryzen (2017) — все современные AMD

Проверить: grep avx2 /proc/cpuinfo

GPU vs CPU

GPU:
+ В 10-100× быстрее
- Модель должна ВЛЕЗАТЬ в VRAM
- RTX 3090 24GB  до 20B Q4

CPU:
+ Любой размер RAM
+ Дешевле
- Медленнее
- Нужно AVX2

Гибрид (GPU+CPU offloading):
- Часть слоёв на GPU, часть на CPU
- Ollama делает это автоматически

9. Системный промпт — как управлять поведением

Что такое системный промпт

Специальный токен-блок в начале контекста
который задаёт "личность" и правила поведения.

Контекст модели:
[<|system|>
Ты краткий ассистент. Отвечай по делу.
<|/system|>]
[<|user|>
Что такое Python?
<|/user|>]
[<|assistant|>]   модель генерирует отсюда

Почему системный промпт работает

При обучении (SFT) модель видела миллионы примеров:

[system: "Ты учитель"]  подробные объяснения
[system: "Ты кодер"]    короткий код без объяснений
[system: "Ты враг"]     плохие ответы

Модель ВЫУЧИЛА: содержимое [system] = правила игры

Что можно задать системным промптом

Личность:     "Ты краткий технический ассистент"
Стиль:        "Отвечай только кодом, без объяснений"
Язык:         "Всегда отвечай по-русски"
Ограничения:  "Не обсуждай политику"
Формат:       "Используй Markdown, код в блоках"
Роль:         "Ты старший разработчик Python"
Контекст:     "Мы работаем над Drupal сайтом..."

Ограничения системного промпта

НЕ меняет веса модели — только направляет
НЕ гарантирует поведение — модель может отклониться
НЕ добавляет знания — только стиль ответов
НЕ работает как код — это просто текст

Итог

LLM = большая матрица весов (14B чисел)
    + алгоритм умножения (трансформер)
    + квантизация (уменьшить до 7GB)
    + движок (llama.cpp внутри Ollama)

Скорость ≈ RAM bandwidth / размер модели
Качество ≈ количество параметров × качество обучения
Поведение = системный промпт + fine-tuning

Для локального AI нужно:
1. Достаточно RAM (модель + KV-cache)
2. Быстрая память (DDR4, желательно много каналов)
3. AVX2 процессор (Haswell 2014+)
4. Ollama как движок
5. Системный промпт для личности

Следующий документ: 02_ARCHITECTURE.md