Нейросети простым языком
Привет!
В интернете можно найти разные объяснения того, как работают нейросети, но те, что мне попадались, были либо слишком специфичны и ориентированы на специалистов, либо слишком упрощены.
Постарался написать свои объяснения, которые не были бы слишком упрощены, но при этом оставались по возможности понятными.
Статья на 10 процентов скомпилирована из других статей, на 30 процентов скомпилирована из множества диалогов с разными LLM и на 60 процентов “написана от руки” на основании статей и ответов.
Входящие данные
На входе нейросеть получает входящие данные в виде запроса пользователя. К запросу пользователя также добавляется общая информация, которая позволяет получить более точный ответ. Если нейросеть поддерживает RAG (Retrieval-Augmented Generation), то к запросу добавляются также:
-
Данные из векторной базы, если в нее предварительно добавили то, что нужно для генерации более актуального ответа
-
Данные, извлечённые из интернет-страницы или документа — чтобы сократить их размер, часто используется специализированная embedding нейросеть: она находит все подходящие куски текста и добавляет их к контексту запроса
Пример настроек embedding нейросети для Chrome расширения Page Assist

Как работает 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 у меня есть отдельная статья:
И отдельная — про векторные базы:
В некоторых нейросетях для отделения запроса пользователя, контекста, полученного при помощи RAG и общего сообщения используется разметка.
Пример:
context:
это данные полученные из интернет при помощи embedding нейросети или из векторного хранилища и отсортированные по совпадению score по мере убывания
{ "context": [ "Пенициллин — это первый открытый антибиотик, произведённый из плесневого грибка рода Penicillium. Он используется для лечения бактериальных инфекций, таких как ангина, сифилис и пневмония. Пенициллин разрушает клеточную стенку бактерий, что приводит к их гибели.", "Некоторые люди имеют аллергию на пенициллин. Это может вызвать серьёзные реакции, включая анафилаксию, поэтому перед назначением препарата важно уточнить наличие аллергии у пациента." ], "instructions": "Ответь простыми словами, используя только информацию из контекста. Если ответа нет — напиши 'Информация не найдена'.", "question": "Что такое пенициллин и для чего он применяется?"}Из чего состоит модель
Трансформер (Transformer)
Архитектура нейросети, которая стала основой для современных языковых моделей, таких как ChatGPT, BERT, LLaMA, Gemma и многие другие.
Он был впервые описан в научной статье 2017 года под названием “Attention is All You Need”.
Проще говоря, трансформер — это “умная машина”, которая умеет читать и понимать текст, обрабатывая все слова одновременно, а не по одному, как это делали предыдущие модели (например, RNN, LSTM).
Слои трансформера (Transformer Layers)
это повторяющиеся блоки, через которые проходит каждый токен внутри модели.
Один слой (или “трансформер-блок”) включает в себя:
*Self-Attention:* токен “смотрит” на другие токены и решает, кто важен.
Feed-Forward Network:
уточнение и преобразование каждого токена.
Layer Normalization или LayerNorm:
стабилизация вычислений.
Residual Connections:
чтобы модель не “забыла” начальную информацию.
LayerNorm может быть до или после Residual (Pre-LN vs Post-LN). Практически все современные LLM (Llama, Qwen, Gemma, DeepSeek) используют Pre-LN, а вместо классического LayerNorm — его упрощённый вариант RMSNorm. Шаги дальше в статье описаны по классической схеме Post-LN, так нагляднее.
и другие этапы.
Каждый слой трансформера (Transformer Block) — это отдельный набор параметров, которые:
независимо обучаются,
независимо применяются к входным данным,
дают всё более “глубокое” понимание смысла и контекста.
Что значит “32 слоя”:
каждый токен проходит через 32 такие операции, последовательно одну за другой, после каждого слоя токен становится всё более информированным, т.е. его представление (вектор) всё глубже отражает контекст
Количество слоев популярных открытых моделей (transformer layers, по официальным конфигурациям, середина 2026 года):
| Модель | Количество слоёв ||---------------------------|------------------|| 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)
это промежуточные выходы нейросети после применения функций и слоёв.
Можно сказать, что активации это данные, которые "живут внутри сети" на каждом этапе прохода входа через модель.
Тензор (Tensor)
в нейросети это многомерный массив чисел, с которым работают слои модели.
Входные данные:
Текст → токены → эмбеддинги → тензор batch_size × seq_len × embedding_dim.
Веса модели:
Весовые матрицы в слоях — тоже тензоры.
Промежуточные представления (активации):
Выход каждого слоя (например, LayerNorm, Attention) — тензор.
Градиенты:
При обучении модель считает градиенты (тензоры) для обновления весов.
Шаги работы нейросети
Шаг 1: Токенизация (из текста / изображения в токены)
Токенизация текста
LLM не работает со словами напрямую — она работает с токенами (частями слов или символами), преобразованными в числа.
Пример:
Текст пользователя:"Привет, мир"
ID токена в словаре модели для слова:"Привет" = 1123"," = 15"мир" = 345
Получаем массив ID токенов:["Привет", ",", "мир"] = [1123, 15, 345]
Как это достигается:
Используется алгоритм вроде Byte Pair Encoding (BPE), Unigram, WordPiece или SentencePiece.
BPE-токенизатор находит наиболее частые пары символов.
WordPiece строит токены на основе вероятности иерархических разбиений.
Часто токены — это не отдельные слова, а части слов.
В словаре есть специальные маркеры границ слов: в BPE-словарях пробел кодируется в начале токена, а в WordPiece маркером “##” помечаются токены-продолжения слова. Поэтому ни один субтокен не пересекает границу двух слов.
Сам разбор строки зависит от алгоритма: WordPiece идёт "жадно" — берёт максимально длинный токен, совпадающий с началом оставшейся строки, BPE последовательно применяет выученные правила слияния пар, а Unigram выбирает наиболее вероятное разбиение целиком.
Пример:
Текст пользователя:"невероятно"
ID токена в словаре модели для части слов:"не" = 24"вероят" = 126"но" = 36Получаем массив ID токенов:["не", "вероят", "но"] = [24, 126, 36]
В WordPiece-словаре то же слово могло бы разбиться как:("не", "##вероятно")где "##" помечает продолжение словаРазные по смыслу слова могут иметь общие токены, например "привет" и "пример" могут иметь общий токен "при":
Хотя «при» повторяется в обоих словах, модель сразу смотрит не только на этот кусочек, но и на соседние токены и на всю фразу (об этом дальше в статье).
Сначала «при» превращается в вектор — просто набор чисел, описывающих эту часть слова.
Затем трансформер (многослойная сеть) смешивает этот вектор с векторами соседних токенов («вет» или «мер») и добавляет информацию о позиции в предложении.
В итоге в первом слое «при» в «привет» уже отличается от «при» в «пример», потому что туда «подмешался» разный контекст.
То есть общий кусочек «при» сам по себе нейтральный, а смысл формируется дальше по слоям на основе окружения.
Почему токены, а не слова:
Меньше словарь = экономия памяти.
Лучше обрабатываются редкие и составные слова.
Позволяет модели “учиться” понимать структуру слов.
Размер словаря, или “vocab_size”, определяет, сколько уникальных токенов может обрабатывать модель.
Больше словарь = меньше разбиений слов на части, но больше объём embedding-слоя.
Слово “программирование” может быть целиком одним токеном в модели с крупным словарём,
а может разбиться на части (“пр”, “ограм”, “мирование”) в модели с меньшим словарём.
Практический нюанс для читателей этого блога: кириллица токенизируется «дороже» латиницы. Токенизаторы обучены в основном на английском, поэтому русское или украинское слово чаще разбивается на большее число токенов, чем английское той же длины. Один и тот же текст по-русски занимает заметно больше токенов: быстрее заканчивается контекст, а в платных API это буквально дороже.
Размеры словаря популярных открытых моделей (по официальным конфигурациям, середина 2026 года):
Модель | Размер словаря |--------------------------|----------------|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-шаблон — принятую у конкретной модели разметку с ролями «система», «пользователь» и «ассистент». Упрощённо это выглядит так:
<|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 года):
| Модель | Максимальная длина контекста ||-------------------------|------------------------------|| 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 (из токена в вектор)
В LLM модели для каждого ID токена хранится Token Embedding, или массив чисел. Эти числа описывают, что означает этот токен, как если бы ты переводил слово в математическую форму.
Изначально token embedding имеет фиксированное значение для каждого токена, но затем уточняется по мере прохождения через слои.
Когда Token Embedding проходит через несколько слоёв трансформера он становится контекстуализированным: он учитывает значение всей фразы. На выходе мы получаем вектор, который содержит “смысл” слова в контексте.
Связи между векторами задаются матрицами весов между слоями.
Изначально эти веса инициализируются случайно или по специальному правилу (например, Xavier или He), а потом во время обучения для каждого веса вычисляют градиент потери (насколько ошибка растёт или падает при его изменении).
Алгоритм обратного распространения (“backpropagation”) рассчитывает, как изменение каждого веса повлияет на итоговую ошибку, а затем градиентным спуском (или его вариациями) корректирует эти веса.
Так постепенно усиливаются связи между теми компонентами векторов, которые вместе дают полезную информацию, и ослабевают все ненужные — получается набор взаимосвязанных векторов, оптимальных для решения задачи.
Размерность вектора (Embedding Size, d_model) на каждом слое трансформера
Количество измерений у token embedding фиксировано для всей модели и зависит от её архитектуры.
Пример:
Текст пользователя:"Привет"
ID токена в словаре модели для слова:"Привет" = 1123
Token embedding для ID токена 1123 = массив значений в формате числа с плавающей точкой из d_model= 4096 элементов:[0.034, 0.120, 0.905, ..., 0.028]Размер embedding size популярных открытых моделей (по официальным конфигурациям, середина 2026 года):
Модель | 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), а слой эмбеддингов и активации растут линейно.
матрица проекции при d_model = 4096занимает в 4 раза больше памяти, чемматрица проекции при d_model = 2048Числа, из которых состоят эти векторы и веса, могут храниться с разной точностью — про форматы чисел и квантование см. раздел в конце статьи.
Шаг 3: Positional Encoding / Embeddings
Без дополнительной информации фразы:
“Кот ест рыбу”
“Рыбу ест кот”
могли бы восприниматься одинаково, ведь набор слов одинаков.
Чтобы дать модели ощущение порядка, каждому токену (слову или части слова) добавляется позиционный вектор — набор чисел, который сообщает модели на каких позициях находятся слова.
Token embedding + Positional Encoding / Embeddings = Суммарный векторТак делали классические трансформеры, и на этой схеме нагляднее всего объяснять. У современных моделей позиция вносится иначе — внутри механизма внимания, поворотом векторов (RoPE, о нём ниже в этом шаге); суммарный вектор у них просто равен эмбедингу токена.
Positional Encoding / Embedding это:
Вектор такой же размерности, как у токена (d)
Представляет позицию в последовательности
Может быть задан формулой (sin/cos) или обучаем
Объединяется с вектором токена на входе в модель
Какой тип используется в моделях:
| Метод | Модель/семейство | Описание ||-----------------------------|-----------------------------|-----------------------------------------------|| 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 | абсолютных позиций |Пример:
Текст пользователя:"Привет, мир"
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
Синусоидальное позиционное кодирование используется в Transformer-моделях для предоставления информации о позиции токенов во входной последовательности. В отличие от рекуррентных сетей, Transformer не имеет встроенного механизма учета порядка токенов. Поэтому необходимо явно закодировать информацию о позиции каждого токена.
С точки зрения математики:
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.
Это создает более медленные колебания для более высоких измерений, что позволяет модели различать позиции на разных масштабах.
Пример:
pos = 0 (первый токен)d_model = 4 (размерность вектора позиционного кодирования)
Тогда вектор позиционного кодирования PE(0) будет иметь размерность 4.Вычислим каждый элемент:
i = 0:PE(0, 0) = sin(0 / 10000^(2*0/4)) = sin(0) = 0PE(0, 1) = cos(0 / 10000^(2*0/4)) = cos(0) = 1
i = 1:PE(0, 2) = sin(0 / 10000^(2*1/4)) = sin(0) = 0PE(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.8415PE(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.01PE(1, 3) = cos(1 / 10000^(2*1/4)) = cos(0.01) ≈ 1
PE(1) = [0.8415, 0.5403, 0.01, 1]
Важно:
В реальных Transformer-моделях позиционное кодирование обычно вычисляется для всех возможных позиций в последовательности заранее и сохраняется как таблица.
Затем, при подаче входной последовательности, соответствующие векторы позиционного кодирования добавляются к эмбеддингам токенов.
Выбор гиперпараметра 10000 является эмпирическим и может быть настроен в зависимости от конкретной задачи.
Относительные позиционные смещения:
Вместо того чтобы кодировать абсолютную позицию каждого токена, некоторые варианты трансформеров вводят относительные позиционные смещения. Это позволяет модели напрямую учитывать расстояние (и направление) между парами токенов, а не их «глобальную» позицию в последовательности.
Зачем нужны относительные смещения:
При генерации или обработке длинных текстов важно, как далеко друг от друга лежат слова, а не только их абсолютные индексы.
Абсолютные эмбеддинги плохо обобщаются на более длинные последовательности, чем те, на которых модель обучалась.
Относительные смещения дают большую гибкость: модель учится, например, «что на 3 позиции вправо может быть объект действия», независимо от того, где это предложение стоит в тексте.
Transformer-XL, относительные позиции через смещение ключей и запросов:
В классическом self-attention считаем:score_{i,j} = (Q_i · K_j) / sqrt(d_k)
В Transformer-XL вводят два дополнительных набора эмбеддингов:E^R[r] — эмбеддинг для относительного сдвига r = j - iU, 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)
Теперь, когда токены имеют не только “значение”, но и “позицию”, они проходят через механизм self-attention, благодаря которому каждое слово смотрит на другие слова, чтобы решить, на кого из них мне обратить внимание, чтобы лучше понять свой смысл.
Это похоже на то, как человек читает предложение: он может вернуться глазами к предыдущим словам, чтобы правильно понять контекст.
Self-Attention отвечает за понимание контекста — какие токены важны друг для друга, и позволяет каждому токену взвешенно смотреть на все остальные в последовательности и определять, на что обратить внимание при построении смысла.
Softmax
это функция, которая берёт на вход вектор чисел и превращает его в распределение вероятностей, где все значения неотрицательны и в сумме дают 1.
softmax(zᵢ) = exp(zᵢ) / ∑ⱼ exp(zⱼ)
Модель создаёт три представления для каждого токена:
Q (вопрос): что я ищу?
K (ключ): что я могу предложить?
V (значение): какую информацию я несу?
Каждое слово сравнивает свой Q с K всех остальных слов, чтобы узнать, на кого стоит смотреть. После этого оно собирает нужную информацию из V тех слов, которые оказались важными.
Для вычисления представлений используется матрица весов слоя- это обучаемый параметр нейросети, то есть она инициализируется случайно при создании модели и обучается вместе с остальными весами. Изначально случайная, потом становится «умной» за счёт обучения.
Как они обучаются:
На этапе обратного распространения (backpropagation) модель сравнивает свои предсказания с правильным ответом (например, следующий токен) и обновляет Wq, Wk, Wv по градиенту ошибки с помощью оптимизатора (например, Adam).
Параметры Wq, Wk и Wv могут быть общими или различными для каждой из голов, в зависимости от модели.
Wq (Query Projection):
Создаёт “вопрос” — что токен хочет найти в других токенах.
Определяет направление внимания.
Q токена = Суммарный вектор токена X * Вес слоя WqWk (Key Projection):
Создаёт “ключ” — что каждый токен предлагает другим.
Используется для сравнения с Q (насколько Qᵢ “похож” на Kⱼ).
K токена = Суммарный вектор токена X * Вес слоя WkWv (Value Projection):
Создаёт “информацию”, которую токен может передать, если на него обратили внимание.
V токена = Суммарный вектор токена X * Вес слоя Wv
Голова внимания (attention-head):
Также у моделей существуют Голова внимания (attention-head). Каждая голова внимания (attention-head) обрабатывает входной вектор по-своему, через свои Q, K, V проекции, и смотрит на разные аспекты предложения.
У каждой attention-head есть своя точка зрения:
одна голова может отслеживать грамматику (например, подлежащее и сказуемое),
другая — смысловые связи (например, кто на что действует),
третья — позиции, контекст и т. д.
Вместо одной “точки зрения” — сразу несколько.
Каждая голова видит весь текст, но — анализирует его по-своему, через проекцию и внимание.
Пример:
В предложении
“Мальчик, который держал мяч, убежал.”
Разные головы могут видеть
Голова 1: “мальчик” ↔ “убежал” → кто выполняет действие
Голова 2: “который” ↔ “держал” → вложенная грамматическая связь
Голова 3: “мяч” ↔ “держал” → объект действия
Каждая голова выдаёт своё представление для каждого токена, с учётом своих “наблюдений”.
*Архитектура Multi-Head Attention (MHA):*
Классическая реализация self-attention, как в оригинальной статье “Attention is All You Need”.
Что происходит:
Есть несколько голов
Каждая голова имеет свои Wq, Wk, Wv
Каждая голова по-своему анализирует весь контекст
Результаты всех голов объединяются и проходят через общую Wo
Плюсы:
Гибкость: каждая голова “смотрит” на вход по-своему
Отлично работает при больших вычислительных ресурсах
Минусы:
Очень дорого по памяти и скорости при большом количестве голов
Особенно при длинных последовательностях
*Архитектура Multi-Query Attention (MQA):*
Оптимизированная версия внимания, используемая в PaLM, Falcon, StarCoder и других, чтобы снизить нагрузку на память и ускорить инференс.
Что происходит:
По одной Wq на голову
Только один Wk и один Wv
Все головы используют одни и те же ключи и значения
Плюсы:
Меньше памяти: K и V хранятся в одном экземпляре
Быстрее генерация: меньше данных хранится между шагами
Минусы:
Меньше гибкости (все головы “смотрят” на одинаковые K и V)
Может слегка ухудшить качество на сложных задачах
Архитектура Grouped Query Attention (GQA):
комбинированный подход из предыдущих двух. Именно GQA используется в большинстве современных моделей: Llama 3/4, Qwen 3, Gemma 2/3/4, GPT-OSS.
Пример:
Например, в архитектуре нейросети 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).
В итоге в каждом из 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 слоёв этот набор весов свой.
Количество голов внимания (attention heads; запросы Q / KV-головы) популярных открытых моделей:
| Модель | 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-головы к нему неприменимо.
*Каузальное маскирование (Causal Masking):*
применяется в некоторых моделях. Когда языковая модель (LLM) генерирует текст, она должна предсказывать следующее слово на основе только предыдущих слов.
Это означает, что Токен не должен иметь доступ к токенам, которые идут после него.
Чтобы обеспечить это ограничение, используется каузальное маскирование — это механизм, который “запрещает” вниманию видеть будущие токены.
Как это работает:
В обычном self-attention каждый токен “смотрит” на все токены в последовательности, включая и будущие.
При генерации это неприемлемо, потому что это было бы “жульничеством” — модель видит ответ заранее.
Чтобы это предотвратить, при вычислении attention применяют маску — специальную матрицу, называемую attention mask.
Для последовательности длины n создаётся треугольная маска, в которой:
Значения выше диагонали заменяются на -∞ или большое отрицательное число.
После этого применяется softmax, и эти значения превращаются в нулевое внимание.
Пример маски (для n = 4 токенов):[ [0, -∞, -∞, -∞], [0, 0, -∞, -∞], [0, 0, 0, -∞], [0, 0, 0, 0]]
Это означает:Токен 0 видит только себя.Токен 1 видит себя и токен 0.Токен 2 видит себя, токен 1 и токен 0.Токен 3 видит всё, что до него.
Где используется Causal Masking:
GPT, LLaMA, Mistral, Gemma и прочие автогенеративные модели обязательно используют каузальное маскирование.
BERT, наоборот, использует bidirectional attention — токен может видеть весь контекст (в т.ч. будущее), потому что задача другая — не генерация, а понимание.
Почему это важно, без causal masking:
Модель при обучении будет “подглядывать” на правильный ответ (следующий токен).
Это приведёт к плохой генерации при использовании модели в inference, когда будущее неизвестно.
Роль позиции в Self-Attention:
Без Position Encoding, Self-Attention видит только смысл слов, но не их порядок. С добавленным Position Encoding, Self-Attention начинает учитывать не только, что написано, но и где это находится:
“Мальчик” раньше “читал” → возможно, он субъект
“Книга” рядом с “читал” → скорее всего, объект
Что в итоге делает трансформер:
Каждое слово получает информацию о своей позиции
Через self-attention каждое слово “спрашивает” все остальные:
“Что вы значите для меня в этом контексте?”
Модель складывает эти ответы и получает глубокое понимание смысла всей фразы
Повторяет это на каждом слое (обычно 12–40 раз), углубляя “понимание”
Текст пользователя:"Привет, мир"
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]и для других токенов
*Вычисление Attention Score между токенами:* На этом этапе мы получили Q, K и V для одного токена и одной головы внимания. Далее можно перейти к вычислению attention score между токенами, например, между “Привет” и “мир”, с использованием формулы:
С точки зрения математики:
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.Пример:
Входные данные:
| Токен | 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.694exp(0.2296) ≈ 1.258exp(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]
Этот вектор - выход внимания (attention output), новое представление токена “Привет”, в котором учтён его контекст: и сам он, и соседи. Именно такие векторы потом идут либо в следующий attention-слой, либо на выход модели. Важно не путать его с attention score: score - это скалярные веса из softmax (0.36, 0.27, 0.37 выше), а здесь - взвешенная ими сумма V-векторов.
Обратите внимание: для простоты пример посчитан без каузальной маски - “Привет” здесь «видит» и стоящий после него токен “мир”. В настоящей LLM с causal masking токен собирал бы информацию только с себя и предыдущих токенов.
Сравнение токена сам с собой нужно потому, что в self-attention каждый токен “смотрит” на все токены, включая сам себя.
Это необходимо для того, чтобы:
Сохранить информацию о самом токене — иначе он бы “терялся” на фоне остальных.
Научиться “усиливать” или “подавлять” себя — например, в некоторых языковых ситуациях токен важен сам по себе (например, личное местоимение), а иногда его контекст важнее.
Матрица внимания — квадратная (n × n), и на её диагонали стоит как раз self-to-self attention; при каузальной маске от неё остаётся нижний треугольник.
Формально attention — это веса, по которым токен агрегирует информацию от других токенов (в т.ч. от самого себя) и w1 — это внимание “Привет” к самому себе. Если его не считать — токен “Привет” вообще не участвовал бы в своем собственном выходе.
Пример:
Он сказал, что он придет.
Когда модель обрабатывает токен “он”, ей нужно:
“посмотреть” на другие токены — чтобы понять контекст,
но и сам токен “он” тоже важен, чтобы не потерять информацию о том, о ком идет речь.
Материалы:
Wikipedia: Attention (machine learning)
Understanding Q,K,V In Transformer( Self Attention)
What is Query, Key, and Value (QKV) in the Transformer Architecture and Why Are They Used?
Шаг 5: Объединение голов внимания (Head Concatenation, Concatenated Multi-Head Attention)
Предыдущие расчёты происходили для каждой из голов внимания, теперь необходимо объединить результаты. Напомню, результаты разные из-за разных Wq, Wk, Wv для разных голов.
С точки зрения математики:
h - количество голов вниманияd_k - размерность выхода внимания каждой головыHeadOutput_i - выход внимания i-й головы.
Concatenation (Объединение векторов в один вектор по координатам):
concat = [HeadOutput_1, HeadOutput_2, ..., HeadOutput_h]Пример:
Входные данные:Допустим, размерность: 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.4062concat[1] = HeadOutput_1[1] = 0.4621concat[2] = HeadOutput_1[2] = 0.5129concat[3] = HeadOutput_2[0] = 0.22concat[4] = HeadOutput_2[1] = 0.33concat[5] = HeadOutput_2[2] = 0.44
concat = [0.4062, 0.4621, 0.5129, 0.22, 0.33, 0.44]Шаг 6: Output Projection (Wo)
После объединения attention-выходов с разных голов, они обычно пропускаются через линейный слой (Dense Layer), чтобы вернуть их в исходное пространство размерности модели.
С точки зрения математики:
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)Пример:
Входные данные:Размерность модели 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 (остаточного соединения)
это суммирование входа слоя с его выходом. Мы складываем выход с входным вектором, который подавался на вход блока (input embedding или выход предыдущего слоя). В реальной модели вход и выход блока имеют одинаковую размерность d_model и складываются поэлементно; в нашем сквозном примере вход был 4-мерным, а выход — 3-мерным, поэтому для иллюстрации возьмём первые три компоненты входа.
Это используется, чтобы:
Сохранить исходную информацию (градиенты легче передаются назад).
Избежать “затухания” сигнала через множество слоёв.
Облегчить обучение даже очень глубоких нейросетей.
Входные данные:
Входной вектор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)
нормализует значения внутри одного вектора признаков, то есть по размерности признаков (feature dimension).
Зачем это нужно:
Устраняет смещение и масштабные различия между признаками.
Делает обучение более стабильным.
Ускоряет сходимость нейросети.
Лучше работает при маленьких батчах, в отличие от BatchNorm.
С точки зрения математики:
Вычисляем среднее значение (mean):μ = (1/n) * ∑ xₙ
Вычисляем стандартное отклонение (std):σ = sqrt((1/n) * ∑ (xₙ - μ)^2 + ε)Где ε - маленькое число, чтобы избежать деления на ноль.
Нормализуем каждый элемент:̂xₙ = (xₙ - μ) / σ
Опционально масштабируем и смещаем (обучаемые параметры):yₙ = γ * ̂xₙ + βПример:
Входные данные:
Возьмём выход 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)
FFN (Feed-Forward Network):
это компонент трансформера, который обрабатывает каждое слово (или токен) по отдельности, без учета других токенов.
Он применяется независимо к каждому токену после слоя внимания и представляет собой двухслойную нейросеть с нелинейной функцией активации.
Это позволяет модели улавливать более сложные зависимости.
MLP (Multilayer Perceptron):
это тип нейронной сети, состоящий из нескольких слоев полностью связанных нейронов. В контексте LLM и трансформеров MLP часто означает ту же самую Feed-Forward Network (FFN).
MLP = общее название архитектуры,
FFN = частный случай MLP, используемый внутри трансформеров.
С точки зрения математики:
x - входной вектор размерности dW1-матрица весов первого линейного слоя размерности (d_ff × d)b1-смещение первого слоя размерности d_ffW2-матрица весов второго линейного слоя размерности (d × d_ff)b2-смещение второго слоя размерности df-функция активации (например, 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Пример:
Входные данные:
Размерность скрытого слоя 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.0y[1] = 0 * x[0] + 1 * x[1] + 0 * x[2] + 0 * x[3] = 0 - 2.0 + 0 + 0 = -2.0y[2] = 0 * x[0] + 0 * x[1] + 1 * x[2] + 0 * x[3] = 0 + 0 + 0.5 + 0 = 0.5y[3] = 0 * x[0] + 0 * x[1] + 0 * x[2] + 1 * x[3] = 0 + 0 + 0 + 3.0 = 3.0y[4] = 1 * x[0] + 1 * x[1] + 1 * x[2] + 1 * x[3] = 1.0 - 2.0 + 0.5 + 3.0 = 2.5y[5] = 1 * x[0] + (-1) * x[1] + 1 * x[2] + (-1) * x[3] = 1.0 + 2.0 + 0.5 - 3.0 = 0.5y[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.25y[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 (второй слой нормализации)
После FFN к результату снова прибавляют вход этого блока (выход шага 8 — то, что подавалось в FFN) и снова нормализуют вектор — это помогает сохранить информацию и стабилизировать расчёты
Шаг 11: Подача выхода блока в следующий трансформер-блок
Получив на выходе одного блока трансформера нормализованный вектор, модель передаёт его на вход следующему блоку. Таких блоков может быть десятки — каждый добавляет всё больше «понимания» контекста
Шаг 12: Финальная LayerNorm нормализация после последнего блока
После последнего слоя ещё раз применяют LayerNorm — это заключительный штрих перед генерацией логитов, чтобы сгладить разброс значений
Шаг 13: Линейная проекция в логиты (Logits Projection) + Softmax
Нормализованный вектор из последнего слоя умножают на матрицу эмбеддингов (или отдельный линейный слой), чтобы получить «сырые» оценки (logits) для каждого токена словаря. Затем эти оценки превращают в вероятности с помощью Softmax или сразу передают в семплеры для отборки токена
Что такое logits:
"сырые" значения, которые ещё не нормализованы в вероятности.
Например, если словарь состоит из 50 000 токенов, то logits — это просто 50 000 чисел, одно для каждого токена.
Для каждого токена в последовательности модель сформировала его представление (hidden state).
Теперь надо преобразовать этот hidden state в логиты по словарю — оценку вероятности появления каждого возможного следующего токена.
Преобразование через Linear Layer:
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)
Полученные logits проходят через цепочку семплеров: сначала применяются штрафы за повторения и температура, затем фильтры (top-k, top-p и др.), а в конце — финальный семплер (миростат, жадный выбор или распределение) . Именно этот токен добавляют в контекст, и генерация повторяется с шага 1 до достижения конца или нужной длины.
В бекенде LLama.cpp можно составлять цепочки семплеров, сначала промежуточные, затем цепочка должна завершаться финальным семплером.
Порядок применения семплеров в llama.cpp:
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 (для разработчиков):
// подготовка параметров цепочки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-Kllama_sampler_chain_add(smpl, llama_sampler_init_typical (0.95f, 1)); // Locally Typicalllama_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-Pllama_sampler_chain_add(smpl, llama_sampler_init_xtc (0.9f, 1.0f, 1, LLAMA_DEFAULT_SEED)); // XTCllama_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)); // Temperaturellama_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 V1llama_sampler_chain_add(smpl, llama_sampler_init_mirostat_v2(LLAMA_DEFAULT_SEED, 5.0f, 0.1f)); // Mirostat V2llama_sampler_chain_add(smpl, llama_sampler_init_greedy()); // Greedyllama_sampler_chain_add(smpl, llama_sampler_init_dist(LLAMA_DEFAULT_SEED)); // Dist
// после генерации не забыть освободить цепочку:llama_sampler_free(smpl);Про llama.cpp у меня есть отдельная статья:
Пример страницы настройки семплеров из приложения https://github.com/a-ghorbani/pocketpal-ai


Промежуточные семплеры
Temperature (temp)
управляет степенью случайности при выборе следующего токена из распределения вероятностей, вычисленного моделью. Температура сужает или расширяет "воронку выбора" следующего слова. Чем она ниже — тем уже воронка, тем выше — тем шире.
После того как модель предсказала логиты (сырые значения вероятностей) для всех возможных токенов, они проходят через softmax-функцию. Температура влияет на эту функцию:
P(token) = softmax(logits / temperature)Эффект параметра temperature:
T = 1 логиты остаются как есть.
T < 1 (например, 0.7) модель усиливает различия между токенами — вероятные токены становятся ещё более вероятными. Поведение становится более предсказуемым.
T > 1 (например, 1.5) различия между токенами сглаживаются — возрастает шанс выбрать менее вероятный токен. Поведение становится более разнообразным и рискованным.
T → 0 softmax превращается в argmax, и всегда выбирается один самый вероятный токен.
Исходные логиты:Token A: 3.0Token B: 2.5Token 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 наиболее вероятных. Все остальные токены отбрасываются, независимо от их абсолютной вероятности.
есть 6 токенов с такими вероятностями:A: 0.35B: 0.30C: 0.15D: 0.10E: 0.06F: 0.04
Если top_k = 3, то оставим только:A: 0.35B: 0.30C: 0.15Top-p (nucleus sampling)
выбирается минимальное множество токенов с наибольшими вероятностями, сумма которых превышает заданный порог p. Остальные токены полностью отсекаются, даже если они имели высокий ранг.
модель выдала вероятности токенов:A: 0.50B: 0.25C: 0.15D: 0.08E: 0.02
При top_p = 0.9 набираем токены с наибольшими вероятностями,пока их сумма не достигнет порога:0.50 + 0.25 + 0.15 = 0.90 — остаются A, B, CD и 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: Итерация генерации
После того как модель выбрала следующий токен, его ID добавляется в конец входной последовательности — повторная токенизация не нужна, выбранный токен уже является токеном.
Затем цикл повторяется: модель считает внимание уже с учётом нового токена и предсказывает следующий, и так до тех пор, пока не будут выполнены условия прекращения.
Пересчитывать при этом все предыдущие токены заново не нужно: их K- и V-векторы сохраняются в KV-кэше, и на каждой итерации по-настоящему обрабатывается только новый токен — именно поэтому шаги генерации занимают примерно одинаковое время.
Отсюда две фазы работы модели, знакомые каждому по ощущению «подумала — и ровно печатает». Сначала prefill: весь промпт обрабатывается одним параллельным проходом, заполняя KV-кэш, — это пауза до первого слова, и она тем длиннее, чем длиннее промпт. Затем decode: токены генерируются по одному, каждый за примерно одинаковое время. Поэтому скорость модели описывают двумя разными числами — время до первого токена и токены в секунду.
Такой цикл позволяет по одному токену строить всю выходную последовательность.
Условия завершения
Генерация завершается, когда модель выдаёт специальный токен конца последовательности (EOS) или когда достигается заранее заданная максимальная длина последовательности.
Это ограничение предотвращает бесконечные или слишком длинные ответы.
Также генерацию останавливают стоп-последовательности: заранее заданные строки (например, маркер начала следующей реплики диалога), при появлении которых вывод обрывается.
Шаг 16: Преобразование токенов в слова
После того как модель сгенерировала нужное количество токенов, их преобразуют обратно в текст — это называется детокенизация. Токенизатор берёт последовательность чисел (токенов) и переводит их в слова и знаки препинания. На практике чат-интерфейсы делают это по ходу — это и есть стриминг: каждый выбранный токен сразу детокенизируется и дописывается на экран, поэтому ответ появляется по словам, а не целиком в конце.
Как модель обучали
Всё описанное выше — работа уже готовой модели. Сама она получается в несколько этапов.
Предобучение (pretraining):
Модель триллионы раз решает одну и ту же задачу — предсказать следующий токен в тексте из огромного корпуса (веб, книги, код). Никакой разметки не нужно: правильный ответ — это просто настоящий следующий токен. На этом этапе модель выучивает язык, факты и закономерности, но умеет только продолжать текст.
Инструкционное дообучение (SFT, Supervised Fine-Tuning):
Модель дообучают на примерах «запрос → хороший ответ», написанных людьми или отобранных из генераций. После этого она отвечает на вопросы, а не продолжает их.
Выравнивание (RLHF, DPO):
Люди сравнивают пары ответов модели и выбирают лучший; на этих предпочтениях модель дообучают давать полезные и безопасные ответы. Классический способ — обучение с подкреплением по человеческим оценкам (RLHF); более простая современная альтернатива — DPO, где предпочтения используются напрямую как обучающие примеры.
Reasoning-модели
«Думающие» модели из таблиц выше (DeepSeek R1, Qwen 3 в режиме размышлений, Gemma 4) механически ничем не отличаются: это те же трансформеры, генерирующие те же токены. Разница в обучении: их дополнительно учили с подкреплением на задачах с проверяемым ответом, награждая за правильный результат, — и модель сама выучилась перед ответом писать длинную цепочку рассуждений, черновик, который интерфейс потом прячет за спойлер. Платят за это токенами: «думающий» ответ может быть в разы длиннее и медленнее обычного.
Откуда берутся галлюцинации
Прямо из механики генерации. Модель обязана выбрать следующий токен всегда — у неё нет встроенного состояния «не знаю», есть только распределение вероятностей, из которого что-то будет выбрано. Если знаний о предмете в весах мало, распределение всё равно выдаст правдоподобно звучащее продолжение: правильную форму ответа с выдуманным содержанием. Поэтому галлюцинации — не баг, который однажды починят, а свойство самого подхода; их подавляют обучением (модель учат отказываться от ответа), инструментами вроде RAG и веб-поиска, но полностью не устраняют.
Интересные вопросы и ответы
Возможно ли в нескольких предложениях описать, как работает нейросеть?
Постараюсь. Модель — это огромная функция, обученная на одну задачу: по началу текста предсказать, какой токен (кусочек слова) вероятнее всего идёт следующим. Ваш вопрос превращается в последовательность токенов, модель вычисляет вероятности всех возможных продолжений, из них выбирается один токен, дописывается к тексту — и всё повторяется, токен за токеном, пока не сложится ответ. А всё «понимание» — в том, что за время обучения на огромном корпусе текстов веса модели научились предсказывать продолжение с учётом смысла и контекста всей фразы, а не просто по частоте слов.
Почему в LLM моделях на входе дается вопрос, а на выходе получается ответ, а не перефразированный вопрос?
Модель обучена на текстах «вопрос→ответ», поэтому при виде вопроса дальше генерируются не слова самого вопроса, а наиболее вероятное продолжение — ответ.
Во время инструкционного обучения (SFT) и RLHF её «поощряют» за полезные ответы, а не за повтор формулировки, поэтому параметры сдвигаются в сторону ответов.
В терминах вероятностей: для вопроса вероятность следующего токена-ответа выше, чем токена, повторяющего вопрос, и декодер выбирает именно его.
Данные, на которых обучается LLM модель, устаревают, какие есть возможности для того, чтобы модель генерировала актуальные ответы?
Retrieval-Augmented Generation (RAG):
Модель при запросе ищет в актуальной внешней базе (поиск по векторному или текстовому индексу) релевантные документы и использует их контекст при генерации ответа. Так можно получать свежую информацию без перетренировки основной сети
Параметр-эффективное дообучение (LoRA, Adapters):
Вместо полного переобучения модели встраивают небольшие адаптеры или low-rank матрицы (LoRA), которые обучаются на новых данных. Это позволяет быстро и недорого «научить» модель новым фактам или доменам
Про LoRA я сделал отдельную статью:
Целевое редактирование весов (Model Editing):
Алгоритмы типа MEMIT/ROME локально корректируют веса модели для добавления или обновления конкретных фактов, не затрагивая остальную часть знаний
Отдельные базы знаний и графы:
Вместо хранения фактов внутри параметров LLM выносите их в внешние KB или графы знаний, которые регулярно обновляются, а модель лишь запрашивает у них информацию
Интеграция с веб-поиском и API:
Подключение модели к живым источникам — встроенный веб-поиск в чат-ботах, вызов внешних API и инструментов (function calling, MCP-серверы) — напрямую возвращает свежее содержимое.
Каким образом LLM модель понимает различные языки?
Модель “понимает” разные языки благодаря тому, что в процессе обучения она видит тексты на многих из них и учится предсказывать следующий фрагмент независимо от языка. Основные моменты:
Большой мультиязычный корпус:
Для обучения собирают тексты (википедия, книги, веб-страницы из Common Crawl и прочие) на десятках и сотнях языков. Например, в открытой модели BLOOM было около 46 языков, причём доля каждого зависит от объёма доступных данных
Общая токенизация subword:
Используются алгоритмы вроде BPE или SentencePiece, которые разбивают слова на фрагменты (subword) и включают в словарь символы и последовательности из разных алфавитов. Так модель оперирует единым набором токенов для всех языков
Универсальная архитектура трансформера:
В трансформере одни и те же веса участвуют при обработке любых языков. Поэтому при обучении на разных языках модель находит общие паттерны (синтаксис, семантика) и использует перекрёстное перенесение знаний (cross-lingual transfer)
Не все языки и не во всех объёмах:
Обучают лишь на тех языках, где есть достаточный объём текстов. Редкие или низкоресурсные языки попадают в отдельные дообучения или получают меньшую долю данных, поэтому качество генерации на них ниже
Специальные дообучения и адаптеры:
Чтобы улучшить знание малоизвестных языков, применяют дообучение (continual pretraining) на локальных данных или вставляют адаптеры (adapters, LoRA), которые тонко корректируют знания модели под конкретный язык.
Дополнительная информация
Точность вектора
Также числа с плавающей точкой, из которых состоит вектор, могут иметь различную точность:
Меньшая точность уменьшает размер модели и ускоряет обработку.
Обычно модели обучаются на полной точности FP32 (float32), то есть вектор состоит из чисел размерностью 32 бита.
Квантование (Quantization)
для уменьшения точности для облегчения модели используется квантование —процесс преобразования чисел с плавающей точкой (например, FP32) в более компактные целочисленные представления (например, INT8), с сохранением приближённого значения.
При этом вводится небольшая потеря точности, но она часто не влияет критично на качество вывода.
Формат: 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 бита на вес).Также у моделей могут быть дополнительные параметры квантования:
_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 байт.
*QAT (Quantization-Aware Training) квантование:* это метод квантования нейронных сетей, при котором квантование учитывается уже во время обучения модели. Он позволяет добиться почти такой же точности, как и у оригинальной модели с float-параметрами, при этом модель будет использовать более компактные int8 или другие низкоразрядные форматы, пригодные для эффективного запуска на устройствах с ограниченными ресурсами (например, смартфонах или микроконтроллерах).
Как работает QAT — поэтапно:
Модель в float32:
Обучение начинается с обычной модели, использующей числа с плавающей точкой (обычно float32). Это обеспечивает высокую точность и стабильность обучения.
Имитация квантования во время forward pass (fake quantization):
При каждом проходе вперёд значения (веса, активации) эмулируются как квантованные, т.е. они преобразуются в int8, а затем обратно в float32. Это даёт возможность модели «увидеть» ошибки квантования ещё на стадии обучения.
float32 → int8 → float32
Таким образом, во время обратного распространения (backpropagation) градиенты считаются по float32-версии, но ошибки из-за квантования всё равно влияют на обучение.
Обновление параметров (backward pass):
Градиенты считаются как обычно, но с учётом искажений от fake quantization. Это позволяет модели адаптироваться к тому, что веса и активации будут впоследствии использоваться в низкой точности.
Экспорт финальной модели в int8:
После завершения обучения веса действительно квантуются в int8, и модель может быть скомпилирована и запущена в производственном окружении.
Что именно квантуется:
Веса (weights) — float32 → int8
Активации (activations) — float32 → int8
(Иногда также квантуются градиенты и промежуточные состояния, но это редкость.)
Зачем использовать QAT:
Выше точность, чем у post-training quantization (PTQ)
Низкое потребление памяти
Быстрее выполнение на CPU/GPU/NPUs с поддержкой int8
Особенно важно для мобильных и embedded-устройств (например, Android)
Сколько памяти нужно модели
Грубая формула: число параметров × байт на вес, плюс KV-кэш, плюс немного на активации.
Модель 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 моделей
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 моделей
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 моделей
.pt / .pth
Используются в PyTorch для сохранения обученных моделей
Хранят веса и структуру модели (или только веса).
Основаны на сериализации Python (pickle), что делает их не очень безопасными.
Подходят для обучения и дообучения.
Не оптимизированы для мобильного вывода или внешнего инференса
Могут занимать много памяти (FP32).
.safetensors
Альтернатива .pt, безопасный и быстрый формат для PyTorch.
Не использует pickle, а значит безопасен при загрузке.
Поддерживает параллельную загрузку, что ускоряет работу.
Только для хранения весов (структура — отдельно).
Подходит для инференса и дообучения.
Поддерживается HuggingFace, PyTorch, JAX.
.bin
Универсальный или частный бинарный формат для весов моделей, особенно в HuggingFace и старом GGML.
Может быть неформализован (структура зависит от фреймворка).
Может содержать полные веса или квантованные данные.
Подходит для загрузки в кастомные движки (например, llama.cpp).
Требует точного знания, как интерпретировать содержимое.
Часто используется в старых проектах или кастомных пайплайнах.
.gguf
Стандарт экосистемы GGML для запуска LLM в llama.cpp, Ollama, LM Studio.
Включает всё в одном файле: веса, словарь, токенизатор, параметры модели.
Поддерживает разные типы квантования (Q2_K, Q4_0, Q8_1 и т.д.).
Хорошо сжат и оптимизирован под запуск на CPU, GPU, Android.
Быстро загружается и легко обрабатывается C++ кодом.
Основной выбор для локального запуска LLM (Gemma, LLaMA, Mistral).
.onnx
Кросс-фреймворк формат для запуска модели в разных средах (Windows, Web, C#, Java и др.).
Стандартизирован, поддерживается многими фреймворками (PyTorch, TF, Keras).
Упрощает перенос моделей между платформами.
Подходит для инференса (не для обучения).
Оптимизируется средствами ONNX Runtime (сжатие, квантование).
Отличный выбор для запуска моделей в embedded-средах и .NET.
.tflite
Формат для запуска моделей TensorFlow на мобильных устройствах (Android, iOS).
Очень компактный и быстрый.
Поддерживает INT8 и FP16 квантование.
Работает с интерпретатором LiteRT (бывший TensorFlow Lite), легко встраивается в Android.
Нельзя обучать или дообучать — только запуск.
Изначально был рассчитан на компактные CNN/RNN-модели, но сейчас через LiteRT и MediaPipe LLM Inference на нём запускают и небольшие LLM (например, Gemma).
Формат MLC LLM
Запуск LLM на Android/iOS/GPU через компиляцию; единого файла-контейнера нет — модель хранится как каталог с шардами весов и JSON-конфигами плюс скомпилированная библиотека.
Модель предварительно компилируется в эффективный байткод.
Поддерживает Vulkan/Metal/OpenCL для ускорения.
Используется для Gemma, Mistral, LLaMA на телефоне.
Требует сложной подготовки (TVM, скрипты, настройка).
Отлично подходит для мобильных приложений без серверов.