---
title: "Нейросети простым языком"
url: "https://romankryvolapov.com/ru/neural-networks-basics/"
description: "Как устроена LLM: токенизация, embedding, attention в трансформере, квантование и семплеры, которые определяют выбор следующего токена."
language: ru
updated: 2026-08-03
---
<span id="0787"></span>

**Привет!**

<span id="c7e3"></span>

В интернете можно найти разные объяснения того, как работают нейросети, но те, что мне попадались, были либо слишком специфичны и ориентированы на специалистов, либо слишком упрощены.

<span id="2497"></span>

Постарался написать свои объяснения, которые не были бы слишком упрощены, но при этом оставались по возможности понятными.

<span id="8d9e"></span>

Статья на 10 процентов скомпилирована из других статей, на 30 процентов скомпилирована из множества диалогов с разными LLM и на 60 процентов “написана от руки” на основании статей и ответов.

## Входящие данные

На входе нейросеть получает входящие данные в виде запроса пользователя. К запросу пользователя также добавляется общая информация, которая позволяет получить более точный ответ. Если нейросеть поддерживает RAG (Retrieval-Augmented Generation), то к запросу добавляются также:

- Данные из векторной базы, если в нее предварительно добавили то, что нужно для генерации более актуального ответа

- Данные, извлечённые из интернет-страницы или документа — чтобы сократить их размер, часто используется специализированная embedding нейросеть: она находит все подходящие куски текста и добавляет их к контексту запроса

Пример настроек embedding нейросети для Chrome расширения Page Assist

![](/images/2019/02/image-1-1024x649.png)

## Как работает RAG

Система состоит из двух основных компонентов

**Retrieval (извлечение):**\
Ищет релевантную информацию из внешней базы знаний (чаще всего — векторная база, например, FAISS, Qdrant, Weaviate). Эта база обычно построена заранее из текстовых документов (pdf, markdown, html и др.), преобразованных в эмбеддинги с помощью модели (например, BERT, Instructor, или SentenceTransformer).

**Generation (генерация):**\
LLM (GPT, LLaMA, Mistral и др.) получает исходный запрос + найденные документы и генерирует финальный ответ.

Не путайте два разных «embedding» в этой статье: здесь речь про отдельную embedding-модель, которая превращает целый фрагмент текста в один вектор, чтобы искать похожие фрагменты; дальше, в шаге 2, — про token embedding, векторы отдельных токенов внутри самой LLM. Механика похожа, но это разные модели и разные векторы.

**Про RAG и function calling у меня есть отдельная статья:**

[/ru/rag-and-function-calling/](/ru/rag-and-function-calling/)

**И отдельная — про векторные базы:**

[/ru/vector-databases/](/ru/vector-databases/)

В некоторых нейросетях для отделения запроса пользователя, контекста, полученного при помощи RAG и общего сообщения используется разметка.

**Пример:** 

**context:**\
это данные полученные из интернет при помощи embedding нейросети или из векторного хранилища и отсортированные по совпадению score по мере убывания

```json
{
  "context": [
    "Пенициллин — это первый открытый антибиотик, произведённый из плесневого грибка рода Penicillium. Он используется для лечения бактериальных инфекций, таких как ангина, сифилис и пневмония. Пенициллин разрушает клеточную стенку бактерий, что приводит к их гибели.",
    "Некоторые люди имеют аллергию на пенициллин. Это может вызвать серьёзные реакции, включая анафилаксию, поэтому перед назначением препарата важно уточнить наличие аллергии у пациента."
  ],
  "instructions": "Ответь простыми словами, используя только информацию из контекста. Если ответа нет — напиши 'Информация не найдена'.",
  "question": "Что такое пенициллин и для чего он применяется?"
}
```

## Из чего состоит модель

### Трансформер (Transformer) {#bc1f}

<span id="bd99"></span>

Архитектура нейросети, которая стала основой для современных языковых моделей, таких как ChatGPT, BERT, LLaMA, Gemma и многие другие.\
Он был впервые описан в научной статье 2017 года под названием “Attention is All You Need”.\
Проще говоря, трансформер — это “умная машина”, которая умеет читать и понимать текст, обрабатывая все слова одновременно, а не по одному, как это делали предыдущие модели (например, RNN, LSTM).

### Слои трансформера (Transformer Layers) {#d621}

<span id="1b47"></span>

это повторяющиеся блоки, через которые проходит каждый токен внутри модели.

<span id="e0b5"></span>

**Один слой (или “трансформер-блок”) включает в себя:**

<span id="b861"></span>

**Self-Attention:\**
токен “смотрит” на другие токены и решает, кто важен.

<span id="aad9"></span>

**Feed-Forward Network:**\
уточнение и преобразование каждого токена.

<span id="d353"></span>

**Layer Normalization или LayerNorm:**\
стабилизация вычислений.

<span id="1648"></span>

**Residual Connections:**\
чтобы модель не “забыла” начальную информацию.

<span id="2156"></span>

LayerNorm может быть до или после Residual (Pre-LN vs Post-LN). Практически все современные LLM (Llama, Qwen, Gemma, DeepSeek) используют Pre-LN, а вместо классического LayerNorm — его упрощённый вариант RMSNorm. Шаги дальше в статье описаны по классической схеме Post-LN, так нагляднее.

<span id="b165"></span>

и другие этапы.

<span id="5f62"></span>

Каждый слой трансформера (Transformer Block) — это отдельный набор параметров, которые:\
независимо обучаются,\
независимо применяются к входным данным,\
дают всё более “глубокое” понимание смысла и контекста.

<span id="085b"></span>

**Что значит “32 слоя”:**\
каждый токен проходит через 32 такие операции, последовательно одну за другой, после каждого слоя токен становится всё более информированным, т.е. его представление (вектор) всё глубже отражает контекст

<span id="61ef"></span>

**Количество слоев популярных открытых моделей (transformer layers, по официальным конфигурациям, середина 2026 года):**

```text
| Модель                    | Количество слоёв |
|---------------------------|------------------|
| Llama 3.1 (8B)            | 32               |
| Llama 3.1 (70B)           | 80               |
| Llama 4 Scout (109B)      | 48               |
| Gemma 3 (12B)             | 48               |
| Gemma 3 (27B)             | 62               |
| Gemma 4 (31B)             | 60               |
| Qwen 3 (8B)               | 36               |
| Qwen 3 (32B)              | 64               |
| Qwen 3 (235B-A22B)        | 94               |
| Qwen 3.5 (9B)             | 32               |
| DeepSeek V3 / R1 / V4 Pro | 61               |
| DeepSeek V4 Flash         | 43               |
| GPT-OSS (20B)             | 24               |
| GPT-OSS (120B)            | 36               |
```

У MoE-моделей (Mixture of Experts — Llama 4, Qwen 3 235B-A22B, DeepSeek V3/V4, GPT-OSS) внутри каждого слоя вместо одного FFN-блока лежит набор «экспертов», из которых для каждого токена активируется лишь небольшая часть: у Qwen 3 235B-A22B из 235 миллиардов параметров на каждый токен работают только 22 миллиарда.

### Активация (Activation) {#9ca7}

<span id="83d1"></span>

это промежуточные выходы нейросети после применения функций и слоёв.\
Можно сказать, что активации это данные, которые "живут внутри сети" на каждом этапе прохода входа через модель.

### Тензор (Tensor) {#715c}

<span id="a64d"></span>

в нейросети это многомерный массив чисел, с которым работают слои модели.\
**Входные данные:**\
Текст → токены → эмбеддинги → тензор batch_size × seq_len × embedding_dim.\
**Веса модели:**\
Весовые матрицы в слоях — тоже тензоры.\
**Промежуточные представления (активации):**\
Выход каждого слоя (например, LayerNorm, Attention) — тензор.\
**Градиенты:**\
При обучении модель считает градиенты (тензоры) для обновления весов.

## Шаги работы нейросети

## Шаг 1: Токенизация (из текста / изображения в токены) {#Шаг-1-Токенизация-из-текста-изображения-в-токены}

### Токенизация текста

<span id="c30b"></span>

LLM не работает со словами напрямую — она работает с токенами (частями слов или символами), преобразованными в числа.

<span id="6d3e"></span>

**Пример:**

```kotlin
Текст пользователя:
"Привет, мир"

ID токена в словаре модели для слова:
"Привет" = 1123
"," = 15
"мир" = 345

Получаем массив ID токенов:
["Привет", ",", "мир"] = [1123, 15, 345]
```

<span id="98bc"></span>

**Как это достигается:**\
Используется алгоритм вроде Byte Pair Encoding (BPE), Unigram, WordPiece или SentencePiece.\
BPE-токенизатор находит наиболее частые пары символов.\
WordPiece строит токены на основе вероятности иерархических разбиений.\
Часто токены — это не отдельные слова, а части слов.

В словаре есть специальные маркеры границ слов: в BPE-словарях пробел кодируется в начале токена, а в WordPiece маркером “##” помечаются токены-продолжения слова. Поэтому ни один субтокен не пересекает границу двух слов.\
Сам разбор строки зависит от алгоритма: WordPiece идёт "жадно" — берёт максимально длинный токен, совпадающий с началом оставшейся строки, BPE последовательно применяет выученные правила слияния пар, а Unigram выбирает наиболее вероятное разбиение целиком.

<span id="3e58"></span>

**Пример:**

```kotlin
Текст пользователя:
"невероятно"

ID токена в словаре модели для части слов:
"не" = 24
"вероят" = 126
"но" = 36
Получаем массив ID токенов:
["не", "вероят", "но"] = [24, 126, 36]

В WordPiece-словаре то же слово могло бы разбиться как:
("не", "##вероятно")
где "##" помечает продолжение слова
```

**Разные по смыслу слова могут иметь общие токены, например "привет" и "пример" могут иметь общий токен "при":**\
Хотя «при» повторяется в обоих словах, модель сразу смотрит не только на этот кусочек, но и на соседние токены и на всю фразу (об этом дальше в статье).\
Сначала «при» превращается в вектор — просто набор чисел, описывающих эту часть слова.\
Затем трансформер (многослойная сеть) смешивает этот вектор с векторами соседних токенов («вет» или «мер») и добавляет информацию о позиции в предложении.\
В итоге в первом слое «при» в «привет» уже отличается от «при» в «пример», потому что туда «подмешался» разный контекст.\
То есть общий кусочек «при» сам по себе нейтральный, а смысл формируется дальше по слоям на основе окружения.

<span id="84d2"></span>

**Почему токены, а не слова:**\
Меньше словарь = экономия памяти.\
Лучше обрабатываются редкие и составные слова.\
Позволяет модели “учиться” понимать структуру слов.

<span id="be78"></span>

Размер словаря, или “vocab_size”, определяет, сколько уникальных токенов может обрабатывать модель.\
Больше словарь = меньше разбиений слов на части, но больше объём embedding-слоя.

<span id="b327"></span>

Слово “программирование” может быть целиком одним токеном в модели с крупным словарём,\
а может разбиться на части (“пр”, “ограм”, “мирование”) в модели с меньшим словарём.

Практический нюанс для читателей этого блога: кириллица токенизируется «дороже» латиницы. Токенизаторы обучены в основном на английском, поэтому русское или украинское слово чаще разбивается на большее число токенов, чем английское той же длины. Один и тот же текст по-русски занимает заметно больше токенов: быстрее заканчивается контекст, а в платных API это буквально дороже.

<span id="8788"></span>

**Размеры словаря популярных открытых моделей (по официальным конфигурациям, середина 2026 года):**

```kotlin
Модель                    | Размер словаря |
--------------------------|----------------|
Llama 2 (2023)            | 32 000         |
Llama 3.1                 | 128 256        |
Llama 4                   | 202 048        |
Gemma 2                   | 256 128        |
Gemma 3 / Gemma 4         | 262 144        |
Qwen 3                    | 151 936        |
Qwen 3.5                  | 248 320        |
DeepSeek V3 / R1 / V4     | 129 280        |
GPT-OSS (20B / 120B)      | 201 088        |
```

Хорошо видно, как словарь растёт от поколения к поколению. У закрытых моделей (GPT, Claude, Gemini) словарь и остальные детали архитектуры не публикуются.

### Специальные токены и chat-шаблон

Кроме токенов обычного текста в словаре есть служебные токены: начало и конец последовательности, границы реплик, у новых моделей — маркеры вызова инструментов и блоков рассуждений. Модель видела их на дообучении и опирается на них, чтобы понимать, где чья реплика и когда пора остановиться.

Поэтому диалог перед токенизацией оборачивается в chat-шаблон — принятую у конкретной модели разметку с ролями «система», «пользователь» и «ассистент». Упрощённо это выглядит так:

```text
<|system|>Ты полезный ассистент.<|end|>
<|user|>Что такое пенициллин?<|end|>
<|assistant|>
```

Модель просто продолжает текст после маркера ассистента, а заканчивает генерацию своим служебным токеном конца реплики.

У каждого семейства моделей шаблон свой, и перепутать его — самый частый способ «сломать» локальный запуск: модель начинает отвечать за пользователя, не останавливается вовремя или сыплет служебными токенами прямо в текст. Готовые бекенды вроде llama.cpp и Ollama обычно берут правильный шаблон из метаданных GGUF-файла автоматически.

### Токенизация изображений

В отличие от текстовой токенизации (где токены — это слова, сабворды, символы), в изображениях токены — это фрагменты изображения или представления признаков. Ниже рассмотрены основные подходы.

Для изображений вместо токенов получают сразу матрицу (или тензор) пикселей.

В классических свёрточных сетях (CNN) маленькие участки картинки (например, 3×3 или 5×5 пикселей) скользят по изображению, и для каждого участка свёртка с набором фильтров выдаёт вектор признаков. Эти векторы собираются в карты признаков и передаются дальше.

В современных трансформерах для изображений (Vision Transformer) картинку разбивают на «пэчи» (квадраты, скажем, 16×16 пикселей), каждый пэтч выравнивают в вектор и тоже проецируют через матрицу эмбеддингов в вектор-представление, как токен в NLP.

**Основные способы токенизации изображений:**

**Patch Embedding (разбиение на патчи) — классический подход ViT:**\
Изображение делится на сетку квадратных патчей, например, 16×16 пикселей.\
Каждый патч разворачивается в вектор (flatten), потом линейно проецируется в эмбеддинг фиксированной размерности (например, 768).\
В результате получается последовательность токенов: один токен на патч.

**Пример:**\
224×224 RGB изображение с патчами 16×16 → 14×14 = 196 токенов + \[CLS\] токен.\
Каждый токен: вектор размером 768.

**CNN Feature Maps как токены:**\
Используются свёрточные сети (ResNet, ConvNext) для извлечения признаков.\
Пространственные признаки (feature map) на выходе можно интерпретировать как токены, где каждый элемент сетки — вектор.\
Применяется в ResNet-вариантах CLIP и других гибридных моделях; непрерывные признаки визуального энкодера используют и мультимодальные LLM вроде LLaVA.

**VQ-VAE / VQ-GAN токенизация (дискретная):**\
Кодировщик (энкодер) преобразует изображение в карту признаков и затем квантует её в дискретные токены (индексы из словаря).\
Каждый токен — это индекс в словаре визуальных патчей.\
Используется в DALL·E первой версии, Parti, Chameleon и других генеративных моделях.\
Плюсы: модель работает с «словами» визуального языка.\
Минусы: потеря точности, неустойчивая генерация.

**Segment/Region-based токены (DETR, Region Attention):**\
Изображение разбивается на смысловые регионы (сегментация, объекты).\
Каждый регион преобразуется в токен с помощью агрегации признаков.\
Используется в задачах детекции объектов и визуального ответа на вопросы (VQA).

**Patch + Positional Encoding:**\
Как и в NLP, каждому патчу добавляется позиционная информация (абсолютная, относительная или learnable), чтобы сохранить пространственную структуру изображения.

### Максимальная длина контекста

Контекст — это оперативная память модели, размер контекста это максимальное количество токенов (слов, символов или их частей), которое языковая модель может обработать за один запрос.\
Это «объём памяти», который модель может видеть одновременно, чтобы сформировать ответ. Всё, что выходит за пределы этого окна, модель забывает или не видит напрямую.

Размер контекста напрямую влияет на способность модели помнить предыдущие сообщения в диалоге.

Модель не имеет встроенной долговременной памяти — она не "помнит" вас как человек. Она просто обрабатывает весь предыдущий диалог как входной текст (токены), передаваемый при каждом запросе. Это и называется контекст.

**Максимальная длина контекста популярных открытых моделей (по официальным конфигурациям, середина 2026 года):**

```kotlin
| Модель                  | Максимальная длина контекста |
|-------------------------|------------------------------|
| Llama 3.1               | 131 072                      |
| Llama 4 Scout           | 10 000 000                   |
| Gemma 3                 | 131 072                      |
| Gemma 4                 | 262 144                      |
| Qwen 3                  | 40 960 (131 072 с YaRN)      |
| Qwen 3.5                | 262 144                      |
| DeepSeek V3.1 / R1      | 131 072                      |
| DeepSeek V4             | 1 048 576                    |
| GPT-OSS                 | 131 072                      |
```

У закрытых моделей контекст того же порядка: например, Gemini 2.5 Pro держит около миллиона токенов.

### Почему длинный контекст дорогой

Плата за контекст двойная. Во-первых, внимание сравнивает каждый токен с каждым: при удвоении длины последовательности объём вычислений растёт примерно вчетверо. Во-вторых, для каждого обработанного токена модель хранит его K- и V-векторы во всех слоях (KV-кэш, о нём в шаге про итерацию генерации) — на длинных контекстах этот кэш занимает гигабайты и может есть больше памяти, чем сами веса.

Поэтому вокруг длинного контекста столько инженерных обходов: Gemma и GPT-OSS чередуют слои полного внимания со слоями «скользящего окна», которые смотрят только на ближайшую тысячу-другую токенов, а DeepSeek сжимает ключи и значения в компактный латентный вектор (MLA). Именно такие приёмы делают контекст в сотни тысяч и миллионы токенов практически возможным.

## Шаг 2: Token embedding (из токена в вектор) {#Шаг-2-Token-embedding-из-токена-в-вектор}

<span id="c9d7"></span>

В LLM модели для каждого ID токена хранится Token Embedding, или массив чисел. Эти числа описывают, что означает этот токен, как если бы ты переводил слово в математическую форму.

<span id="c825"></span>

Изначально token embedding имеет фиксированное значение для каждого токена, но затем уточняется по мере прохождения через слои.

<span id="e149"></span>

Когда Token Embedding проходит через несколько слоёв трансформера он становится контекстуализированным: он учитывает значение всей фразы. На выходе мы получаем вектор, который содержит “смысл” слова в контексте.

Связи между векторами задаются матрицами весов между слоями.

Изначально эти веса инициализируются случайно или по специальному правилу (например, Xavier или He), а потом во время обучения для каждого веса вычисляют градиент потери (насколько ошибка растёт или падает при его изменении).

Алгоритм обратного распространения (“backpropagation”) рассчитывает, как изменение каждого веса повлияет на итоговую ошибку, а затем градиентным спуском (или его вариациями) корректирует эти веса.

Так постепенно усиливаются связи между теми компонентами векторов, которые вместе дают полезную информацию, и ослабевают все ненужные — получается набор взаимосвязанных векторов, оптимальных для решения задачи.

### Размерность вектора (Embedding Size, d_model) на каждом слое трансформера

<span id="6700"></span>

Количество измерений у token embedding фиксировано для всей модели и зависит от её архитектуры.

<span id="6c08"></span>

**Пример:**

```kotlin
Текст пользователя:
"Привет"

ID токена в словаре модели для слова:
"Привет" = 1123

Token embedding для ID токена 1123 = массив значений в формате числа с плавающей точкой из d_model= 4096 элементов:
[0.034, 0.120, 0.905, ..., 0.028]
```

**Размер embedding size популярных открытых моделей (по официальным конфигурациям, середина 2026 года):**

```kotlin
Модель                    | d_model |
--------------------------|---------|
Llama 3.1 (8B)            | 4096    |
Llama 3.1 (70B)           | 8192    |
Llama 4 Scout (109B)      | 5120    |
Gemma 3 (12B)             | 3840    |
Gemma 3 (27B)             | 5376    |
Gemma 4 (31B)             | 5376    |
Qwen 3 (8B)               | 4096    |
Qwen 3 (32B)              | 5120    |
Qwen 3 (235B-A22B)        | 4096    |
Qwen 3.5 (9B)             | 4096    |
DeepSeek V3 / R1 / V4 Pro | 7168    |
DeepSeek V4 Flash         | 4096    |
GPT-OSS (20B / 120B)      | 2880    |
```

**Количество элементов вектора смысла влияет на:**\
Больше размерность = больше “места” для хранения семантики, синтаксиса, контекста.\
Это позволяет различать более тонкие смыслы между токенами.\
Ценой памяти: квадратично с ростом d_model растут внутренние матрицы проекций (их размер d_model × d_model), а слой эмбеддингов и активации растут линейно.

```text
матрица проекции при d_model = 4096
занимает в 4 раза больше памяти, чем
матрица проекции при d_model = 2048
```

Числа, из которых состоят эти векторы и веса, могут храниться с разной точностью — про форматы чисел и квантование см. раздел в конце статьи.

## Шаг 3: Positional Encoding / Embeddings {#Шаг-3-Positional-Encoding-Embeddings}

<span id="13ee"></span>

Без дополнительной информации фразы:\
“Кот ест рыбу”\
“Рыбу ест кот”\
могли бы восприниматься одинаково, ведь набор слов одинаков.

<span id="3f4b"></span>

Чтобы дать модели ощущение порядка, каждому токену (слову или части слова) добавляется позиционный вектор — набор чисел, который сообщает модели на каких позициях находятся слова.

```text
Token embedding + Positional Encoding / Embeddings = Суммарный вектор
```

Так делали классические трансформеры, и на этой схеме нагляднее всего объяснять. У современных моделей позиция вносится иначе — внутри механизма внимания, поворотом векторов (RoPE, о нём ниже в этом шаге); суммарный вектор у них просто равен эмбедингу токена.

<span id="8692"></span>

**Positional Encoding / Embedding это:**\
Вектор такой же размерности, как у токена (d)\
Представляет позицию в последовательности\
Может быть задан формулой (sin/cos) или обучаем\
Объединяется с вектором токена на входе в модель

<span id="f52f"></span>

**Какой тип используется в моделях:**

```text
| Метод                       | Модель/семейство            | Описание                                      |
|-----------------------------|-----------------------------|-----------------------------------------------|
| Sinusoidal Encoding         | Transformer                 | Не обучаются, основаны на синусах и           |
|                             | (Vaswani et al., 2017)      | косинусах с разной частотой                   |
|-----------------------------|-----------------------------|-----------------------------------------------|
| Learnable Embeddings        | BERT, GPT-2/GPT-3,          | Обучаемая таблица позиций, похожа на          |
|                             | OPT, ELECTRA                | эмбеддинги слов                               |
|-----------------------------|-----------------------------|-----------------------------------------------|
| Rotary Positional Embedding | почти все современные LLM:  | Вращение векторов — сохраняет относительные   |
| (RoPE)                      | Llama 2/3/4, Qwen, Gemma,   | позиции между токенами                        |
|                             | Mistral, DeepSeek, GPT-OSS  |                                               |
|-----------------------------|-----------------------------|-----------------------------------------------|
| ALiBi                       | BLOOM, MPT                  | Линейный bias, добавляется к attention score, |
|                             |                             | не требует хранения позиций                   |
|-----------------------------|-----------------------------|-----------------------------------------------|
| Relative Position Bias      | T5, DeBERTa, Transformer-XL,| Использует смещения между токенами вместо     |
|                             | Pegasus, LongT5             | абсолютных позиций                            |
```

**Пример:**

```kotlin
Текст пользователя:
"Привет, мир"

ID токена в словаре модели для слова:
"Привет" = 1123
"," = 15
"мир" = 345
Получаем массив ID токенов:
["Привет", ",", "мир"] = [1123, 15, 345]

Token embedding из базы модели для:
ID токена 1123 = [0.034, 0.120, 0.905, ..., 0.028]
ID токена 15 = [0.022, -0.010, -0.313, ..., 0.117]
ID токена 345 = [-0.102, 0.241, 0.543, ..., 0.055]

Позиция ID токенa слова в тексте пользователя:
[1123, 15, 345] = [позиции 0, позиции 1, позиции 2]

Вычисляем или получаем из базы модели вектор позиции:
Вектор позиции 0 = [0.001, 0.087, -0.432, ..., 0.019]
Вектор позиции 1 = [0.005, -0.013, 0.021, ..., -0.012]
Вектор позиции 2 = [-0.003, 0.099, -0.082, ..., 0.003]

Суммарный вектор для ID 1123:
Token embedding [0.034, 0.120, 0.905, ..., 0.028] +
Вектор позиции 0 [0.001, 0.087, -0.432, ..., 0.019] =
[0.035, 0.207, 0.473, ..., 0.047]
аналогично для остальных токенов
```

### Вычисление Sinusoidal Position Encoding {#3348}

<span id="c60a"></span>

Синусоидальное позиционное кодирование используется в Transformer-моделях для предоставления информации о позиции токенов во входной последовательности. В отличие от рекуррентных сетей, Transformer не имеет встроенного механизма учета порядка токенов. Поэтому необходимо явно закодировать информацию о позиции каждого токена.

<span id="0f01"></span>

**С точки зрения математики:**

```text
pos - позиция токена в последовательности (начиная с 0)
i - индекс измерения вектора позиционного кодирования (начиная с 0)
d_model - размерность вектора позиционного кодирования (размерность эмбеддинга)
PE(pos, i) - i-й элемент вектора позиционного кодирования для позиции pos.
10000 - это гиперпараметр. Он используется для масштабирования позиции и частоты синусоид. Выбор этого значения позволяет модели легко экстраполировать на последовательности, длиннее тех, на которых она была обучена.

Тогда i-й элемент вектора позиционного кодирования для позиции pos:
PE(pos, 2i) = sin(pos / 10000^(2i/d_model))
PE(pos, 2i+1) = cos(pos / 10000^(2i/d_model))
```

Формула использует разные частоты (длины волн) синусоидальных функций для разных измерений вектора позиционного кодирования.\
Четные измерения используют синус, а нечетные — косинус. Это позволяет модели различать позиции по разным фазам и амплитудам.\
Деление pos на 10000^(2i/d_model) уменьшает частоту синусоиды с увеличением индекса измерения i.\
Это создает более медленные колебания для более высоких измерений, что позволяет модели различать позиции на разных масштабах.

**Пример:**

```text
pos = 0 (первый токен)
d_model = 4 (размерность вектора позиционного кодирования)

Тогда вектор позиционного кодирования PE(0) будет иметь размерность 4.
Вычислим каждый элемент:

i = 0:
PE(0, 0) = sin(0 / 10000^(2*0/4)) = sin(0) = 0
PE(0, 1) = cos(0 / 10000^(2*0/4)) = cos(0) = 1

i = 1:
PE(0, 2) = sin(0 / 10000^(2*1/4)) = sin(0) = 0
PE(0, 3) = cos(0 / 10000^(2*1/4)) = cos(0) = 1

PE(0) = [0, 1, 0, 1]

Теперь вычислим вектор позиционного кодирования для pos = 1:

i = 0:
PE(1, 0) = sin(1 / 10000^(2*0/4)) = sin(1) ≈ 0.8415
PE(1, 1) = cos(1 / 10000^(2*0/4)) = cos(1) ≈ 0.5403

i = 1:
PE(1, 2) = sin(1 / 10000^(2*1/4)) = sin(0.01) ≈ 0.01
PE(1, 3) = cos(1 / 10000^(2*1/4)) = cos(0.01) ≈ 1

PE(1) = [0.8415, 0.5403, 0.01, 1]
```

<span id="be2c"></span>

**Важно:**\
В реальных Transformer-моделях позиционное кодирование обычно вычисляется для всех возможных позиций в последовательности заранее и сохраняется как таблица.\
Затем, при подаче входной последовательности, соответствующие векторы позиционного кодирования добавляются к эмбеддингам токенов.\
Выбор гиперпараметра 10000 является эмпирическим и может быть настроен в зависимости от конкретной задачи.

<span id="9e4a"></span>

**Относительные позиционные смещения:**\
Вместо того чтобы кодировать абсолютную позицию каждого токена, некоторые варианты трансформеров вводят **относительные** позиционные смещения. Это позволяет модели напрямую учитывать расстояние (и направление) между парами токенов, а не их «глобальную» позицию в последовательности.

<span id="34db"></span>

**Зачем нужны относительные смещения:**\
При генерации или обработке длинных текстов важно, как далеко друг от друга лежат слова, а не только их абсолютные индексы.\
Абсолютные эмбеддинги плохо обобщаются на более длинные последовательности, чем те, на которых модель обучалась.\
Относительные смещения дают большую гибкость: модель учится, например, «что на 3 позиции вправо может быть объект действия», независимо от того, где это предложение стоит в тексте.

<span id="22c5"></span>

Transformer-XL, относительные позиции через смещение ключей и запросов:

```text
В классическом self-attention считаем:
score_{i,j} = (Q_i · K_j) / sqrt(d_k)

В Transformer-XL вводят два дополнительных набора эмбеддингов:
E^R[r] — эмбеддинг для относительного сдвига r = j - i
U, V — два вектора-сдвига

Итоговая формула:

Q_i·E^R[j-i] - контент-зависимая часть внимания, учитывающая относительное смещение
V·E^R[j-i] - контент-независимая часть, задающая базовый bias для данного смещения

score_{i,j} = (1/√d_k) * (Q_i·K_j + Q_i·E^R[j-i] + U·K_j + V·E^R[j-i])
```

### Как работает RoPE

Практически все современные LLM используют Rotary Positional Embedding (RoPE). Идея: вместо того чтобы прибавлять вектор позиции к эмбедингу на входе, координаты векторов Q и K разбивают на пары и поворачивают каждую пару на угол, пропорциональный позиции токена. У каждой пары своя «частота» вращения, от быстрой к медленной — примерно как частоты синусоид выше.

Что это даёт:\
Позиция вносится внутри каждого слоя внимания, а не один раз на входе.\
Скалярное произведение двух повёрнутых векторов зависит только от разницы их позиций — модель автоматически получает относительные позиции, без отдельных таблиц смещений.\
Векторы V и сами эмбединги не трогаются: позиция влияет ровно там, где сравниваются токены.

С RoPE связаны и приёмы растягивания контекста. Если модель обучена на 40 тысячах токенов, а нужно 130 — частоты вращения пересчитывают так, чтобы более дальние позиции «уместились» в привычный модели диапазон углов. Так работают NTK-масштабирование и YaRN: то самое «131 072 с YaRN» из таблицы контекстов — не отдельное обучение на длинном контексте, а математическое растяжение RoPE с лёгким дообучением.

## Шаг 4: Attention, Self-Attention (Вектор Внимания, Q,K,V-проекции) и Разбиение на Головы Внимания (Multi-Head Attention) {#Шаг-4-Attention-Self-Attention-Вектор-Внимания-Q-K-V-проекции-и-Разбиение-на-Головы-Внимания-Multi-Head-Attention}

<span id="9d27"></span>

Теперь, когда токены имеют не только “значение”, но и “позицию”, они проходят через механизм self-attention, благодаря которому каждое слово смотрит на другие слова, чтобы решить, на кого из них мне обратить внимание, чтобы лучше понять свой смысл.

<span id="ec0d"></span>

Это похоже на то, как человек читает предложение: он может вернуться глазами к предыдущим словам, чтобы правильно понять контекст.

<span id="4d30"></span>

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

### Softmax

это функция, которая берёт на вход вектор чисел и превращает его в распределение вероятностей, где все значения неотрицательны и в сумме дают 1.

```kotlin
softmax(zᵢ) = exp(zᵢ) / ∑ⱼ exp(zⱼ)
```

<span id="5f51"></span>

**Модель создаёт три представления для каждого токена:**

<span id="8c06"></span>

**Q (вопрос):** что я ищу?\
**K (ключ):** что я могу предложить?\
**V (значение):** какую информацию я несу?

<span id="79b2"></span>

Каждое слово сравнивает свой Q с K всех остальных слов, чтобы узнать, на кого стоит смотреть. После этого оно собирает нужную информацию из V тех слов, которые оказались важными.

<span id="ea4d"></span>

Для вычисления представлений используется матрица весов слоя- это обучаемый параметр нейросети, то есть она инициализируется случайно при создании модели и обучается вместе с остальными весами. Изначально случайная, потом становится «умной» за счёт обучения.

<span id="d49d"></span>

**Как они обучаются:**\
На этапе обратного распространения (backpropagation) модель сравнивает свои предсказания с правильным ответом (например, следующий токен) и обновляет Wq, Wk, Wv по градиенту ошибки с помощью оптимизатора (например, Adam).

<span id="a092"></span>

Параметры Wq, Wk и Wv могут быть общими или различными для каждой из голов, в зависимости от модели.

<span id="beee"></span>

**Wq (Query Projection):**\
Создаёт “вопрос” — что токен хочет найти в других токенах.\
Определяет направление внимания.

```text
Q токена = Суммарный вектор токена X * Вес слоя Wq
```

**Wk (Key Projection):**\
Создаёт “ключ” — что каждый токен предлагает другим.\
Используется для сравнения с Q (насколько Qᵢ “похож” на Kⱼ).

```text
K токена = Суммарный вектор токена X * Вес слоя Wk
```

**Wv (Value Projection):**\
Создаёт “информацию”, которую токен может передать, если на него обратили внимание.

```text
V токена = Суммарный вектор токена X * Вес слоя Wv
```

<span id="4f04"></span>

**Голова внимания (attention-head):**\
Также у моделей существуют Голова внимания (attention-head). Каждая голова внимания (attention-head) обрабатывает входной вектор по-своему, через свои Q, K, V проекции, и смотрит на разные аспекты предложения.

<span id="fe2e"></span>

**У каждой attention-head есть своя точка зрения:**\
одна голова может отслеживать грамматику (например, подлежащее и сказуемое),\
другая — смысловые связи (например, кто на что действует),\
третья — позиции, контекст и т. д.\
Вместо одной “точки зрения” — сразу несколько.

<span id="e246"></span>

Каждая голова видит весь текст, но — анализирует его по-своему, через проекцию и внимание.

<span id="157b"></span>

**Пример:**\
В предложении\
“Мальчик, который держал мяч, убежал.”\
Разные головы могут видеть\
Голова 1: “мальчик” ↔ “убежал” → кто выполняет действие\
Голова 2: “который” ↔ “держал” → вложенная грамматическая связь\
Голова 3: “мяч” ↔ “держал” → объект действия\
Каждая голова выдаёт своё представление для каждого токена, с учётом своих “наблюдений”.

<span id="f138"></span>

**Архитектура Multi-Head Attention (MHA):\**
Классическая реализация self-attention, как в оригинальной статье *“Attention is All You Need”*.\
**Что происходит:**\
Есть несколько голов\
Каждая голова имеет свои Wq, Wk, Wv\
Каждая голова по-своему анализирует весь контекст\
Результаты всех голов объединяются и проходят через общую Wo\
**Плюсы:**\
Гибкость: каждая голова “смотрит” на вход по-своему\
Отлично работает при больших вычислительных ресурсах\
**Минусы:**\
Очень дорого по памяти и скорости при большом количестве голов\
Особенно при длинных последовательностях

<span id="6bf1"></span>

**Архитектура Multi-Query Attention (MQA):\**
Оптимизированная версия внимания, используемая в PaLM, Falcon, StarCoder и других, чтобы снизить нагрузку на память и ускорить инференс.\
**Что происходит:**\
По одной Wq на голову\
Только один Wk и один Wv\
Все головы используют одни и те же ключи и значения\
**Плюсы:**\
Меньше памяти: K и V хранятся в одном экземпляре\
Быстрее генерация: меньше данных хранится между шагами\
**Минусы:**\
Меньше гибкости (все головы “смотрят” на одинаковые K и V)\
Может слегка ухудшить качество на сложных задачах

<span id="ce58"></span>

**Архитектура Grouped Query Attention (GQA):**\
комбинированный подход из предыдущих двух. Именно GQA используется в большинстве современных моделей: Llama 3/4, Qwen 3, Gemma 2/3/4, GPT-OSS.

<span id="be3e"></span>

**Пример:**\
Например, в архитектуре нейросети Gemma 3 используется Grouped Query Attention (GQA)- компромисс между стандартным Multi-Head Attention (MHA) и Multi-Query Attention (MQA). В этой схеме матрицы Wq (для запросов) различны для каждой головы, тогда как матрицы Wk (для ключей) и Wv (для значений) могут быть общими для групп голов.\
В этой нейросети:\
Wq: каждая голова имеет свою уникальную матрицу Wq, что позволяет каждой голове фокусироваться на различных аспектах входной последовательности.\
Wk и Wv: головы делятся на группы, и внутри каждой группы используется общая матрица Wk и Wv. Это снижает объем вычислений и памяти, необходимых для хранения ключей и значений.\
В Gemma 3 27B — 32 головы запросов (Wq) и 16 KV-голов: головы делятся на 16 групп по 2, и каждая группа использует общие матрицы ключей и значений (Wk и Wv).

<span id="2e52"></span>

**В итоге в каждом из 62 слоёв модели Gemma 3 размером 27B будут:**\
32 разных Wq (Query Projection)\
16 разных Wk (Key Projection)\
16 разных Wv (Value Projection)\
1 общая Wo (Output Projection)\
1 MLP со своими весами (Feed-Forward Network)\
свои параметры нормализации\
— и у каждого из 62 слоёв этот набор весов свой.

<span id="8365"></span>

**Количество голов внимания (attention heads; запросы Q / KV-головы) популярных открытых моделей:**

```text
| Модель                  | Q-головы / KV-головы   |
|-------------------------|------------------------|
| Llama 3.1 (8B)          | 32 / 8                 |
| Llama 3.1 (70B)         | 64 / 8                 |
| Llama 4 Scout           | 40 / 8                 |
| Gemma 3 (12B)           | 16 / 8                 |
| Gemma 3 (27B)           | 32 / 16                |
| Gemma 4 (31B)           | 32 / 16                |
| Qwen 3 (8B)             | 32 / 8                 |
| Qwen 3 (32B)            | 64 / 8                 |
| Qwen 3 (235B-A22B)      | 64 / 4                 |
| Qwen 3.5 (9B)           | 16 / 4                 |
| DeepSeek V3 / R1 / V4   | 128 (MLA)              |
| GPT-OSS (20B / 120B)    | 64 / 8                 |
```

У DeepSeek вместо классического GQA используется MLA (Multi-Head Latent Attention): ключи и значения сжимаются в общий латентный вектор, поэтому деление на KV-головы к нему неприменимо.

<span id="d0ab"></span>

**Каузальное маскирование (Causal Masking):\**
применяется в некоторых моделях. Когда языковая модель (LLM) генерирует текст, она должна предсказывать следующее слово на основе только предыдущих слов.\
Это означает, что Токен не должен иметь доступ к токенам, которые идут после него.\
Чтобы обеспечить это ограничение, используется каузальное маскирование — это механизм, который “запрещает” вниманию видеть будущие токены.

<span id="f521"></span>

**Как это работает:**\
В обычном self-attention каждый токен “смотрит” на все токены в последовательности, включая и будущие.\
При генерации это неприемлемо, потому что это было бы “жульничеством” — модель видит ответ заранее.\
Чтобы это предотвратить, при вычислении attention применяют маску — специальную матрицу, называемую attention mask.\
Для последовательности длины n создаётся треугольная маска, в которой:\
Значения выше диагонали заменяются на -∞ или большое отрицательное число.\
После этого применяется softmax, и эти значения превращаются в нулевое внимание.

```text
Пример маски (для n = 4 токенов):
[
 [0, -∞, -∞, -∞],
 [0,  0, -∞, -∞],
 [0,  0,  0, -∞],
 [0,  0,  0,  0]
]

Это означает:
Токен 0 видит только себя.
Токен 1 видит себя и токен 0.
Токен 2 видит себя, токен 1 и токен 0.
Токен 3 видит всё, что до него.
```

<span id="f9aa"></span>

**Где используется Causal Masking:**\
GPT, LLaMA, Mistral, Gemma и прочие автогенеративные модели обязательно используют каузальное маскирование.\
BERT, наоборот, использует bidirectional attention — токен может видеть весь контекст (в т.ч. будущее), потому что задача другая — не генерация, а понимание.

<span id="7cc3"></span>

**Почему это важно, без causal masking:**\
Модель при обучении будет “подглядывать” на правильный ответ (следующий токен).\
Это приведёт к плохой генерации при использовании модели в inference, когда будущее неизвестно.

<span id="c0be"></span>

**Роль позиции в Self-Attention:**\
Без Position Encoding, Self-Attention видит только смысл слов, но не их порядок. С добавленным Position Encoding, Self-Attention начинает учитывать не только, что написано, но и где это находится:\
“Мальчик” раньше “читал” → возможно, он субъект\
“Книга” рядом с “читал” → скорее всего, объект

<span id="bda3"></span>

**Что в итоге делает трансформер:**\
Каждое слово получает информацию о своей позиции\
Через self-attention каждое слово “спрашивает” все остальные:\
“Что вы значите для меня в этом контексте?”\
Модель складывает эти ответы и получает глубокое понимание смысла всей фразы\
Повторяет это на каждом слое (обычно 12–40 раз), углубляя “понимание”

```kotlin
Текст пользователя:
"Привет, мир"

ID токена из словаре модели:
"Привет" = 1123
"," = 15
"мир" = 345
Получаем массив ID токенов:
["Привет", ",", "мир"] = [1123, 15, 345]

Token embedding из базы модели:
"Привет" = ID 1123 = [0.034, 0.120, 0.905, ..., 0.028]
"," = ID 15 = [0.022, -0.010, -0.313, ..., 0.117]
"мир" = ID 345 = [-0.102, 0.241, 0.543, ..., 0.055]

Вычисляем или получаем из базы модели вектор позиции:
Вектор позиции "Привет" = [0.001, 0.087, -0.432, ..., 0.019]
Вектор позиции "," = [0.005, -0.013, 0.021, ..., -0.012]
Вектор позиции "мир" = [-0.003, 0.099, -0.082, ..., 0.003]

Для "Привет":
Token embedding [0.034, 0.120, 0.905, ..., 0.028] +
Вектор позиции [0.001, 0.087, -0.432, ..., 0.019] =
Суммарный вектор X [0.035, 0.207, 0.473, ..., 0.047]

Вычисляем Q, K, V для каждого токена и для каждой головы внимания:
Q = X * Wq головы
K  = X * Wk головы
V = X * Wv головы
где X это суммарный вектор для токена
Wq, Wk, Wv - общие и одинаковые для всех токенов в этом слое,
но разные или частично разные для каждой головы

Например:
Wq головы = [[0.1, 0.2, 0.3], [0.4, 0.5, 0.6], [0.7, 0.8, 0.9], [1.0, 1.1, 1.2]]
Wk головы = [[0.12, 0.22, 0.32], [0.42, 0.52, 0.62], [0.72, 0.82, 0.92], [1.02, 1.12, 1.22]]
Wv головы = [[0.11, 0.21, 0.31], [0.41, 0.51, 0.61], [0.71, 0.81, 0.91], [1.01, 1.11, 1.21]]

Тогда:

Q "Привет" = x * Wq = [0.035, 0.207, 0.473, 0.047] * Wq

Q[0] = 0.035 * 0.1 + 0.207 * 0.4 + 0.473 * 0.7 + 0.047 * 1.0
     ≈ 0.0035 + 0.0828 + 0.3311 + 0.047
     ≈ 0.4644

Q[1] = 0.035*0.2 + 0.207*0.5 + 0.473*0.8 + 0.047*1.1
     = 0.007 + 0.1035 + 0.3784 + 0.0517
     ≈ 0.5406

Q[2] = 0.035*0.3 + 0.207*0.6 + 0.473*0.9 + 0.047*1.2
     = 0.0105 + 0.1242 + 0.4257 + 0.0564
     ≈ 0.6168

Q = [0.4644, 0.5406, 0.6168]

аналогичным образом вычисляем для "Привет":
K = [0.47964, 0.55684, 0.63204]
V = [0.47299, 0.54822, 0.62442]
и для других токенов
```

<span id="5cbc"></span>

**Вычисление Attention Score между токенами:\**
На этом этапе мы получили Q, K и V для одного токена и одной головы внимания. Далее можно перейти к вычислению attention score между токенами, например, между “Привет” и “мир”, с использованием формулы:

<span id="9c6a"></span>

**С точки зрения математики:**

```kotlin
Q - матрица запросов (Queries)
K - матрица ключей (Keys)
V - матрица значений (Values)
d_k - размерность вектора

Attention(Q, K, V) = softmax((Q * Kᵀ) / √d_k + mask) * V

Q * Kᵀ - матричное умножение Q на транспонированную матрицу K.
Это вычисляет "сырые" веса внимания между каждым запросом и каждым ключом.

√d_k - корень квадратный из размерности вектора ключа (dimension of key vectors).
Используется для масштабирования, чтобы предотвратить слишком большие значения
в softmax, что может привести к проблемам с градиентами.
Это называется Scaled Dot-Product Attention.

mask - это матрица, которая используется для ограничения внимания.
В генеративных моделях применяется causal mask (каузальное маскирование) —
она не позволяет токену "видеть вперёд" при вычислении внимания.
Это критично для задач, где модель предсказывает следующий токен.

softmax(...) - функция softmax применяется к результату деления Q ⋅ Kᵀ
на √d_k (и добавления mask, если она есть). Это нормализует веса внимания
так, чтобы они суммировались в 1 для каждого запроса.

Умножение результата softmax на V дает взвешенную сумму значений,
где веса определяются attention score.
```

**Пример:**

```kotlin
Входные данные:

| Токен    | Q-вектор                 | K-вектор                 |  V-вектор              |
|----------|--------------------------|--------------------------|------------------------|
| "Привет" | [0.4644, 0.5406, 0.6168] | [0.4796, 0.5568, 0.6320] |  [0.473, 0.548, 0.624] |
|----------|--------------------------|--------------------------|------------------------|
| ","      |[0.2, 0.3, 0.1]           | [0.25, 0.35, 0.15]       |  [0.12, 0.09, 0.04]    |
|----------|--------------------------|--------------------------|------------------------|
| "мир"    | [0.55, 0.33, 0.77]       | [0.5213, 0.5967, 0.6721] |  [0.55, 0.65, 0.75]    |

Для простоты используем размерность d_k для Q, K = 3 (для примера)

Вычисление:

Привет–Привет: 0.4644*0.4796 + 0.5406*0.5568 + 0.6168*0.6320 ≈ 0.2228 + 0.3011 + 0.3899 = 0.9138
Привет–",": 0.4644*0.25 + 0.5406*0.35 + 0.6168*0.15 ≈ 0.1161 + 0.1892 + 0.0925 = 0.3978
Привет–мир: 0.4644*0.5213 + 0.5406*0.5967 + 0.6168*0.6721 ≈ 0.2422 + 0.3225 + 0.4147 = 0.9794
","–Привет: 0.2*0.4796 + 0.3*0.5568 + 0.1*0.6320 ≈ 0.0959 + 0.1670 + 0.0632 = 0.3261
","–",": 0.2*0.25 + 0.3*0.35 + 0.1*0.15 = 0.05 + 0.105 + 0.015 = 0.17
","–мир: 0.2*0.5213 + 0.3*0.5967 + 0.1*0.6721 ≈ 0.1043 + 0.1790 + 0.0672 = 0.3505
мир–Привет: 0.55*0.4796 + 0.33*0.5568 + 0.77*0.6320 ≈ 0.2638 + 0.1837 + 0.4876 = 0.9351
мир–",": 0.55*0.25 + 0.33*0.35 + 0.77*0.15 ≈ 0.1375 + 0.1155 + 0.1155 = 0.3685
мир–мир: 0.55*0.5213 + 0.33*0.5967 + 0.77*0.6721 ≈ 0.2867 + 0.1969 + 0.5185 = 1.0021

Делим на √d_k = √3 ≈ 1.732

| From \ To | Привет                  | ","                     | мир                     |
| --------- | ----------------------- | ----------------------- | ----------------------- |
| Привет    | 0.9138 / 1.732 ≈ 0.5275 | 0.3978 / 1.732 ≈ 0.2296 | 0.9794 / 1.732 ≈ 0.5652 |
| ","       | 0.3261 / 1.732 ≈ 0.1882 | 0.17 / 1.732 ≈ 0.0982   | 0.3505 / 1.732 ≈ 0.2023 |
| мир       | 0.9351 / 1.732 ≈ 0.5397 | 0.3685 / 1.732 ≈ 0.2127 | 1.0021 / 1.732 ≈ 0.5786 |

Применим экспоненту для "Привет":
exp(0.5275) ≈ 1.694
exp(0.2296) ≈ 1.258
exp(0.5652) ≈ 1.759

Сумма всех экспонент для "Привет":

1.694 + 1.258 + 1.759 ≈ 4.711

softmax = exp(x) / сумма всех exp(x)

Softmax вес для "Привет" относительно других токенов:

| Токен  | Softmax вес          |
| ------ | -------------------- |
| Привет | 1.694 / 4.711 ≈ 0.36 |
| ","    | 1.258 / 4.711 ≈ 0.27 |
| мир    | 1.759 / 4.711 ≈ 0.37 |

Когда "Привет" генерирует своё представление (на выходе attention слоя), он:
берёт 36% информации от самого себя,
27% от ",",
и 37% от слова "мир".

Взвешенное суммирование:

Output= 0.36 ∗ V("Привет") + 0.27 ∗ V(",") + 0.37 ∗ V("мир")

Координата 1 = 0.36∗0.473+0.27∗0.12+0.37∗0.55≈0.1703+0.0324+0.2035 ≈ 0.4062
Координата 2 = 0.36∗0.548+0.27∗0.09+0.37∗0.65≈0.1973+0.0243+0.2405 ≈ 0.4621
Координата 3 = 0.36∗0.624+0.27∗0.04+0.37∗0.75≈0.2246+0.0108+0.2775 ≈ 0.5129

Выход внимания для токена "Привет": [0.4062, 0.4621, 0.5129]
```

<span id="a129"></span>

Этот вектор - выход внимания (attention output), новое представление токена “Привет”, в котором учтён его контекст: и сам он, и соседи. Именно такие векторы потом идут либо в следующий attention-слой, либо на выход модели. Важно не путать его с attention score: score - это скалярные веса из softmax (0.36, 0.27, 0.37 выше), а здесь - взвешенная ими сумма V-векторов.

Обратите внимание: для простоты пример посчитан без каузальной маски - “Привет” здесь «видит» и стоящий после него токен “мир”. В настоящей LLM с causal masking токен собирал бы информацию только с себя и предыдущих токенов.

<span id="f44a"></span>

Сравнение токена сам с собой нужно потому, что в self-attention каждый токен “смотрит” на все токены, включая сам себя.\
Это необходимо для того, чтобы:\
Сохранить информацию о самом токене — иначе он бы “терялся” на фоне остальных.

<span id="c0c9"></span>

Научиться “усиливать” или “подавлять” себя — например, в некоторых языковых ситуациях токен важен сам по себе (например, личное местоимение), а иногда его контекст важнее.\
Матрица внимания — квадратная (n × n), и на её диагонали стоит как раз self-to-self attention; при каузальной маске от неё остаётся нижний треугольник.

<span id="8092"></span>

Формально attention — это веса, по которым токен агрегирует информацию от других токенов (в т.ч. от самого себя) и w1 — это внимание “Привет” к самому себе. Если его не считать — токен “Привет” вообще не участвовал бы в своем собственном выходе.

<span id="68fa"></span>

Пример:\
Он сказал, что он придет.\
Когда модель обрабатывает токен “он”, ей нужно:\
“посмотреть” на другие токены — чтобы понять контекст,\
но и сам токен “он” тоже важен, чтобы не потерять информацию о том, о ком идет речь.

<span id="cfcc"></span>

**Материалы:**

[Wikipedia: Attention (machine learning)](https://en.wikipedia.org/wiki/Attention_\(machine_learning\))

[Wikipedia: Softmax function](https://en.wikipedia.org/wiki/Softmax_function)

[Understanding Q,K,V In Transformer( Self Attention)](https://medium.com/analytics-vidhya/understanding-q-k-v-in-transformer-self-attention-9a5eddaa5960)

[What is Query, Key, and Value (QKV) in the Transformer Architecture and Why Are They Used?](https://epichka.com/blog/2023/qkv-transformer/)

## Шаг 5: Объединение голов внимания (Head Concatenation, Concatenated Multi-Head Attention) {#Шаг-5-Объединение-голов-внимания-Head-Concatenation-Concatenated-Multi-Head-Attention}

<span id="a6b5"></span>

Предыдущие расчёты происходили для каждой из голов внимания, теперь необходимо объединить результаты. Напомню, результаты разные из-за разных Wq, Wk, Wv для разных голов.

<span id="2354"></span>

**С точки зрения математики:**

```text
h - количество голов внимания
d_k - размерность выхода внимания каждой головы
HeadOutput_i - выход внимания i-й головы.

Concatenation (Объединение векторов в один вектор по координатам):

concat = [HeadOutput_1, HeadOutput_2, ..., HeadOutput_h]
```

**Пример:**

```kotlin
Входные данные:
Допустим, размерность: 6 (2 головы × по 3 значения)

Количество голов внимания h = 2
Размерность вектора d_k = 3
"Привет" для головы 1 HeadOutput_1 = [0.4062, 0.4621, 0.5129]
"Привет" для головы 2 HeadOutput_2 = [0.22, 0.33, 0.44]

Вычисление:

Concatenation (Объединение векторов в один вектор по координатам):

concat[0] = HeadOutput_1[0] = 0.4062
concat[1] = HeadOutput_1[1] = 0.4621
concat[2] = HeadOutput_1[2] = 0.5129
concat[3] = HeadOutput_2[0] = 0.22
concat[4] = HeadOutput_2[1] = 0.33
concat[5] = HeadOutput_2[2] = 0.44

concat = [0.4062, 0.4621, 0.5129, 0.22, 0.33, 0.44]
```

## Шаг 6: Output Projection (Wo) {#Шаг-6-Output-Projection-Wo}

<span id="d827"></span>

После объединения attention-выходов с разных голов, они обычно пропускаются через линейный слой (Dense Layer), чтобы вернуть их в исходное пространство размерности модели.

<span id="7e3e"></span>

**С точки зрения математики:**

```text
concat ∈ ℝ⁶ - входной вектор
Wₒ ∈ ℝ⁶ˣ³ - матрица весов проекции
output ∈ ℝ³ - выходной вектор после проекции

Умножение:
output = concat • Wₒ

Подробная формула для каждой компоненты выходного вектора:
output[0] = concat[0]*W_o[0][0] + concat[1]*W_o[1][0] + concat[2]*W_o[2][0] + concat[3]*W_o[3][0] + concat[4]*W_o[4][0] + concat[5]*W_o[5][0]

Альтернативно, в виде суммы:
output_j = ∑_{i=1}^{6} concat_i ⋅ W_o[i][j]     для j = 1, 2, 3

Матрично:
output = concat (1x6) ⋅ W_o (6x3) = (1x3)
```

**Пример:**

```kotlin
Входные данные:
Размерность модели d_model = 3

Значит, проекционная матрица W_o будет иметь
размерность (6, 3), т.е. 6 входов → 3 выхода
W_o = [
  [0.1, 0.2, 0.3],
  [0.0, 0.1, 0.0],
  [0.2, 0.0, 0.1],
  [0.1, 0.2, 0.2],
  [0.0, 0.1, 0.3],
  [0.3, 0.0, 0.1]
]

Входной вектор после concat:
concat = [0.4062, 0.4621, 0.5129, 0.22, 0.33, 0.44]

Вычисление:

x = 0.4062*0.1 + 0.4621*0.0 + 0.5129*0.2 + 0.22*0.1 + 0.33*0.0 + 0.44*0.3
  = 0.04062 + 0 + 0.10258 + 0.022 + 0 + 0.132
  = 0.2972

y = 0.4062*0.2 + 0.4621*0.1 + 0.5129*0.0 + 0.22*0.2 + 0.33*0.1 + 0.44*0.0
  = 0.08124 + 0.04621 + 0 + 0.044 + 0.033 + 0
  = 0.2045

z = 0.4062*0.3 + 0.4621*0.0 + 0.5129*0.1 + 0.22*0.2 + 0.33*0.3 + 0.44*0.1
  = 0.12186 + 0 + 0.05129 + 0.044 + 0.099 + 0.044
  = 0.3602

Финальный output вектор:
output = [0.2972, 0.2045, 0.3602]
```

## Шаг 7: Добавление Residual (остаточного соединения) {#Шаг-7-Добавление-Residual-остаточного-соединения}

<span id="a262"></span>

это суммирование входа слоя с его выходом. Мы складываем выход с входным вектором, который подавался на вход блока (input embedding или выход предыдущего слоя). В реальной модели вход и выход блока имеют одинаковую размерность d_model и складываются поэлементно; в нашем сквозном примере вход был 4-мерным, а выход — 3-мерным, поэтому для иллюстрации возьмём первые три компоненты входа.

<span id="d1c3"></span>

**Это используется, чтобы:**\
Сохранить исходную информацию (градиенты легче передаются назад).\
Избежать “затухания” сигнала через множество слоёв.\
Облегчить обучение даже очень глубоких нейросетей.

```kotlin
Входные данные:

Входной вектор
input = [0.035, 0.207, 0.473]

Выходной вектор
output = [0.2972, 0.2045, 0.3602]

Вычисление:

residual = output + input = residual = [0.3322, 0.4115, 0.8332]
```

## Шаг 8: Нормализация слоя (Layer Normalization) {#Шаг-8-Нормализация-слоя-Layer-Normalization}

<span id="87df"></span>

нормализует значения внутри одного вектора признаков, то есть по размерности признаков (feature dimension).

<span id="a5b6"></span>

**Зачем это нужно:**\
Устраняет смещение и масштабные различия между признаками.\
Делает обучение более стабильным.\
Ускоряет сходимость нейросети.\
Лучше работает при маленьких батчах, в отличие от BatchNorm.

**С точки зрения математики:**

```text
Вычисляем среднее значение (mean):
μ = (1/n) * ∑ xₙ

Вычисляем стандартное отклонение (std):
σ = sqrt((1/n) * ∑ (xₙ - μ)^2 + ε)
Где ε - маленькое число, чтобы избежать деления на ноль.

Нормализуем каждый элемент:
̂xₙ = (xₙ - μ) / σ

Опционально масштабируем и смещаем (обучаемые параметры):
yₙ = γ * ̂xₙ + β
```

**Пример:**

```kotlin
Входные данные:

Возьмём выход residual из предыдущего шага:
x = [0.3322, 0.4115, 0.8332]

Вычисление:

Вычислим среднее:
μ = (0.3322 + 0.4115 + 0.8332) / 3 = 1.5769 / 3 ≈ 0.5256

Вычислим стандартное отклонение:
σ = sqrt(((0.3322 - 0.5256)^2 + (0.4115 - 0.5256)^2 + (0.8332 - 0.5256)^2) / 3)
   = sqrt((0.0374 + 0.0130 + 0.0946) / 3)
   = sqrt(0.0483) ≈ 0.22

Нормализуем:
̂x₁ = (0.3322 - 0.5256) / 0.22 ≈ -0.88
̂x₂ = (0.4115 - 0.5256) / 0.22 ≈ -0.52
̂x₃ = (0.8332 - 0.5256) / 0.22 ≈ 1.40

Если обучаемые параметры γ = 1 и β = 0, это и есть результат:
[-0.88, -0.52, 1.40]
```

## Шаг 9: FFN (Feed-Forward Network) и MLP (Multilayer Perceptron) {#Шаг-9-FFN-Feed-Forward-Network-и-MLP-Multilayer-Perceptron}

<span id="b3cd"></span>

**FFN (Feed-Forward Network):**\
это компонент трансформера, который обрабатывает каждое слово (или токен) по отдельности, без учета других токенов.

<span id="79a8"></span>

Он применяется независимо к каждому токену после слоя внимания и представляет собой двухслойную нейросеть с нелинейной функцией активации.

<span id="5ef6"></span>

Это позволяет модели улавливать более сложные зависимости.

<span id="66f4"></span>

**MLP (Multilayer Perceptron):**\
это тип нейронной сети, состоящий из нескольких слоев полностью связанных нейронов. В контексте LLM и трансформеров MLP часто означает ту же самую Feed-Forward Network (FFN).

<span id="93c6"></span>

MLP = общее название архитектуры,\
FFN = частный случай MLP, используемый внутри трансформеров.

<span id="f88b"></span>

**С точки зрения математики:**

```text
x - входной вектор размерности d
W1-матрица весов первого линейного слоя размерности (d_ff × d)
b1-смещение первого слоя размерности d_ff
W2-матрица весов второго линейного слоя размерности (d × d_ff)
b2-смещение второго слоя размерности d
f-функция активации (например, ReLU или GELU)
d-размерность входа/выхода
d_ff-размерность скрытого слоя (обычно d_ff = 4 × d)

Основная формула FFN:
FFN(x) = W2 · f(W1 · x + b1) + b2

Пошаговое разложение:

Линейное преобразование, размерность z1: (d_ff × 1):
z1 = W1 · x + b1

Нелинейная активация, применяется поэлементно, размерность сохраняется:
z2 = f(z1)

Второе линейное преобразование, итоговый вектор того же размера, что и x: (d × 1):
y = W2 · z2 + b2
```

**Пример:**

```kotlin
Входные данные:

Размерность скрытого слоя d = 4,
Внутренняя размерность d_ff = 8,
Активация: ReLU,
Входной вектор:
x = [x[0], x[1], x[2], x[3]] = [1.0, -2.0, 0.5, 3.0]

матрица 8x4 W1 =
[
  [1, 0, 0, 0],
  [0, 1, 0, 0],
  [0, 0, 1, 0],
  [0, 0, 0, 1],
  [1, 1, 1, 1],
  [1, -1, 1, -1],
  [0.5, 0.5, 0.5, 0.5],
  [-1, -1, -1, -1]
]

Вычисление:

y[0] = 1 * x[0] + 0 * x[1] + 0 * x[2] + 0 * x[3] = 1.0 + 0 + 0 + 0 = 1.0
y[1] = 0 * x[0] + 1 * x[1] + 0 * x[2] + 0 * x[3] = 0 - 2.0 + 0 + 0 = -2.0
y[2] = 0 * x[0] + 0 * x[1] + 1 * x[2] + 0 * x[3] = 0 + 0 + 0.5 + 0 = 0.5
y[3] = 0 * x[0] + 0 * x[1] + 0 * x[2] + 1 * x[3] = 0 + 0 + 0 + 3.0 = 3.0
y[4] = 1 * x[0] + 1 * x[1] + 1 * x[2] + 1 * x[3] = 1.0 - 2.0 + 0.5 + 3.0 = 2.5
y[5] = 1 * x[0] + (-1) * x[1] + 1 * x[2] + (-1) * x[3] = 1.0 + 2.0 + 0.5 - 3.0 = 0.5
y[6] = 0.5 * x[0] + 0.5 * x[1] + 0.5 * x[2] + 0.5 * x[3] = 0.5 - 1.0 + 0.25 + 1.5 = 1.25
y[7] = -1 * x[0] + (-1) * x[1] + (-1) * x[2] + (-1) * x[3] = -1.0 + 2.0 - 0.5 - 3.0 = -2.5

Вектор после линейного слоя (до активации):
y = [1.0, -2.0, 0.5, 3.0, 2.5, 0.5, 1.25, -2.5]

Применяем ReLU:
ReLU(y[i]) = max(0, y[i])

ReLU(y) = [1.0, 0.0, 0.5, 3.0, 2.5, 0.5, 1.25, 0.0]
```

В современных моделях FFN чуть сложнее описанного: используется gated-вариант (SwiGLU) с тремя матрицами — две готовят «кандидата» и «ворота», которые перемножаются поэлементно, — а вместо ReLU берут гладкую активацию SiLU/GELU; внутренняя размерность при этом не 4×, а около 2.7× от d_model. Сути это не меняет: по-прежнему пара линейных слоёв и нелинейность, применяемые к каждому токену отдельно.

У MoE-моделей именно на месте FFN стоит набор «экспертов»: небольшой роутер выбирает для каждого токена нескольких из них (например, 8 из 128), и работают только выбранные — так Qwen 3 235B-A22B тратит на токен лишь 22 из своих 235 миллиардов параметров.

## Шаг 10: Residual + LayerNorm (второй слой нормализации) {#Шаг-10-Residual-LayerNorm-второй-слой-нормализации}

После FFN к результату снова прибавляют вход этого блока (выход шага 8 — то, что подавалось в FFN) и снова нормализуют вектор — это помогает сохранить информацию и стабилизировать расчёты

## Шаг 11: Подача выхода блока в следующий трансформер-блок {#Шаг-11-Подача-выхода-блока-в-следующий-трансформер-блок}

Получив на выходе одного блока трансформера нормализованный вектор, модель передаёт его на вход следующему блоку. Таких блоков может быть десятки — каждый добавляет всё больше «понимания» контекста

## Шаг 12: Финальная LayerNorm нормализация после последнего блока {#Шаг-12-Финальная-LayerNorm-нормализация-после-последнего-блока}

После последнего слоя ещё раз применяют LayerNorm — это заключительный штрих перед генерацией логитов, чтобы сгладить разброс значений

## Шаг 13: Линейная проекция в логиты (Logits Projection) + Softmax {#Шаг-13-Линейная-проекция-в-логиты-Logits-Projection-Softmax}

Нормализованный вектор из последнего слоя умножают на матрицу эмбеддингов (или отдельный линейный слой), чтобы получить «сырые» оценки (logits) для каждого токена словаря. Затем эти оценки превращают в вероятности с помощью Softmax или сразу передают в семплеры для отборки токена

**Что такое logits:**\
"сырые" значения, которые ещё не нормализованы в вероятности.\
Например, если словарь состоит из 50 000 токенов, то logits — это просто 50 000 чисел, одно для каждого токена.

Для каждого токена в последовательности модель сформировала его представление (hidden state).

Теперь надо преобразовать этот hidden state в логиты по словарю — оценку вероятности появления каждого возможного следующего токена.

**Преобразование через Linear Layer:**

```text
logits = hidden_state @ Wᵀ + b

hidden_state: последний вектор (или вся последовательность — но чаще интересует последний токен),
Wᵀ: транспонированная матрица эмбеддингов (размер [vocab_size, hidden_dim]),
b: сдвиг (bias), часто опускается для экономии.
```

в части моделей применяется weight tying — Wᵀ берётся из того же слоя, что и эмбеддинги на входе; это экономит память. Так делают в основном компактные модели (Gemma, младшие Qwen), а, например, крупные Llama и DeepSeek держат отдельную выходную матрицу.

При генерации это преобразование делают только для последней позиции: чтобы выбрать следующий токен, нужны логиты одного последнего hidden state — считать сотни тысяч логитов для каждого токена промпта незачем.

## Шаг 14: Выбор следующего токена (sampling) {#Шаг-14-Выбор-следующего-токена-sampling}

Полученные logits проходят через цепочку семплеров: сначала применяются штрафы за повторения и температура, затем фильтры (top-k, top-p и др.), а в конце — финальный семплер (миростат, жадный выбор или распределение) . Именно этот токен добавляют в контекст, и генерация повторяется с шага 1 до достижения конца или нужной длины.

В бекенде **LLama.cpp** можно составлять цепочки семплеров, сначала промежуточные, затем цепочка должна завершаться финальным семплером.\
Порядок применения семплеров в llama.cpp:

```kotlin
1. Штрафы за повторы:
   - repeat_penalty
   - frequency_penalty
   - presence_penalty
   - DRY (штраф за повтор n-грамм)

2. Фильтрация токенов:
   - top_k
   - typical_p
   - top_p
   - min_p
   - XTC

3. Масштабирование логитов:
   - temperature

4. Грамматические ограничения:
   - grammar

5. Финальный выбор токена:
   - mirostat (v1 или v2) или
   - greedy / dist (случайная выборка)
```

Пример из LLama.cpp (для разработчиков):

```cpp
// подготовка параметров цепочки
struct llama_sampler_chain_params sparams = llama_sampler_chain_default_params();
llama_sampler * smpl = llama_sampler_chain_init(sparams);

// промежуточные семплеры:
llama_sampler_chain_add(smpl, llama_sampler_init_top_k (50)); // Top-K
llama_sampler_chain_add(smpl, llama_sampler_init_typical (0.95f, 1)); // Locally Typical
llama_sampler_chain_add(smpl, llama_sampler_init_top_p (0.9f, 1)); // Top-P (nucleus)
llama_sampler_chain_add(smpl, llama_sampler_init_min_p (0.05f, 1)); // Min-P
llama_sampler_chain_add(smpl, llama_sampler_init_xtc (0.9f, 1.0f, 1, LLAMA_DEFAULT_SEED)); // XTC
llama_sampler_chain_add(smpl, llama_sampler_init_top_n_sigma(1.5f)); // Top-nσ
llama_sampler_chain_add(smpl, llama_sampler_init_temp (0.7f)); // Temperature
llama_sampler_chain_add(smpl, llama_sampler_init_temp_ext (0.7f, 0.1f, 1.5f));  // Extended temp

llama_sampler_chain_add(smpl, llama_sampler_init_grammar(vocab, grammar, "root"));
llama_sampler_chain_add(smpl,llama_sampler_init_grammar_lazy_patterns(vocab, grammar, "root", patterns, 2, tokens, N));

// финальный семплер, только 1 вариант из:
llama_sampler_chain_add(smpl, llama_sampler_init_mirostat(32000, LLAMA_DEFAULT_SEED, 5.0f, 0.1f, 100)); // Mirostat V1
llama_sampler_chain_add(smpl, llama_sampler_init_mirostat_v2(LLAMA_DEFAULT_SEED, 5.0f, 0.1f)); // Mirostat V2
llama_sampler_chain_add(smpl, llama_sampler_init_greedy()); // Greedy
llama_sampler_chain_add(smpl, llama_sampler_init_dist(LLAMA_DEFAULT_SEED)); // Dist

// после генерации не забыть освободить цепочку:
llama_sampler_free(smpl);
```

**Про llama.cpp у меня есть отдельная статья:**

[/ru/llama-cpp/](/ru/llama-cpp/)

Пример страницы настройки семплеров из приложения [https://github.com/a-ghorbani/pocketpal-ai](https://github.com/a-ghorbani/pocketpal-ai)

![](/images/2019/02/Screenshot_2025-05-21-20-17-31-917_com.pocketpalai-1-461x1024.jpg)

![](/images/2019/02/Screenshot_2025-05-21-20-17-44-076_com.pocketpalai-461x1024.jpg)

## Промежуточные семплеры

### Temperature (temp)

управляет степенью случайности при выборе следующего токена из распределения вероятностей, вычисленного моделью. Температура сужает или расширяет "воронку выбора" следующего слова. Чем она ниже — тем уже воронка, тем выше — тем шире.

После того как модель предсказала логиты (сырые значения вероятностей) для всех возможных токенов, они проходят через softmax-функцию. Температура влияет на эту функцию:

```kotlin
P(token) = softmax(logits / temperature)
```

**Эффект параметра temperature:**\
T = 1 логиты остаются как есть.\
T \< 1 (например, 0.7) модель усиливает различия между токенами — вероятные токены становятся ещё более вероятными. Поведение становится более предсказуемым. \
T > 1 (например, 1.5) различия между токенами сглаживаются — возрастает шанс выбрать менее вероятный токен. Поведение становится более разнообразным и рискованным.\
T → 0 softmax превращается в argmax, и всегда выбирается один самый вероятный токен.

```kotlin
Исходные логиты:
Token A: 3.0
Token B: 2.5
Token C: 1.0

После softmax без изменения (temp = 1.0):
A: 57%
B: 35%
C: 8%

С пониженной температурой (temp = 0.5):
A: 72%
B: 27%
C: 1%

С повышенной температурой (temp = 1.5):
A: 51%
B: 36%
C: 13%
```

### Top-k

модель выбирает следующий токен только из k наиболее вероятных. Все остальные токены отбрасываются, независимо от их абсолютной вероятности.

```kotlin
есть 6 токенов с такими вероятностями:
A: 0.35
B: 0.30
C: 0.15
D: 0.10
E: 0.06
F: 0.04

Если top_k = 3, то оставим только:
A: 0.35
B: 0.30
C: 0.15
```

### Top-p (nucleus sampling)

выбирается минимальное множество токенов с наибольшими вероятностями, сумма которых превышает заданный порог p. Остальные токены полностью отсекаются, даже если они имели высокий ранг.

```kotlin
модель выдала вероятности токенов:
A: 0.50
B: 0.25
C: 0.15
D: 0.08
E: 0.02

При top_p = 0.9 набираем токены с наибольшими вероятностями,
пока их сумма не достигнет порога:
0.50 + 0.25 + 0.15 = 0.90 — остаются A, B, C
D и E будут отброшены — потом вероятности оставшихся нормализуются заново.
```

### Min-p

отсекаются все токены, чья вероятность ниже порога, заданного как доля `p` от вероятности самого вероятного токена. Например, при min_p = 0.1 и вероятности лидера 0.50 порог составит 0.05 — всё, что ниже, отбрасывается. Порог относительный: когда модель уверена, фильтр жёстче, когда распределение плоское — мягче.

### Typical sampling

выбирает токены, близкие к «типичному» уровню неожиданности (surprisal), отсекая слишком предсказуемые и слишком редкие. В отличие от top-k и top-p, он ориентируется на медианное значение surprisal, а не на вероятность токенов.

### Repetition penalties, Frequency penalty, Presence penalty

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

### Logit bias

метод, позволяющий вручную изменить вероятность отдельных токенов перед применением softmax, добавляя или вычитая значения из их логитов. Это даёт точечный контроль: можно, например, заставить модель избегать определённых слов или, наоборот, чаще их выбирать.

### Tail Free Sampling (TFS)

отбрасывает длинный «хвост» распределения, регулируя плотность вероятностей.

### DRY (Don't Repeat Yourself)

штрафует токены, которые продолжили бы уже встречавшиеся в тексте n-граммы: чем длиннее получающийся повтор, тем сильнее штраф. Хорошо убирает зацикливание на целых фразах, не трогая одиночные повторы слов.

### Grammar

использует правила грамматики (обычно в виде CFG) для жёсткого ограничения допустимых токенов на каждом шаге генерации. Вместо изменения логитов, он просто запрещает все токены, не соответствующие текущему допустимому состоянию грамматики, обеспечивая строго структурированный вывод.

и другие

## Финальные семплеры

### Greedy

самый простой способ генерации, при котором модель всегда выбирает токен с наивысшей вероятностью (максимальный логит). Он быстрый и детерминированный, но часто приводит к однообразному и предсказуемому тексту.

### Random

выбор следующего токена случайно, пропорционально его вероятности после всех применённых семплеров (top-k, top-p и т.д.). Это противоположность жадного выбора (greedy), где всегда берётся токен с максимальной вероятностью.

### Dist

так в llama.cpp называется финальная случайная выборка: токен выбирается случайно, пропорционально итоговому распределению вероятностей после всех фильтров — то же, что Random выше, с фиксируемым seed для воспроизводимости.

### Mirostat v1

поддерживает заданный уровень «удивления» (перплексии), динамически корректируя выбор слов. Он помогает избежать чрезмерных повторов (ловушка скуки) и бессвязности (ловушка путаницы), обеспечивая сбалансированную и качественную генерацию текста.

### Mirostat v2

поддерживает заданный уровень неожиданности (перплексии) с помощью более точного управления, чем Mirostat v1. Он использует расширенный механизм обратной связи, позволяющий динамически корректировать выбор слов для достижения стабильного качества текста.

и другие

## Шаг 15: Итерация генерации {#Шаг-15-Итерация-генерации}

После того как модель выбрала следующий токен, его ID добавляется в конец входной последовательности — повторная токенизация не нужна, выбранный токен уже является токеном.

Затем цикл повторяется: модель считает внимание уже с учётом нового токена и предсказывает следующий, и так до тех пор, пока не будут выполнены условия прекращения.

Пересчитывать при этом все предыдущие токены заново не нужно: их K- и V-векторы сохраняются в KV-кэше, и на каждой итерации по-настоящему обрабатывается только новый токен — именно поэтому шаги генерации занимают примерно одинаковое время.

Отсюда две фазы работы модели, знакомые каждому по ощущению «подумала — и ровно печатает». Сначала prefill: весь промпт обрабатывается одним параллельным проходом, заполняя KV-кэш, — это пауза до первого слова, и она тем длиннее, чем длиннее промпт. Затем decode: токены генерируются по одному, каждый за примерно одинаковое время. Поэтому скорость модели описывают двумя разными числами — время до первого токена и токены в секунду.

Такой цикл позволяет по одному токену строить всю выходную последовательность.

### Условия завершения

Генерация завершается, когда модель выдаёт специальный токен конца последовательности (EOS) или когда достигается заранее заданная максимальная длина последовательности.

Это ограничение предотвращает бесконечные или слишком длинные ответы.

Также генерацию останавливают стоп-последовательности: заранее заданные строки (например, маркер начала следующей реплики диалога), при появлении которых вывод обрывается.

## Шаг 16: Преобразование токенов в слова {#Шаг-16-Преобразование-токенов-в-слова}

После того как модель сгенерировала нужное количество токенов, их преобразуют обратно в текст — это называется детокенизация. Токенизатор берёт последовательность чисел (токенов) и переводит их в слова и знаки препинания. На практике чат-интерфейсы делают это по ходу — это и есть стриминг: каждый выбранный токен сразу детокенизируется и дописывается на экран, поэтому ответ появляется по словам, а не целиком в конце.

## Как модель обучали

Всё описанное выше — работа уже готовой модели. Сама она получается в несколько этапов.

**Предобучение (pretraining):**\
Модель триллионы раз решает одну и ту же задачу — предсказать следующий токен в тексте из огромного корпуса (веб, книги, код). Никакой разметки не нужно: правильный ответ — это просто настоящий следующий токен. На этом этапе модель выучивает язык, факты и закономерности, но умеет только продолжать текст.

**Инструкционное дообучение (SFT, Supervised Fine-Tuning):**\
Модель дообучают на примерах «запрос → хороший ответ», написанных людьми или отобранных из генераций. После этого она отвечает на вопросы, а не продолжает их.

**Выравнивание (RLHF, DPO):**\
Люди сравнивают пары ответов модели и выбирают лучший; на этих предпочтениях модель дообучают давать полезные и безопасные ответы. Классический способ — обучение с подкреплением по человеческим оценкам (RLHF); более простая современная альтернатива — DPO, где предпочтения используются напрямую как обучающие примеры.

### Reasoning-модели

«Думающие» модели из таблиц выше (DeepSeek R1, Qwen 3 в режиме размышлений, Gemma 4) механически ничем не отличаются: это те же трансформеры, генерирующие те же токены. Разница в обучении: их дополнительно учили с подкреплением на задачах с проверяемым ответом, награждая за правильный результат, — и модель сама выучилась перед ответом писать длинную цепочку рассуждений, черновик, который интерфейс потом прячет за спойлер. Платят за это токенами: «думающий» ответ может быть в разы длиннее и медленнее обычного.

### Откуда берутся галлюцинации

Прямо из механики генерации. Модель обязана выбрать следующий токен всегда — у неё нет встроенного состояния «не знаю», есть только распределение вероятностей, из которого что-то будет выбрано. Если знаний о предмете в весах мало, распределение всё равно выдаст правдоподобно звучащее продолжение: правильную форму ответа с выдуманным содержанием. Поэтому галлюцинации — не баг, который однажды починят, а свойство самого подхода; их подавляют обучением (модель учат отказываться от ответа), инструментами вроде RAG и веб-поиска, но полностью не устраняют.

## Интересные вопросы и ответы

### Возможно ли в нескольких предложениях описать, как работает нейросеть? {#Возможно-ли-в-нескольких-предложениях-описать-как-работает-нейросеть}

Постараюсь. Модель — это огромная функция, обученная на одну задачу: по началу текста предсказать, какой токен (кусочек слова) вероятнее всего идёт следующим. Ваш вопрос превращается в последовательность токенов, модель вычисляет вероятности всех возможных продолжений, из них выбирается один токен, дописывается к тексту — и всё повторяется, токен за токеном, пока не сложится ответ. А всё «понимание» — в том, что за время обучения на огромном корпусе текстов веса модели научились предсказывать продолжение с учётом смысла и контекста всей фразы, а не просто по частоте слов.

### Почему в LLM моделях на входе дается вопрос, а на выходе получается ответ, а не перефразированный вопрос? {#Почему-в-LLM-моделях-на-входе-дается-вопрос-а-на-выходе-получается-ответ-а-не-перефразированный-вопрос}

Модель обучена на текстах «вопрос→ответ», поэтому при виде вопроса дальше генерируются не слова самого вопроса, а наиболее вероятное продолжение — ответ.

Во время инструкционного обучения (SFT) и RLHF её «поощряют» за полезные ответы, а не за повтор формулировки, поэтому параметры сдвигаются в сторону ответов.

В терминах вероятностей: для вопроса вероятность следующего токена-ответа выше, чем токена, повторяющего вопрос, и декодер выбирает именно его.

### Данные, на которых обучается LLM модель, устаревают, какие есть возможности для того, чтобы модель генерировала актуальные ответы? {#Данные-на-которых-обучается-LLM-модель-устаревают-какие-есть-возможности-для-того-чтобы-модель-генерировала-актуальные-ответы}

**Retrieval-Augmented Generation (RAG):**\
Модель при запросе ищет в актуальной внешней базе (поиск по векторному или текстовому индексу) релевантные документы и использует их контекст при генерации ответа. Так можно получать свежую информацию без перетренировки основной сети

**Параметр-эффективное дообучение (LoRA, Adapters):**\
Вместо полного переобучения модели встраивают небольшие адаптеры или low-rank матрицы (LoRA), которые обучаются на новых данных. Это позволяет быстро и недорого «научить» модель новым фактам или доменам

**Про LoRA я сделал отдельную статью:**

[/ru/lora-fine-tuning/](/ru/lora-fine-tuning/)

**Целевое редактирование весов (Model Editing):**\
Алгоритмы типа MEMIT/ROME локально корректируют веса модели для добавления или обновления конкретных фактов, не затрагивая остальную часть знаний

**Отдельные базы знаний и графы:**\
Вместо хранения фактов внутри параметров LLM выносите их в внешние KB или графы знаний, которые регулярно обновляются, а модель лишь запрашивает у них информацию

**Интеграция с веб-поиском и API:**\
Подключение модели к живым источникам — встроенный веб-поиск в чат-ботах, вызов внешних API и инструментов (function calling, MCP-серверы) — напрямую возвращает свежее содержимое.

### Каким образом LLM модель понимает различные языки? {#Каким-образом-LLM-модель-понимает-различные-языки}

Модель “понимает” разные языки благодаря тому, что в процессе обучения она видит тексты на многих из них и учится предсказывать следующий фрагмент независимо от языка. Основные моменты:

**Большой мультиязычный корпус:**\
Для обучения собирают тексты (википедия, книги, веб-страницы из Common Crawl и прочие) на десятках и сотнях языков. Например, в открытой модели BLOOM было около 46 языков, причём доля каждого зависит от объёма доступных данных

**Общая токенизация subword:**\
Используются алгоритмы вроде BPE или SentencePiece, которые разбивают слова на фрагменты (subword) и включают в словарь символы и последовательности из разных алфавитов. Так модель оперирует единым набором токенов для всех языков

**Универсальная архитектура трансформера:**\
В трансформере одни и те же веса участвуют при обработке любых языков. Поэтому при обучении на разных языках модель находит общие паттерны (синтаксис, семантика) и использует перекрёстное перенесение знаний (cross-lingual transfer)

**Не все языки и не во всех объёмах:**\
Обучают лишь на тех языках, где есть достаточный объём текстов. Редкие или низкоресурсные языки попадают в отдельные дообучения или получают меньшую долю данных, поэтому качество генерации на них ниже

**Специальные дообучения и адаптеры:**\
Чтобы улучшить знание малоизвестных языков, применяют дообучение (continual pretraining) на локальных данных или вставляют адаптеры (adapters, LoRA), которые тонко корректируют знания модели под конкретный язык.

## Дополнительная информация

### Точность вектора {#cc48}

<span id="27e7"></span>

Также числа с плавающей точкой, из которых состоит вектор, могут иметь различную точность:\
Меньшая точность уменьшает размер модели и ускоряет обработку.

Обычно модели обучаются на полной точности FP32 (float32), то есть вектор состоит из чисел размерностью 32 бита.

### Квантование (Quantization) {#b0aa}

<span id="057d"></span>

для уменьшения точности для облегчения модели используется квантование —процесс преобразования чисел с плавающей точкой (например, FP32) в более компактные целочисленные представления (например, INT8), с сохранением приближённого значения.\
При этом вводится небольшая потеря точности, но она часто не влияет критично на качество вывода.

```text
Формат: FP32 (float32), 32 бита = 4 байта
Предназначение: Полная точность. Используется при обучении моделей, а также при точном инференсе.
Обеспечивает максимальную точность, но требует много памяти и вычислительных ресурсов.
Бинарное значение: 01000000 01001001 00001111 11011011
Фактическое значение: 3.14159
Возможные типы квантования: Не используется — это полный (не квантованный) формат.

Формат: FP16 (float16), 16 бит = 2 байта
Предназначение: Половинная точность. Используется для ускоренного обучения и вывода на GPU (например, NVIDIA Tensor Cores).
Быстрее и в 2 раза экономичнее по памяти по сравнению с FP32.
Бинарное значение: 01000010 01001000
Фактическое значение: ≈ 3.140625
В GGUF-файлах неквантованные веса такой точности помечаются как F16.

Формат: BF16 (bfloat16), 16 бит = 2 байта
Предназначение: Альтернатива FP16, используемая в TPU и некоторых GPU. Имеет ту же экспоненту, что и FP32, но укороченную мантиссу.
Быстрее и компактнее, при этом сохраняет диапазон FP32.
Бинарное значение: 01000000 01001001
(это просто первые два байта FP32-представления — мантисса укорочена)
Фактическое значение: ≈ 3.140625
В GGUF-файлах помечается как BF16.

Формат: INT8 (8 бит), scale = 0.125
Предназначение: Квантованное целое число. Используется в оптимизированных моделях для инференса на CPU и мобильных устройствах.
Требует восстановления масштаба (scale) и смещения (zero_point).
Квантизированное значение: 25
Бинарное значение: 00011001
Фактическое значение: 25 × 0.125 = 3.125
Возможные типы квантования: Q8_0, Q8_1, Int8Affine, PerChannelQuant (ONNX), dynamic/int8 (TensorFlow Lite)

Формат: INT4 (Q4), 4 бита, scale = 0.5 (2 числа в 1 байте)
Предназначение: Очень сжатый формат для языковых моделей. Используется в llama.cpp, GGUF и других системах.
Обеспечивает существенное уменьшение размера модели. Требует восстановления (деквантования) при запуске.
Квантизированное значение:
7 (максимальное значение для signed 4-битного int: -8…+7)
Бинарное значение: 0111
Фактическое значение: 7 × 0.5 = 3.5
Возможные типы квантования:
Q4_0, Q4_1, Q4_K_S, Q4_K_M (llama.cpp, GGUF)

Формат: INT2 (Q2), 2 бита, scale = 1.0 (4 числа в 1 байте)
Предназначение: Экстремально сжатый формат для использования в LLM на устройствах с ограниченными ресурсами.
Используется в некоторых вариантах GGUF, MLC, а также в экспериментах с экстремальным квантованием.
Квантизированное значение: 1 (максимум среди значений: -2…+1)
Бинарное значение: 01
Фактическое значение: 1 × 1.0 = 1.0
Возможные типы квантования: Q2_K (llama.cpp, GGUF)
Реальный Q2_K хранит ещё блочные масштабы, поэтому фактически тратит около 2.6 бита на вес.

Формат: INT1 (Q1), 1 бит, scale = 2.0 (8 чисел в 1 байте)
Предназначение: Минимально возможная точность. Используется в бинарных нейросетях и прототипах.
Обычно значения -1 или +1. Применяется редко в LLM, но может быть полезен в BNN (Binary Neural Networks).
Квантизированное значение: 1
Бинарное значение: 1
Фактическое значение: 1 × 2.0 = 2.0
Возможные типы квантования: BinaryNet, XNOR-Net (чаще в академических/экспериментальных BNN)
Современный пример экстремального квантования LLM — BitNet b1.58 с тернарными весами -1/0/+1 (~1.58 бита на вес).
```

Также у моделей могут быть дополнительные параметры квантования:

```text
_K
- Обозначает "K-блочную" квантизацию (K-Block Quantization).
- Веса разбиваются на блоки фиксированной длины (например, по 32 или 64 значения).
- Внутри каждого блока используется общий scale и zero_point.
- Это позволяет существенно уменьшить размер модели, сохраняя более высокую точность по сравнению с простой квантизацией.
- Примеры форматов: Q2_K, Q4_K, Q6_K, Q8_K

_0, _1
- Показывают, какую схему квантования применяют:
  - _0: базовая схема, без смещений (bias), один scale на блок
  - _1: улучшенная схема, с дополнительными bias или scale-сдвигами
- Используются в форматах: Q4_0, Q4_1, Q5_0, Q5_1, Q8_0, Q8_1
- Как правило, _1 обеспечивает лучшую точность при незначительном увеличении размера

Метки варианта размера: K_S, K_M, K_L

K_S
- Small — самый маленький и самый агрессивно сжатый вариант
- Максимальная экономия памяти в ущерб качеству

K_M
- Medium — средний компромисс между точностью и объёмом
- Самый популярный выбор для локального запуска

K_L
- Large — самый большой и самый точный из K-вариантов
- Чуть больше памяти, чуть выше качество

Пример названия модели:
"mistral-7b.Q4_K_M.gguf" — это означает:
- Модель Mistral 7B
- Используется квантование Q4_K (4-битная K-блочная)
- Вариант размера: Medium

Помимо K-квантов в llama.cpp есть более новые I-кванты
(IQ2_XXS, IQ3_S, IQ4_XS и др.) — они дают лучшее качество
при том же размере за счёт более сложной схемы кодирования.
```

### Продуктовое квантование (Product Quantization)

Метод, который позволяет сильно сжать векторы признаков (эмбеддинги), разбивая их на части и кодируя каждую часть через ближайший шаблон (кластер). Используется в векторных базах и поиске ближайших соседей (например, FAISS); веса самих LLM им обычно не сжимают.

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

Есть вектор (например, размером 128 чисел). Это может быть эмбеддинг текста или изображения от нейросети.

Разбиваем вектор на куски — например, 8 частей по 16 чисел (128 / 8 = 16).

Для каждой позиции куска обучаем свой кодировщик (квантайзер) — на больших данных заранее выбираются, какие "шаблоны" (центры кластеров) похожи на возможные куски.\
Например:\
первый кусок может быть похож на шаблон №12,\
второй — на шаблон №3,\
третий — на шаблон №88,\
и так далее.

Сохраняем только номера этих шаблонов. Вместо хранения 128 чисел (float32 = 512 байт), храним, например, 8 чисел (по 1 байту на шаблон) — всего 8 байт.

<span id="204e"></span>

**QAT (Quantization-Aware Training) квантование:\**
это метод квантования нейронных сетей, при котором квантование учитывается уже во время обучения модели. Он позволяет добиться почти такой же точности, как и у оригинальной модели с float-параметрами, при этом модель будет использовать более компактные int8 или другие низкоразрядные форматы, пригодные для эффективного запуска на устройствах с ограниченными ресурсами (например, смартфонах или микроконтроллерах).

<span id="7ea5"></span>

**Как работает QAT — поэтапно:**

<span id="ab05"></span>

**Модель в float32:**\
Обучение начинается с обычной модели, использующей числа с плавающей точкой (обычно float32). Это обеспечивает высокую точность и стабильность обучения.\
**Имитация квантования во время forward pass (fake quantization):**\
При каждом проходе вперёд значения (веса, активации) эмулируются как квантованные, т.е. они преобразуются в int8, а затем обратно в float32. Это даёт возможность модели «увидеть» ошибки квантования ещё на стадии обучения.

```text
float32 → int8 → float32
```

<span id="d07c"></span>

Таким образом, во время обратного распространения (backpropagation) градиенты считаются по float32-версии, но ошибки из-за квантования всё равно влияют на обучение.\
**Обновление параметров (backward pass):**\
Градиенты считаются как обычно, но с учётом искажений от fake quantization. Это позволяет модели адаптироваться к тому, что веса и активации будут впоследствии использоваться в низкой точности.\
**Экспорт финальной модели в int8:**\
После завершения обучения веса действительно квантуются в int8, и модель может быть скомпилирована и запущена в производственном окружении.

<span id="0584"></span>

**Что именно квантуется:**\
Веса (weights) — float32 → int8\
Активации (activations) — float32 → int8\
(Иногда также квантуются градиенты и промежуточные состояния, но это редкость.)

<span id="fb18"></span>

**Зачем использовать QAT:**\
Выше точность, чем у post-training quantization (PTQ)\
Низкое потребление памяти\
Быстрее выполнение на CPU/GPU/NPUs с поддержкой int8\
Особенно важно для мобильных и embedded-устройств (например, Android)

<span id="82c4"></span>

### Сколько памяти нужно модели

Грубая формула: число параметров × байт на вес, плюс KV-кэш, плюс немного на активации.

```text
Модель 7-8B:
FP16 (2 байта на вес)   ≈ 14-16 ГБ
Q8 (1 байт на вес)      ≈ 7-8 ГБ
Q4 (~0.6 байта на вес)  ≈ 4-5 ГБ

Модель 27-32B в Q4      ≈ 16-20 ГБ
Модель 70B в Q4         ≈ 40+ ГБ

Плюс KV-кэш: зависит от длины контекста,
на десятках тысяч токенов — ещё гигабайты.
```

Практическое правило для локального запуска: смотреть не на «миллиарды параметров», а на размер конкретного GGUF-файла — он честно показывает, сколько памяти займут веса, — и оставлять запас на контекст.

### Способы запуска (бэкенды) для LLM моделей {#Способы-запуска-бэкенды-для-LLM-моделей}

**vLLM:**\
Основной серверный движок LLM-инференса: непрерывный батчинг запросов и PagedAttention для экономного KV-кэша; стандарт де-факто для развёртывания открытых моделей на GPU.\
**Форматы:** веса Hugging Face (.safetensors)

**SGLang:**\
Быстрорастущая альтернатива vLLM с переиспользованием KV-кэша между запросами (RadixAttention); силён на структурированной генерации и агентных нагрузках.\
**Форматы:** веса Hugging Face (.safetensors)

**NVIDIA TensorRT-LLM:**\
Специализированная LLM-надстройка над TensorRT: компилирует модель под конкретные GPU NVIDIA, максимальная скорость ценой гибкости.\
**Формат:** скомпилированный движок

**ONNX Runtime:**\
Кроссплатформенный движок для выполнения моделей в формате ONNX.\
**Формат файла:** .onnx

**TensorFlow (Inference API):**\
Стандартный движок для моделей TensorFlow, поддерживает SavedModel и замороженный граф.\
**Форматы:** папка SavedModel (saved_model.pb + variables/), единый файл .pb

**LiteRT (бывший TensorFlow Lite):**\
Лёгкий движок для мобильных и встраиваемых устройств, оптимизированный по размеру и скорости.\
**Формат:** .tflite

**PyTorch (TorchScript):**\
Позволяет сериализовать и запускать модели без зависимости от Python, с оптимизациями JIT.\
**Форматы:** .pt, .pth

**NVIDIA TensorRT:**\
Аппаратно-ускоренный движок для GPU NVIDIA, компилирует модели (обычно из ONNX) под конкретную карту.\
**Формат:** скомпилированный движок .engine (или план UFF/ONNX → .engine)

**Intel OpenVINO:**\
Оптимизирует и ускоряет сети на CPU и Intel GPU/VPU, конвертирует модели в IR-формат.\
**Форматы:** .xml (структура) + .bin (веса)

**Apple Core ML:**\
Фреймворк для запуска моделей на iOS/macOS, интегрируется с Xcode и ускоряется через Core ML Runtime.\
**Форматы:** .mlpackage, устаревший .mlmodel

**Microsoft ML.NET:**\
.NET-движок для инференса на CPU, подходит для C# и F#.\
**Формат:** архив модели .zip

**Apache TVM:**\
Компилирует и оптимизирует модели под разнообразное железо, создаёт нативные библиотеки.\
**Форматы:** сериализованный Relay-модуль или скомпилированная библиотека (.so, .dll)

**Alibaba MNN:**\
Мобильный нейронный движок с широкими оптимизациями для ARM, поддерживает серверный запуск.\
**Формат:** .mnn

**Tencent NCNN:**\
Компактный движок для мобильных CPU/GPU, без сторонних зависимостей.\
**Форматы:** .param (структура) + .bin (веса)

**OpenCV DNN:**\
Модуль компьютерного зрения с поддержкой разных форматов (ONNX, Caffe, TensorFlow, Darknet).\
**Форматы:** зависит от исходного — .onnx, .pb, .caffemodel + .prototxt, .weights

**Unity Sentis (ранее Barracuda):**\
Движок для запуска нейросетей в играх Unity, поддерживает ONNX-модели.\
**Форматы:** .onnx, .sentis

**MLC LLM:**\
ML-компилятор и движок для LLM, компилирует веса в свой IR и шардирует их.\
**Формат:** каталог …-MLC с .bin-шардами и JSON-конфигами (например, mlc-chat-config.json)

**MediaPipe:**\
Фреймворк для создания мультимодальных пайплайнов (детекция, сегментация и др.), опирается на TFLite.\
**Форматы:** .tflite (модель) + .pbtxt (граф)

**llama.cpp:**\
C++-движок для LLaMA-подобных моделей с поддержкой квантования на CPU/GPU.\
**Формат:** .gguf (старый GGML-формат .bin устарел)

**GGML:**\
Библиотека для эффективного инференса моделей (основа llama.cpp и др.).\
**Форматы:** .gguf, .bin

**NeuralMagic DeepSparse:**\
Оптимизированный движок для разреженных нейросетей на CPU.\
**Формат:** .onnx

**AWS Neuron SDK:**\
Движок для ускорения инференса на AWS Inferentia, компилирует модели под Neuron.\
**Форматы:** Neuron-артефакты (.nef или библиотека)

Дальше — движки скорее исторические: сегодня они почти не встречаются, но упоминаются в старых материалах.

**Glow:**\
Компилятор и рантайм от Facebook для нейросетей, входной формат ONNX.\
**Форматы:** промежуточный файл .bc или скомпилированная библиотека .so

**Apache MXNet:**\
Фреймворк с собственным рантаймом для CPU/GPU, поддерживает Docker-развёртывание.\
**Форматы:** .params (веса) + .json (сеть)

**Caffe:**\
Классический движок для CV-сетей, часто используется в исследованиях.\
**Форматы:** .caffemodel (веса) + .prototxt (описание сети)

### Приложения для запуска LLM моделей {#Приложения-для-запуска-LLM-моделей}

**LM Studio:**\
Десктоп-приложение для Windows, macOS и Linux с GUI для запуска локальных LLM (GPT-подобных).\
**Форматы моделей:** .gguf, .bin

**Ollama:**\
Лёгкий CLI/GUI-клиент для Windows, macOS и Linux, работает «из коробки» с LLM.\
**Форматы моделей:** .gguf, .bin

**Jan:**\
Открытый десктоп-клиент для Windows, macOS и Linux в духе LM Studio, но полностью open source; работает поверх llama.cpp.\
**Форматы моделей:** .gguf

**Open WebUI:**\
Веб-интерфейс к Ollama и любым OpenAI-совместимым серверам: чаты, RAG по своим документам, несколько пользователей; ставится локально или на свой сервер.\
**Форматы моделей:** те, что поддерживает подключённый бекенд

**KoboldCpp:**\
Однофайловая сборка llama.cpp с веб-интерфейсом и богатыми настройками семплеров, популярна для творческой генерации.\
**Форматы моделей:** .gguf

**llama.cpp (prebuilt binaries):**\
Готовые исполняемые файлы для Windows/macOS/Linux, позволяют запускать LLaMA-подобные модели без установки зависимостей.\
**Форматы моделей:** .bin, .gguf

Отдельная группа — приложения для генерации изображений (Stable Diffusion), не LLM:

**Automatic1111 Stable Diffusion WebUI:**\
Самый популярный локальный веб-интерфейс для генерации изображений на Windows/macOS/Linux.\
**Форматы моделей:** .ckpt, .safetensors

**DiffusionBee:**\
Настольное приложение для macOS (есть бета-сборки для Windows), «всё в одном» для Stable Diffusion.\
**Форматы моделей:** .ckpt, .safetensors

**InvokeAI:**\
Кроссплатформенный пакет с CLI и WebUI для генерации изображений на базе Stable Diffusion.\
**Форматы моделей:** .ckpt, .safetensors

### Формат хранения LLM моделей {#Формат-хранения-LLM-моделей}

<span id="b0af"></span>

**.pt / .pth**\
Используются в PyTorch для сохранения обученных моделей

<span id="b0af"></span>

Хранят веса и структуру модели (или только веса).\
Основаны на сериализации Python (pickle), что делает их не очень безопасными.\
Подходят для обучения и дообучения.\
Не оптимизированы для мобильного вывода или внешнего инференса\
Могут занимать много памяти (FP32).

<span id="d4ab"></span>

**.safetensors**\
Альтернатива .pt, безопасный и быстрый формат для PyTorch.

<span id="bcf5"></span>

Не использует pickle, а значит безопасен при загрузке.\
Поддерживает параллельную загрузку, что ускоряет работу.\
Только для хранения весов (структура — отдельно).\
Подходит для инференса и дообучения.\
Поддерживается HuggingFace, PyTorch, JAX.

<span id="226a"></span>

**.bin**\
Универсальный или частный бинарный формат для весов моделей, особенно в HuggingFace и старом GGML.

<span id="c532"></span>

Может быть неформализован (структура зависит от фреймворка).\
Может содержать полные веса или квантованные данные.\
Подходит для загрузки в кастомные движки (например, llama.cpp).\
Требует точного знания, как интерпретировать содержимое.\
Часто используется в старых проектах или кастомных пайплайнах.

<span id="0725"></span>

**.gguf**\
Стандарт экосистемы GGML для запуска LLM в llama.cpp, Ollama, LM Studio.

<span id="da38"></span>

Включает всё в одном файле: веса, словарь, токенизатор, параметры модели.\
Поддерживает разные типы квантования (Q2_K, Q4_0, Q8_1 и т.д.).\
Хорошо сжат и оптимизирован под запуск на CPU, GPU, Android.\
Быстро загружается и легко обрабатывается C++ кодом.\
Основной выбор для локального запуска LLM (Gemma, LLaMA, Mistral).

<span id="f864"></span>

**.onnx**\
Кросс-фреймворк формат для запуска модели в разных средах (Windows, Web, C#, Java и др.).

<span id="e475"></span>

Стандартизирован, поддерживается многими фреймворками (PyTorch, TF, Keras).\
Упрощает перенос моделей между платформами.\
Подходит для инференса (не для обучения).\
Оптимизируется средствами ONNX Runtime (сжатие, квантование).\
Отличный выбор для запуска моделей в embedded-средах и .NET.

<span id="65b9"></span>

**.tflite**\
Формат для запуска моделей TensorFlow на мобильных устройствах (Android, iOS).

<span id="9d34"></span>

Очень компактный и быстрый.\
Поддерживает INT8 и FP16 квантование.\
Работает с интерпретатором LiteRT (бывший TensorFlow Lite), легко встраивается в Android.\
Нельзя обучать или дообучать — только запуск.\
Изначально был рассчитан на компактные CNN/RNN-модели, но сейчас через LiteRT и MediaPipe LLM Inference на нём запускают и небольшие LLM (например, Gemma).

<span id="367f"></span>

**Формат MLC LLM**\
Запуск LLM на Android/iOS/GPU через компиляцию; единого файла-контейнера нет — модель хранится как каталог с шардами весов и JSON-конфигами плюс скомпилированная библиотека.

<span id="cafb"></span>

Модель предварительно компилируется в эффективный байткод.\
Поддерживает Vulkan/Metal/OpenCL для ускорения.\
Используется для Gemma, Mistral, LLaMA на телефоне.\
Требует сложной подготовки (TVM, скрипты, настройка).\
Отлично подходит для мобильных приложений без серверов.

[ Copyright: Roman Kryvolapov ](https://t.me/RomanKryvolapov)
