<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Machine Learning on kenji.blog</title><link>http://kenji.blog/ru/categories/machine-learning/</link><description>Recent content in Machine Learning on kenji.blog</description><generator>Hugo -- gohugo.io</generator><language>ru</language><copyright>kenjinote</copyright><lastBuildDate>Fri, 11 Sep 2026 22:00:00 +0900</lastBuildDate><atom:link href="http://kenji.blog/ru/categories/machine-learning/index.xml" rel="self" type="application/rss+xml"/><item><title>【Для начинающих】Разбор математической структуры модели Transformer</title><link>http://kenji.blog/ru/p/transformer-mathematical-structure/</link><pubDate>Fri, 11 Sep 2026 22:00:00 +0900</pubDate><guid>http://kenji.blog/ru/p/transformer-mathematical-structure/</guid><description>&lt;img src="http://kenji.blog/p/transformer-mathematical-structure/img/eyecatch.jpg" alt="Featured image of post 【Для начинающих】Разбор математической структуры модели Transformer" />&lt;h1 id="введение-зачем-изучать-математику-transformer">Введение: зачем изучать математику Transformer?
&lt;/h1>&lt;p>Архитектура «Transformer» — это архитектура, о которой можно смело сказать, что она переписала историю современной обработки естественного языка (NLP) и ИИ в целом. Впервые предложенная в 2017 году исследователями Google в статье «Attention Is All You Need», эта модель функционирует как сердце крупномасштабных языковых моделей (LLM), которые сейчас доминируют в мире, таких как серия GPT от OpenAI (базовая технология ChatGPT), BERT от Google и Claude от Anthropic.&lt;/p>
&lt;p>Однако, хотя часто встречаются качественные объяснения работы Transformer, такие как «понимание контекста с помощью механизма внимания (Attention)», на самом деле существует на удивление мало подробных объяснений &lt;strong>математической структуры&lt;/strong>, стоящей за этим, для начинающих. Чтобы по-настоящему понять, как ИИ обрабатывает «слова» как «математические формулы» и генерирует удивительно естественные тексты, необходимо разобраться в его математических механизмах.&lt;/p>
&lt;p>В этой статье мы подробно и доступно объясним математическую структуру, которая является сердцем Transformer: «Механизм внутреннего внимания (Self-Attention)», «Модель Запрос-Ключ-Значение (Q/K/V)», «Нормализация с помощью функции Softmax» и «Позиционное кодирование (Positional Encoding)». Статья предназначена для тех, кто обладает базовыми знаниями в области математики и программирования (понимает концепции матриц и производных на уровне старшей школы).&lt;/p>
&lt;p>Вас может ошеломить обилие формул, но за каждым вычислением стоит четкий «смысл». К тому моменту, как вы дочитаете эту статью, вы должны понять, что Transformer — это не просто волшебный черный ящик, а тщательно спроектированный кристалл математики и статистики.&lt;/p>
&lt;hr>
&lt;h1 id="1-ограничения-традиционных-методов-и-инновационность-transformer">1. Ограничения традиционных методов и инновационность Transformer
&lt;/h1>&lt;p>До появления Transformer в обработке естественного языка доминировали рекуррентные нейронные сети (RNN) и их производные, такие как LSTM (Long Short-Term Memory). RNN были разработаны для обработки временных рядов данных и читают текст слово за словом с самого начала.&lt;/p>
&lt;p>Однако у RNN было два фатальных недостатка:&lt;/p>
&lt;ol>
&lt;li>&lt;strong>Сложность изучения долгосрочных зависимостей&lt;/strong>: Когда предложение становится длинным, информация о словах, введенных в начале, затухает к тому времени, когда достигается конец (проблема исчезающего градиента).&lt;/li>
&lt;li>&lt;strong>Невозможность параллельных вычислений&lt;/strong>: Поскольку слова необходимо обрабатывать последовательно, трудно выполнять крупномасштабные параллельные вычисления с использованием GPU, что требует огромного времени на обучение.&lt;/li>
&lt;/ol>
&lt;p>Transformer полностью отбросил структуру RNN и произвел сдвиг парадигмы, используя исключительно «Attention» (внимание) для понимания контекста. Благодаря этому, независимо от длины последовательности, не происходит потери информации, а также становится возможным распараллеливание вычислений для максимального использования производительности GPU.&lt;/p>
&lt;hr>
&lt;h1 id="2-общая-архитектура-transformer">2. Общая архитектура Transformer
&lt;/h1>&lt;p>Сначала давайте взглянем на общую архитектуру Transformer. Transformer в общих чертах состоит из двух блоков: «Encoder» (кодировщик) и «Decoder» (декодировщик). На примере задачи перевода: Encoder преобразует исходный язык (например, английский) в математическое векторное представление, а Decoder на основе этого векторного представления генерирует целевой язык (например, японский/русский).&lt;/p>
&lt;p>Следующая диаграмма представляет собой упрощенную внутреннюю структуру блока Encoder.&lt;/p>
&lt;div class="mermaid">graph TD
A["Входные токены"] --> B["Входное вложение"]
B --> C["Позиционное кодирование"]
C --> D["Многоголовое внутреннее внимание"]
D --> E["Сложение и нормализация слоя"]
E --> F["Сеть прямой связи"]
F --> G["Сложение и нормализация слоя"]
G --> H["Вывод на следующий слой"]
C -.->|"Остаточное соединение"| E
E -.->|"Остаточное соединение"| G&lt;/div>
&lt;p>С этого момента мы шаг за шагом рассмотрим математические операции, выполняемые в каждом компоненте.&lt;/p>
&lt;hr>
&lt;h1 id="3-векторизация-слов-и-позиционное-кодирование-positional-encoding">3. Векторизация слов и позиционное кодирование (Positional Encoding)
&lt;/h1>&lt;p>Компьютеры не могут понимать текст как таковой. Введенный текст сначала разбивается на единицы, называемые «токенами (Token)», и каждый из них преобразуется в вектор фиксированной длины. Это и есть &lt;strong>Input Embedding&lt;/strong>.&lt;/p>
&lt;h2 id="31-математика-input-embedding">3.1 Математика Input Embedding
&lt;/h2>&lt;p>Пусть размер словаря (vocabulary) равен $V$, а размерность вектора вложения (embedding) равна $d_{model}$ (в оригинальной статье $d_{model} = 512$). Каждое слово $w_i$ преобразуется в вектор $x_i \in \mathbb{R}^{d_{model}}$ с использованием матрицы вложения $W_E \in \mathbb{R}^{V \times d_{model}}$.&lt;/p>
$$ x_i = W_E \cdot \text{one\_hot}(w_i) $$
&lt;p>Таким образом, весь текст представляется как матрица $X \in \mathbb{R}^{N \times d_{model}}$ (где $N$ — длина текста).&lt;/p>
&lt;h2 id="32-необходимость-и-формулы-позиционного-кодирования-positional-encoding">3.2 Необходимость и формулы позиционного кодирования (Positional Encoding)
&lt;/h2>&lt;p>Transformer не обрабатывает слова последовательно, как RNN, а обрабатывает все слова одновременно параллельно. С точки зрения скорости вычислений это большое преимущество, но в то же время вызывает проблему &lt;strong>потери важной информации о «порядке слов»&lt;/strong>. Например, «собака кусает человека» и «человек кусает собаку» имеют один и тот же набор входных слов, но совершенно разный смысл.&lt;/p>
&lt;p>Чтобы предоставить модели эту информацию о порядке слов, было придумано &lt;strong>Positional Encoding&lt;/strong>.
Позиционное кодирование $PE$ для $i$-го измерения слова, находящегося в позиции $pos$, вычисляется с использованием следующих тригонометрических функций:&lt;/p>
$$ PE_{(pos, 2i)} = \sin\left(\frac{pos}{10000^{2i/d_{model}}}\right) $$
$$ PE_{(pos, 2i+1)} = \cos\left(\frac{pos}{10000^{2i/d_{model}}}\right) $$
&lt;p>Где $pos$ — это позиция слова ($0, 1, 2, \dots, N-1$), а $i$ — индекс размерности вектора ($0, 1, \dots, d_{model}/2 - 1$).&lt;/p>
&lt;h3 id="почему-используются-синус-и-косинус">Почему используются синус и косинус?
&lt;/h3>&lt;p>На первый взгляд это выглядит как очень сложная и странная формула, но для этого есть глубокая математическая причина. Использование тригонометрических функций позволяет модели легко изучать &lt;strong>разницу не только в «абсолютной позиции», но и в «относительной позиции»&lt;/strong>.&lt;/p>
&lt;p>Вспомните теоремы сложения для тригонометрических функций, которые изучают в старшей школе:
&lt;/p>
$$ \sin(\alpha + \beta) = \sin\alpha \cos\beta + \cos\alpha \sin\beta $$
$$ \cos(\alpha + \beta) = \cos\alpha \cos\beta - \sin\alpha \sin\beta $$
&lt;p>Позиционное кодирование для позиции $pos + k$, сдвинутой на $k$ относительно позиции $pos$, может быть представлено как линейная комбинация позиционного кодирования позиции $pos$. Другими словами, используя матрицу $M_k$, это можно записать следующим образом:&lt;/p>
$$ PE_{pos+k} = M_k \cdot PE_{pos} $$
&lt;p>Благодаря этому механизм Attention позволяет легко распознавать относительное расстояние («насколько далеко друг от друга находятся слова») посредством вычисления скалярного произведения. Кроме того, комбинируя несколько синусоидальных и косинусоидальных волн с разной длиной волны, есть преимущество в том, что можно создать уникальный вектор позиции, независимо от длины текста.&lt;/p>
&lt;p>Итоговая входная матрица $X_{input}$ представляет собой сумму вектора вложения слов и этого позиционного кодирования.&lt;/p>
$$ X_{input} = X + PE $$
&lt;hr>
&lt;h1 id="4-глубокая-математика-внутреннего-внимания-self-attention">4. Глубокая математика внутреннего внимания (Self-Attention)
&lt;/h1>&lt;p>Наконец-то мы переходим к самому важному компоненту Transformer — &lt;strong>Self-Attention (Механизм внутреннего внимания)&lt;/strong>. Цель Self-Attention — «вычислить степень связи между всеми словами в тексте и обновить вектор каждого слова до более богатого представления с учетом контекста».&lt;/p>
&lt;p>Здесь используется аналогия с «поисковой системой»:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Query (Q)&lt;/strong>: Запрос (поисковый запрос). «Какую информацию я сейчас ищу?»&lt;/li>
&lt;li>&lt;strong>Key (K)&lt;/strong>: Ключ (заголовок). «Какая информация у меня есть?»&lt;/li>
&lt;li>&lt;strong>Value (V)&lt;/strong>: Значение (сущность). «Какую информацию я на самом деле предоставляю?»&lt;/li>
&lt;/ul>
&lt;h2 id="41-создание-матриц-q-k-v">4.1 Создание матриц $Q, K, V$
&lt;/h2>&lt;p>Для входной матрицы $X \in \mathbb{R}^{N \times d_{model}}$ (для упрощения мы здесь игнорируем размер батча (batch size)), умножая на обучаемые весовые матрицы $W^Q, W^K, W^V \in \mathbb{R}^{d_{model} \times d_k}$, мы вычисляем запрос $Q$, ключ $K$ и значение $V$. (Обычно $d_k = d_v = d_{model} / h$)&lt;/p>
$$ Q = X W^Q $$
$$ K = X W^K $$
$$ V = X W^V $$
&lt;p>Здесь $Q, K, V$ — все являются матрицами $\mathbb{R}^{N \times d_k}$.&lt;/p>
&lt;h2 id="42-вычисление-оценки-внимания-attention-score---скалярное-произведение">4.2 Вычисление оценки внимания (Attention Score) - Скалярное произведение
&lt;/h2>&lt;p>Чтобы измерить, насколько Query каждого слова связан с Key всех остальных слов, мы вычисляем &lt;strong>скалярное произведение&lt;/strong> векторов. В матричном виде это записывается следующим образом:&lt;/p>
$$ \text{Scores} = Q K^T $$
&lt;p>Каждый элемент $s_{ij}$ полученной таким образом матрицы $\text{Scores} \in \mathbb{R}^{N \times N}$ представляет собой скалярное произведение Query $i$-го слова и Key $j$-го слова, то есть «силу связи».&lt;/p>
&lt;h2 id="43-масштабирование-scale">4.3 Масштабирование (Scale)
&lt;/h2>&lt;p>При вычислении оценки (score) с помощью скалярного произведения возникает одна проблема. Если размерность вектора $d_k$ становится большой, значение скалярного произведения может стать экстремально большим или маленьким.&lt;/p>
&lt;p>Давайте докажем это математически.
Предположим, что каждый элемент запроса $q \sim \mathcal{N}(0, 1)$ и каждый элемент ключа $k \sim \mathcal{N}(0, 1)$ следуют независимому стандартному нормальному распределению.
Найдем среднее и дисперсию скалярного произведения $q \cdot k = \sum_{i=1}^{d_k} q_i k_i$.
Среднее: Так как $\mathbb{E}[q_i k_i] = \mathbb{E}[q_i] \mathbb{E}[k_i] = 0 \times 0 = 0$, среднее суммы также равно $0$.
Дисперсия: Из независимости дисперсия $q_i k_i$ равна $\text{Var}(q_i k_i) = \mathbb{E}[(q_i k_i)^2] - (\mathbb{E}[q_i k_i])^2 = 1 \times 1 - 0 = 1$.
Следовательно, дисперсия всего скалярного произведения будет равна размерности $d_k$.&lt;/p>
$$ \text{Var}(q \cdot k) = d_k $$
&lt;p>Когда дисперсия становится большой, градиенты функции Softmax, применяемой после этого, становятся экстремально маленькими для значений, отличных от максимального, происходит «исчезновение градиента», и обучение останавливается.
Чтобы предотвратить это, оценка (score) делится (масштабируется) на $\sqrt{d_k}$, чтобы дисперсия всегда оставалась равной $1$.&lt;/p>
$$ \text{Scaled Scores} = \frac{Q K^T}{\sqrt{d_k}} $$
&lt;h2 id="44-преобразование-в-вероятности-с-помощью-функции-softmax">4.4 Преобразование в вероятности с помощью функции Softmax
&lt;/h2>&lt;p>К полученным оценкам по строкам применяется &lt;strong>функция Softmax&lt;/strong>, чтобы преобразовать их в распределение вероятностей (веса), сумма которых равна $1$.&lt;/p>
$$ a_{ij} = \text{softmax}(s_i)_j = \frac{\exp(s_{ij} / \sqrt{d_k})}{\sum_{m=1}^N \exp(s_{im} / \sqrt{d_k})} $$
&lt;p>Матрица $A \in \mathbb{R}^{N \times N}$ называется матрицей весов внимания (Attention Weight). Глядя на каждую строку $i$ этой матрицы, мы видим значения от 0 до 1, которые показывают, «насколько много внимания (Attention) следует уделить любому другому слову $j$ для понимания слова $i$».&lt;/p>
&lt;h2 id="45-взвешенная-сумма-value">4.5 Взвешенная сумма Value
&lt;/h2>&lt;p>Наконец, используя полученную матрицу весов внимания $A$, вычисляется взвешенная сумма матрицы Value $V$.&lt;/p>
$$ \text{Output} = A V = \text{softmax}\left(\frac{Q K^T}{\sqrt{d_k}}\right) V $$
&lt;p>Матрица $Z \in \mathbb{R}^{N \times d_v}$, полученная в результате этой операции, представляет собой набор «векторных представлений слов, обновленных с учетом контекста».
Это и есть полная картина &lt;strong>Scaled Dot-Product Attention&lt;/strong>, определенная в оригинальной статье.&lt;/p>
&lt;hr>
&lt;h1 id="5-multi-head-attention-многоголовое-внимание">5. Multi-Head Attention (Многоголовое внимание)
&lt;/h1>&lt;p>Возможно, что с помощью всего одного вычисления Attention (одна голова) контекст будет понят только с одной точки зрения (например, «грамматические отношения»). Поэтому для одновременного учета разнообразных семантических и синтаксических отношений в языке (таких как «подлежащее и сказуемое», «местоимение и его референт» и т.д.) был введен механизм &lt;strong>Multi-Head Attention&lt;/strong>.&lt;/p>
&lt;p>Ранее описанные процессы генерации $Q, K, V$ и вычисления Attention выполняются параллельно $h$ раз (количество голов, в оригинальной статье $h=8$).&lt;/p>
$$ \text{head}_i = \text{Attention}(X W_i^Q, X W_i^K, X W_i^V) $$
&lt;p>Здесь $W_i^Q, W_i^K, W_i^V \in \mathbb{R}^{d_{model} \times d_k}$ — обучаемые весовые матрицы, специфичные для $i$-й головы.&lt;/p>
&lt;p>Результаты $\text{head}_i \in \mathbb{R}^{N \times d_v}$, выведенные каждой головой, объединяются (Concatenate) по горизонтали.&lt;/p>
$$ \text{Concat}(\text{head}_1, \dots, \text{head}_h) \in \mathbb{R}^{N \times (h \cdot d_v)} $$
&lt;p>Поскольку обычно устанавливается так, что $h \cdot d_v = d_{model}$, размерность после объединения возвращается к исходной $d_{model}$. Наконец, эта матрица умножается на весовую матрицу $W^O \in \mathbb{R}^{d_{model} \times d_{model}}$ для получения окончательного вывода.&lt;/p>
$$ \text{MultiHead}(Q, K, V) = \text{Concat}(\text{head}_1, \dots, \text{head}_h) W^O $$
&lt;div class="mermaid">graph TD
X["Вход X"] --> Q1["Q1"]
X --> K1["K1"]
X --> V1["V1"]
Q1 &amp; K1 &amp; V1 --> H1["Голова 1"]
X --> Q2["Q2"]
X --> K2["K2"]
X --> V2["V2"]
Q2 &amp; K2 &amp; V2 --> H2["Голова 2"]
X --> QN["..."]
X --> KN["..."]
X --> VN["..."]
QN &amp; KN &amp; VN --> HN["Голова h"]
H1 &amp; H2 &amp; HN --> C["Объединить"]
C --> WO["Умножить на WO"]
WO --> OUT["Многоголовый вывод"]&lt;/div>
&lt;hr>
&lt;h1 id="6-сеть-прямой-связи-feed-forward-neural-network-ffn">6. Сеть прямой связи (Feed-Forward Neural Network, FFN)
&lt;/h1>&lt;p>Вывод Multi-Head Attention затем передается в &lt;strong>Position-wise Feed-Forward Network (FFN)&lt;/strong>.
Это двухслойная полносвязная нейронная сеть, применяемая «независимо к каждой позиции (слову)» в последовательности.&lt;/p>
&lt;p>В виде формулы это выражается следующим образом:&lt;/p>
$$ \text{FFN}(x) = \max(0, x W_1 + b_1) W_2 + b_2 $$
&lt;p>Где $\max(0, z)$ представляет функцию активации ReLU (Rectified Linear Unit) (в недавних моделях также часто используются GELU или SwiGLU).&lt;/p>
&lt;p>Роль этой сети чрезвычайно важна. В то время как механизм Attention изучает «отношения между словами (пространственные и последовательные отношения)», FFN отвечает за «нелинейное преобразование признаков каждого вектора слова самого по себе».
Обычно вес первого слоя $W_1$ временно значительно увеличивает размерность (например, в 4 раза с $d_{model}=512$ до $d_{ff}=2048$), выполняет сложные вычисления в пространстве признаков, а затем вес второго слоя $W_2$ возвращает её к исходной размерности. Благодаря этому «расширению и сжатию размерности» выразительная сила модели резко возрастает.&lt;/p>
&lt;hr>
&lt;h1 id="7-остаточное-соединение-residual-connection-и-нормализация-слоя-layer-normalization">7. Остаточное соединение (Residual Connection) и Нормализация слоя (Layer Normalization)
&lt;/h1>&lt;p>В глубоком обучении по мере углубления слоев сети возникает проблема исчезновения или взрыва градиентов во время обучения, из-за чего обучение проходит неудачно. Для предотвращения этого вокруг каждого подслоя (Attention и FFN) в Transformer размещены &lt;strong>Остаточное соединение (Residual Connection)&lt;/strong> и &lt;strong>Нормализация слоя (Layer Normalization)&lt;/strong>.&lt;/p>
&lt;p>В виде формулы вывод подслоя обрабатывается следующим образом:&lt;/p>
$$ \text{Output} = \text{LayerNorm}(x + \text{Sublayer}(x)) $$
&lt;h2 id="71-остаточное-соединение-x--textsublayerx">7.1 Остаточное соединение ($x + \text{Sublayer}(x)$)
&lt;/h2>&lt;p>Вход $x$ напрямую прибавляется к выводу подслоя. Благодаря этому при обратном распространении ошибки градиенты напрямую передаются в более мелкие слои по короткому пути, что стабилизирует обучение даже при глубокой структуре слоев.&lt;/p>
&lt;h2 id="72-математика-нормализации-слоя-layer-normalization">7.2 Математика нормализации слоя (Layer Normalization)
&lt;/h2>&lt;p>Layer Normalization — это технология, которая вычисляет среднее и дисперсию по направлению размерности признаков и нормализует данные. Для входа с размером батча $B$, длиной последовательности $N$ и размерностью $d_{model}$, нормализация выполняется для одного вектора слова $x \in \mathbb{R}^{d_{model}}$.&lt;/p>
&lt;p>Вычисляются среднее $\mu$ и дисперсия $\sigma^2$:
&lt;/p>
$$ \mu = \frac{1}{d_{model}} \sum_{i=1}^{d_{model}} x_i $$
$$ \sigma^2 = \frac{1}{d_{model}} \sum_{i=1}^{d_{model}} (x_i - \mu)^2 $$
&lt;p>Затем получается нормализованный вывод $\hat{x}$:
&lt;/p>
$$ \text{LN}(x) = \frac{x - \mu}{\sqrt{\sigma^2 + \epsilon}} \odot \gamma + \beta $$
&lt;p>
(где $\epsilon$ — малая константа для предотвращения деления на ноль, а $\gamma, \beta$ — обучаемые параметры масштаба и сдвига)&lt;/p>
&lt;p>Причина выбора нормализации по слоям (Layer Normalization) вместо нормализации по батчам (Batch Normalization) заключается в том, что при обработке данных последовательностей переменной длины, таких как текст, статистические показатели между батчами становятся нестабильными. Благодаря Layer Normalization, Transformer может обучаться стабильно независимо от размера батча.&lt;/p>
&lt;hr>
&lt;h1 id="8-специфическая-структура-декодера-masked-attention-и-cross-attention">8. Специфическая структура декодера: Masked Attention и Cross-Attention
&lt;/h1>&lt;p>Описанная до сих пор структура принадлежит кодировщику. В блоке декодера, генерирующем текст, структура немного отличается.&lt;/p>
&lt;h2 id="81-masked-multi-head-attention">8.1 Masked Multi-Head Attention
&lt;/h2>&lt;p>Роль декодера заключается в «предсказании следующего слова на основе предыдущих слов». Следовательно, во время обучения недопустимо заглядывать в «будущие слова», иначе это будет похоже на списывание. Математическая операция, предотвращающая это, называется &lt;strong>Masking (Маскирование)&lt;/strong>.&lt;/p>
&lt;p>К матрице оценок $Q K^T$ прибавляется матрица маски $M$, в которой верхнетреугольная часть (соответствующая будущей информации) заполнена очень маленькими значениями, близкими к $-\infty$.&lt;/p>
$$ M_{ij} = \begin{cases} 0 &amp; (i \le j) \\ -\infty &amp; (i > j) \end{cases} $$
$$ \text{Masked Attention}(Q, K, V) = \text{softmax}\left(\frac{Q K^T + M}{\sqrt{d_k}}\right) V $$
&lt;p>При вычислении функции Softmax $\exp(-\infty) = 0$, поэтому веса внимания (Attention Weight) для будущих слов становятся полностью равными $0$. Это обеспечивает авторегрессионную генерацию с сохранением причинно-следственной связи (Causality).&lt;/p>
&lt;h2 id="82-encoder-decoder-cross-attention">8.2 Encoder-Decoder Cross-Attention
&lt;/h2>&lt;p>Второй подслой декодера — это &lt;strong>Cross-Attention&lt;/strong>, который обращается к выводу кодировщика.
Здесь $Q$ генерируется из предыду слоя декодера, а $K$ и $V$ генерируются из вывода последнего слоя кодировщика.&lt;/p>
$$ Q_{decoder} = X_{dec} W^Q $$
$$ K_{encoder} = X_{enc} W^K $$
$$ V_{encoder} = X_{enc} W^V $$
&lt;p>С помощью этого вычисления модель может изучать в задачах, таких как перевод, «с какой частью исходного предложения на иностранном языке сильнее всего связано переводимое в данный момент слово».&lt;/p>
&lt;hr>
&lt;h1 id="9-вычислительная-сложность-и-математика-оптимизации-в-современную-эпоху">9. Вычислительная сложность и математика оптимизации в современную эпоху
&lt;/h1>&lt;p>Transformer — великолепная модель, но из-за ее математической структуры у нее есть свои «слабые стороны».
Обратите внимание на вычислительную сложность Self-Attention. При вычислении матрицы оценок $Q K^T$ матрица размера $(N \times d_k)$ умножается на матрицу размера $(d_k \times N)$, поэтому вычислительная сложность составляет &lt;strong>$O(N^2 \cdot d_{model})$&lt;/strong>.&lt;/p>
&lt;p>Другими словами, &lt;strong>вычислительная сложность и использование памяти растут квадратично относительно длины последовательности $N$&lt;/strong>.
Это не проблема для коротких текстов, но если попытаться ввести в LLM длинный контекст, например целую книгу, $N$ достигнет десятков или сотен тысяч, и память GPU мгновенно исчерпается при традиционных вычислениях Attention.&lt;/p>
&lt;p>Чтобы разорвать это проклятие $O(N^2)$, в последние годы были предложены различные оптимизации с точки зрения математики и аппаратного обеспечения.
Типичным примером является &lt;strong>FlashAttention&lt;/strong>. FlashAttention — это алгоритм, который делит вычисления Attention на плитки (Tiling), чтобы минимизировать передачу данных (доступ к памяти) между иерархиями памяти GPU (SRAM и HBM). Хотя математически он выдает точно такой же результат, как стандартный Attention (Exact Attention), оптимизация на аппаратном уровне обеспечивает резкое ускорение и сокращение используемой памяти, что сделало возможным создание моделей с длинным контекстом, таких как GPT-4.&lt;/p>
&lt;p>Кроме того, активно ведутся исследования таких подходов, как Sparse Attention и Linear Attention, которые аппроксимируют вычислительную сложность до $O(N \log N)$ или $O(N)$.&lt;/p>
&lt;hr>
&lt;h1 id="10-пример-реализации-псевдокод-в-стиле-pytorch">10. Пример реализации (псевдокод в стиле PyTorch)
&lt;/h1>&lt;p>Если перевести описанную математическую структуру в реальный программный код (Python / PyTorch), то окажется, что она описывается удивительно просто. Вот псевдокод ядра Self-Attention.&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;span class="lnt">25
&lt;/span>&lt;span class="lnt">26
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">torch&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">torch.nn.functional&lt;/span> &lt;span class="k">as&lt;/span> &lt;span class="nn">F&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">math&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">scaled_dot_product_attention&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">q&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">k&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">v&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">mask&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="kc">None&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># форма q, k, v: [batch_size, num_heads, seq_length, d_k]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">d_k&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">q&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">size&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># 1. Вычисление оценки скалярным произведением: Q * K^T&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># Транспонируем последние два измерения для умножения матриц&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">scores&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">torch&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">matmul&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">q&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">k&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">transpose&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="mi">2&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="o">-&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># 2. Масштабирование&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">scores&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">scores&lt;/span> &lt;span class="o">/&lt;/span> &lt;span class="n">math&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">sqrt&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">d_k&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># 3. Маскирование (в случае Masked Attention)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">mask&lt;/span> &lt;span class="ow">is&lt;/span> &lt;span class="ow">not&lt;/span> &lt;span class="kc">None&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">scores&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">scores&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">masked_fill&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">mask&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="o">-&lt;/span>&lt;span class="mf">1e9&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># 4. Преобразование в вероятности с помощью Softmax&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">attention_weights&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">F&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">softmax&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">scores&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">dim&lt;/span>&lt;span class="o">=-&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># 5. Умножение на матрицу Value&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">output&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">torch&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">matmul&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">attention_weights&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">v&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">output&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">attention_weights&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>Как можно видеть, формула $Q K^T / \sqrt{d_k}$ интуитивно реализована как &lt;code>torch.matmul(q, k.transpose(-2, -1)) / math.sqrt(d_k)&lt;/code>. Тот факт, что математическая теория реализуется в несколько строк кода с помощью продвинутых библиотек оптимизации, является очень интересным аспектом глубокого обучения.&lt;/p>
&lt;hr>
&lt;h1 id="заключение-форма-интеллекта-видимая-через-формулы">Заключение: форма «интеллекта», видимая через формулы
&lt;/h1>&lt;p>В этой статье мы раскрыли математическую структуру, лежащую в глубине модели Transformer.&lt;/p>
&lt;p>Embedding, отображающий слова в многомерное векторное пространство, Positional Encoding, выражающее информацию о позиции через сложение тригонометрических волн, и механизм Self-Attention, который представляет собой вычисление скалярного произведения матриц, рожденное из аналогии с информационным поиском. Каждый из этих компонентов — не более чем наложение базовой математики: линейной алгебры, дифференциального и интегрального исчисления, а также теории вероятностей и статистики.&lt;/p>
&lt;p>Однако, когда эти простые матричные операции накладываются друг на друга во множество слоев и изучают закономерности из огромных наборов данных через миллиарды и сотни миллиардов параметров, возникает «форма интеллекта», которая кажется способной понимать наши «слова», выполнять логические рассуждения и порой генерировать творческие идеи.&lt;/p>
&lt;p>Как и предполагает провокационное название «Attention Is All You Need» (Внимание - это все, что вам нужно), красота этой архитектуры, отбросившей сложную рекуррентную и сверточную обработку и специализирующейся исключительно на вычислении «внимания (степени связи)», заключается именно в ее математической простоте.&lt;/p>
&lt;p>Возможно, в будущем появятся новые архитектуры, превосходящие Transformer (например, Mamba, являющаяся State Space Model), но математическая база «понимания контекста с помощью Attention», созданная Transformer, навсегда останется в истории ИИ.&lt;/p>
&lt;p>Если у вас будет возможность использовать LLM, такие как ChatGPT или Claude в будущем, представьте себе, как в фоновом режиме каждую секунду вычисляются триллионы матричных умножений $Q K^T$, а функция Softmax выдает вероятности. Разрешение вашего понимания технологий повысится, и мир ИИ покажется вам еще более интересным.&lt;/p>
&lt;h3 id="литература">Литература
&lt;/h3>&lt;ul>
&lt;li>Vaswani, A., et al. (2017). &amp;ldquo;Attention Is All You Need.&amp;rdquo; &lt;em>Advances in Neural Information Processing Systems&lt;/em>.&lt;/li>
&lt;li>Alammar, J. (2018). &amp;ldquo;The Illustrated Transformer.&amp;rdquo;&lt;/li>
&lt;/ul>
&lt;hr>
&lt;p>&lt;em>Эта статья была написана как руководство для тех, кто изучает математические основы обработки естественного языка и ИИ. Если у вас есть вопросы или вы хотите что-то обсудить, пожалуйста, дайте знать в комментариях!&lt;/em>&lt;/p></description></item><item><title>Объяснение работы технологии квантования llama.cpp (GGUF)</title><link>http://kenji.blog/ru/p/llama-cpp-quantization-gguf/</link><pubDate>Fri, 11 Sep 2026 00:00:00 +0900</pubDate><guid>http://kenji.blog/ru/p/llama-cpp-quantization-gguf/</guid><description>&lt;img src="http://kenji.blog/p/llama-cpp-quantization-gguf/img/eyecatch.jpg" alt="Featured image of post Объяснение работы технологии квантования llama.cpp (GGUF)" />&lt;h2 id="1-введение-почему-llm-нужно-квантование">1. Введение: Почему LLM нужно квантование?
&lt;/h2>&lt;p>Эволюция больших языковых моделей (LLM: Large Language Models) в последние годы поразительна, но за ней скрываются серьезные проблемы: &amp;ldquo;истощение вычислительных ресурсов&amp;rdquo; и &amp;ldquo;узкое место пропускной способности памяти&amp;rdquo;. Например, если загрузить модель с 70 миллиардами параметров (70B), такую как Llama 3, в память с использованием стандартных 16-битных чисел с плавающей запятой (FP16), только параметры займут около 140 ГБ VRAM/RAM. Если к этому добавить контекст во время логического вывода (KV кэш), модель не будет работать без кластеризации нескольких высококлассных GPU для дата-центров (NVIDIA A100 80GB или H100 80GB).&lt;/p>
&lt;p>Спасителем, появившимся для запуска LLM индивидуальными разработчиками и на периферийных устройствах (MacBook или обычные игровые ПК), стал &lt;strong>llama.cpp&lt;/strong> и лежащая в его основе &lt;strong>технология квантования (Quantization)&lt;/strong>. В частности, формат файлов &lt;strong>GGUF (GPT-Generated Unified Format)&lt;/strong> и продвинутый алгоритм блочного квантования, известный как &lt;strong>k-quants&lt;/strong>, представляют собой революционный метод сжатия размера модели в несколько раз, максимально ограничивая при этом снижение точности модели (Perplexity).&lt;/p>
&lt;p>В этой статье мы подробно объясним квантование в llama.cpp, начиная с математических основ, различий с форматом GGML, детальной структуры формата GGUF и заканчивая внутренними механизмами k-quants.&lt;/p>
&lt;hr>
&lt;h2 id="2-математические-основы-квантования-quantization">2. Математические основы квантования (Quantization)
&lt;/h2>&lt;p>В контексте LLM квантование относится к операции отображения непрерывных значений (или высокоточных чисел с плавающей запятой) в дискретные значения с меньшим количеством бит (INT8, INT4, INT3 и т.д.).&lt;/p>
&lt;h3 id="21-базовые-формулы-линейного-квантования">2.1. Базовые формулы линейного квантования
&lt;/h3>&lt;p>Самым простым подходом является линейное квантование (Min-Max квантование). Пусть $W$ - исходный тензор весов с высокой точностью, а $W_q$ - квантованный целочисленный тензор.&lt;/p>
$$ W_q = \text{round}\left( \frac{W}{S} \right) + Z $$
&lt;p>Где,&lt;/p>
&lt;ul>
&lt;li>$S$ - это &lt;strong>масштабный коэффициент (Scale Factor)&lt;/strong>, определяющий размер шага (разрешение) квантования.&lt;/li>
&lt;li>$Z$ - это &lt;strong>нулевая точка (Zero-point)&lt;/strong>, значение смещения для сдвига, определяющее, какому целому значению после квантования соответствует вещественный $0.0$.&lt;/li>
&lt;li>$\text{round}(\cdot)$ - функция округления до ближайшего целого.&lt;/li>
&lt;/ul>
&lt;p>При обратном квантовании (Dequantization) во время логического вывода восстанавливаются приблизительные вещественные веса $\tilde{W}$.&lt;/p>
$$ \tilde{W} = S \times (W_q - Z) $$
&lt;h3 id="22-симметричное-и-асимметричное-квантование">2.2. Симметричное и асимметричное квантование
&lt;/h3>&lt;p>В зависимости от обработки нулевой точки $Z$, методы делятся на два основных типа.&lt;/p>
&lt;ol>
&lt;li>
&lt;p>&lt;strong>Асимметричное квантование (Asymmetric Quantization)&lt;/strong>
Отображение использует минимальное значение данных $W_{\min}$ и максимальное значение $W_{\max}$.
&lt;/p>
$$ S = \frac{W_{\max} - W_{\min}}{2^b - 1}, \quad Z = \text{round}\left(-\frac{W_{\min}}{S}\right) $$
&lt;p>
Где $b$ - количество бит квантования (например, для 4 бит $2^4-1 = 15$). Поскольку необходимо хранить $Z$, незначительно увеличиваются накладные расходы на вычисления и память.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>Симметричное квантование (Symmetric Quantization)&lt;/strong>
Отображение центрируется вокруг нуля с использованием максимального абсолютного значения данных ($Z=0$).
&lt;/p>
$$ S = \frac{\max(|W_{\max}|, |W_{\min}|)}{2^{b-1} - 1}, \quad Z = 0 $$
&lt;p>
Раннее квантование llama.cpp (например, унаследованное Q4_0) использовало симметричное квантование. Поскольку член $Z$ отсутствует, преимуществом является значительное ускорение вычислений скалярного произведения с помощью инструкций SIMD.&lt;/p>
&lt;/li>
&lt;/ol>
&lt;hr>
&lt;h2 id="3-эволюция-от-ggml-к-gguf-и-структура-файла">3. Эволюция от GGML к GGUF и структура файла
&lt;/h2>&lt;p>Говоря о llama.cpp, нельзя не упомянуть &lt;strong>GGML&lt;/strong>, библиотеку тензорных вычислений, написанную на C++, и производный от нее формат файлов &lt;strong>GGUF&lt;/strong>.&lt;/p>
&lt;h3 id="31-проблемы-ggml">3.1. Проблемы GGML
&lt;/h3>&lt;p>Ранний llama.cpp использовал формат &lt;code>ggml&lt;/code> (и его варианты, такие как &lt;code>ggjt&lt;/code>). Однако у них были следующие проблемы:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Отсутствие расширяемости:&lt;/strong> Магические числа и гиперпараметры были жестко закодированы (hardcoded) с фиксированной длиной и порядком, что приводило к разрушительным изменениям каждый раз при добавлении новой архитектуры модели (например, Llama, Falcon, Mixtral и т.д.) или нового токенизатора.&lt;/li>
&lt;li>&lt;strong>Потеря обратной совместимости:&lt;/strong> Формат часто обновлялся, из-за чего старые файлы моделей часто переставали читаться в последних версиях llama.cpp.&lt;/li>
&lt;/ul>
&lt;h3 id="32-рождение-формата-gguf">3.2. Рождение формата GGUF
&lt;/h3>&lt;p>&lt;strong>GGUF&lt;/strong>, представленный в августе 2023 года, является универсальным форматом, разработанным для решения этих проблем. Его главная особенность - принятие &lt;strong>структуры метаданных на основе пар ключ-значение (Key-Value)&lt;/strong>.&lt;/p>
&lt;p>Следующая диаграмма Mermaid абстрактно показывает файловую структуру GGUF.&lt;/p>
&lt;div class="mermaid">graph TD
A["Файл GGUF"] --> B["Заголовок (Magic, Version)"]
A --> C["Метаданные (Пары Ключ-Значение)"]
A --> D["Информация о тензоре (Name, Shape, Offset)"]
A --> E["Данные тензора (Binary payload)"]
C --> C1["general.architecture: llama"]
C --> C2["llama.context_length: 4096"]
C --> C3["tokenizer.ggml.tokens: [...]"]
E --> E1["Веса Слоя 0"]
E --> E2["Веса Слоя 1"]
E --> E3["..."]&lt;/div>
&lt;p>&lt;strong>Основные преимущества GGUF:&lt;/strong>&lt;/p>
&lt;ol>
&lt;li>&lt;strong>Гибкость:&lt;/strong> Все настройки, такие как гиперпараметры модели, конфигурация RoPE (Rotary Positional Embedding), данные словаря токенизатора и т.д., хранятся в виде именованных пар Key-Value. Неизвестные ключи игнорируются, что упрощает добавление новых функций.&lt;/li>
&lt;li>&lt;strong>Независимость от порядка байтов (Endianness):&lt;/strong> GGUF по умолчанию использует порядок от младшего к старшему (Little-endian), но имеет явный флаг, что делает его безопасно переносимым между различными архитектурами.&lt;/li>
&lt;li>&lt;strong>Оптимизация для mmap (отображение в память):&lt;/strong> Данные тензоров выровнены (с отступами) по определенным границам, что позволяет отображать их напрямую с диска в адресное пространство памяти с помощью системного вызова ОС &lt;code>mmap()&lt;/code>. Благодаря этому время инициализации при загрузке модели практически равно нулю.&lt;/li>
&lt;/ol>
&lt;hr>
&lt;h2 id="4-глубины-k-quants-продвинутое-блочное-квантование">4. Глубины k-quants: Продвинутое блочное квантование
&lt;/h2>&lt;p>Истинная ценность формата GGUF заключается в механизме &lt;strong>k-quants (K-quantization)&lt;/strong>, отвечающем за сжатие весов модели.&lt;/p>
&lt;p>Обычно веса нейронной сети близки к нормальному распределению, если рассматривать слой целиком, но локально существуют выбросы (Outliers). Если квантовать веса всего слоя с помощью единого масштабного коэффициента $S$, масштаб будет искажен выбросами, и информация о малых весах будет полностью потеряна.&lt;/p>
&lt;p>Чтобы предотвратить это, llama.cpp выполняет &lt;strong>блочное квантование (Block-wise Quantization)&lt;/strong>. Тензор весов разбивается на небольшие блоки (например, по 32 или 256 элементов), и каждому блоку присваивается свой собственный масштабный коэффициент (и нулевая точка).&lt;/p>
&lt;h3 id="41-ограничения-унаследованного-квантования-q4_0-q4_1">4.1. Ограничения унаследованного квантования (Q4_0, Q4_1)
&lt;/h3>&lt;p>В раннем &lt;code>Q4_0&lt;/code> блок состоял из 32 весов FP16 и имел один общий масштабный коэффициент FP16.&lt;/p>
&lt;ul>
&lt;li>Размер блока: 32&lt;/li>
&lt;li>Память: 1 масштаб (16 бит) + 32 4-битных веса (128 бит) = 144 бита&lt;/li>
&lt;li>Эффективное количество бит на элемент (bpw: bits per weight): $144 / 32 = 4.5$ bpw&lt;/li>
&lt;/ul>
&lt;p>Хотя это уже было довольно эффективно, проявились пределы точности и степени сжатия. Тогда и появился &lt;strong>k-quants&lt;/strong>, имеющий более сложную и продуманную иерархическую структуру.&lt;/p>
&lt;h3 id="42-иерархическая-структура-суперблоков-и-субблоков-пример-q4_k_m">4.2. Иерархическая структура суперблоков и субблоков (Пример Q4_K_M)
&lt;/h3>&lt;p>k-quants имеет иерархическую структуру, состоящую из больших &amp;ldquo;Суперблоков (Super-block)&amp;rdquo; и содержащихся в них меньших &amp;ldquo;Субблоков (Sub-block)&amp;rdquo;. Это позволяет также квантовать сами метаданные (значения масштаба и т.д.), предельно снижая bpw при сохранении точности.&lt;/p>
&lt;p>Давайте рассмотрим структуру &lt;strong>Q4_K_M&lt;/strong>, которая является наиболее популярной конфигурацией. Q4_K_M использует суперблок из 256 элементов.&lt;/p>
&lt;div class="mermaid">graph TD
A["Суперблок (256 весов)"] --> B["Метаданные масштаба (FP16/INT8)"]
A --> C["Субблок 0 (32 веса, 4-бит)"]
A --> D["Субблок 1 (32 веса, 4-бит)"]
A --> E["..."]
A --> F["Субблок 7 (32 веса, 4-бит)"]
B --> B1["Супер-масштаб (FP16)"]
B --> B2["Суб-масштабы (8 x 6-бит)"]
B --> B3["Суб-минимумы (8 x 6-бит)"]&lt;/div>
&lt;p>Фактическая структура в C++ (GGML) концептуально определяется следующим образом:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;span class="lnt">7
&lt;/span>&lt;span class="lnt">8
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-cpp" data-lang="cpp">&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// Концептуальная структура block_q4_K в llama.cpp
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="cp">#define QK_K 256
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cp">&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">struct&lt;/span> &lt;span class="nc">block_q4_K&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kt">uint8_t&lt;/span> &lt;span class="n">d&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="mi">2&lt;/span>&lt;span class="p">];&lt;/span> &lt;span class="c1">// Супер-масштаб для всего суперблока (например, FP16 x 2)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="kt">uint8_t&lt;/span> &lt;span class="n">scales&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="mi">12&lt;/span>&lt;span class="p">];&lt;/span> &lt;span class="c1">// Упакованные данные: 6-битные масштабы и 6-битные минимумы (нулевые точки) для 8 субблоков (по 32 элемента)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="kt">uint8_t&lt;/span> &lt;span class="n">qs&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">QK_K&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="mi">2&lt;/span>&lt;span class="p">];&lt;/span> &lt;span class="c1">// Данные весов, квантованные до 4 бит (256 элементов / 2 = 128 байт)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>&lt;strong>Математический процесс деквантования (Dequantization):&lt;/strong>&lt;/p>
&lt;p>Приблизительное вещественное значение $\tilde{W}_{i, j}$ элемента $j$ ($0 \le j &lt; 32$) внутри субблока $i$ ($0 \le i &lt; 8$) вычисляется следующим образом:&lt;/p>
$$ \tilde{W}_{i, j} = S_{\text{super}} \times s_i \times (w_{i, j} - m_i) $$
&lt;ul>
&lt;li>$S_{\text{super}}$: Масштаб с плавающей запятой для всего суперблока&lt;/li>
&lt;li>$s_i$: 6-битный масштаб, квантованный для субблока $i$&lt;/li>
&lt;li>$m_i$: 6-битный минимум (нулевая точка), квантованный для субблока $i$&lt;/li>
&lt;li>$w_{i, j}$: 4-битный квантованный вес ($0 \dots 15$)&lt;/li>
&lt;/ul>
&lt;p>Благодаря этой иерархической структуре, сохраняя способность адаптироваться к выбросам, резко сокращается объем памяти, занимаемый самими масштабными коэффициентами. Q4_K_M достигает в целом около &lt;strong>4.8 bpw&lt;/strong>.&lt;/p>
&lt;h3 id="43-разнообразие-опций-k-quants">4.3. Разнообразие опций k-quants
&lt;/h3>&lt;p>llama.cpp предлагает множество вариаций в зависимости от цели. Суффиксы (S, M, L) после &amp;ldquo;K&amp;rdquo; обозначают размер (маленький, средний, большой).&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th style="text-align:left">Формат&lt;/th>
&lt;th style="text-align:center">BPW (Bits per Weight)&lt;/th>
&lt;th style="text-align:left">Обзор и особенности&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td style="text-align:left">&lt;strong>Q2_K&lt;/strong>&lt;/td>
&lt;td style="text-align:center">2.5～3.3&lt;/td>
&lt;td style="text-align:left">Экстремальное сжатие. Значительное падение точности, но подходит для сред с крайне малым объемом VRAM.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align:left">&lt;strong>Q3_K_M&lt;/strong>&lt;/td>
&lt;td style="text-align:center">3.3&lt;/td>
&lt;td style="text-align:left">Стандарт для 3-битного квантования. Уступает Q4, но часто остается в пределах допустимого.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align:left">&lt;strong>Q4_K_M&lt;/strong>&lt;/td>
&lt;td style="text-align:center">4.8&lt;/td>
&lt;td style="text-align:left">&lt;strong>Рекомендуемая золотая середина&lt;/strong>. Баланс между уменьшением размера модели вдвое и сохранением точности.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align:left">&lt;strong>Q5_K_M&lt;/strong>&lt;/td>
&lt;td style="text-align:center">5.5&lt;/td>
&lt;td style="text-align:left">Когда требуется более высокая точность. Промежуточное положение между Q4 и FP16.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align:left">&lt;strong>Q6_K&lt;/strong>&lt;/td>
&lt;td style="text-align:center">6.6&lt;/td>
&lt;td style="text-align:left">Сохраняет Perplexity почти на уровне FP16, но размер файла больше.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align:left">&lt;strong>Q8_0&lt;/strong>&lt;/td>
&lt;td style="text-align:center">8.5&lt;/td>
&lt;td style="text-align:left">Эквивалент INT8. Используется в основном для промежуточных тензоров при логическом выводе вычислений и только в финальных слоях.&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>※ Фактический BPW усредняется по всей модели, поскольку выполняется смешанное квантование (Mixed Quantization) в зависимости от тензоров модели (например, проекции Q/K/V в Attention или веса в FFN). Внутри производится оптимизация, при которой важные тензоры квантуются в Q6, а остальные - в Q4.&lt;/p>
&lt;hr>
&lt;h2 id="5-оптимизация-производительности-при-логическом-выводе-архитектуры-simd-и-cuda">5. Оптимизация производительности при логическом выводе: архитектуры SIMD и CUDA
&lt;/h2>&lt;p>Простая загрузка модели GGUF в память не делает логический вывод быстрым. Большая часть логического вывода в LLM - это «умножение матрицы на вектор (Matrix-Vector Multiplication, или GEMV, или Matrix-Matrix, GEMM)». Ключ к успеху - это то, как ускорить операцию умножения и сложения между квантованными весами и активациями (входными данными), хранящимися в FP16 (или FP32).&lt;/p>
&lt;h3 id="51-использование-инструкций-simd-в-процессорных-средах">5.1. Использование инструкций SIMD в процессорных средах
&lt;/h3>&lt;p>Невероятная скорость, с которой llama.cpp работает при логическом выводе на CPU, обусловлена оптимизацией &lt;strong>SIMD (Single Instruction, Multiple Data)&lt;/strong> на уровне ассемблера.
Например, он в полной мере использует наборы инструкций &lt;strong>AVX2&lt;/strong> и &lt;strong>AVX-512&lt;/strong> на процессорах Intel/AMD, и &lt;strong>ARM NEON&lt;/strong> на Apple Silicon.&lt;/p>
&lt;p>Во время логического вывода не тратится время на возвращение $W_q$ в формат FP32 (деквантование) перед умножением.
Сторона активации также динамически квантуется блоками (Dynamic Quantization, обычно в INT8), и целочисленные операции &lt;strong>INT8 $\times$ INT4&lt;/strong> вычисляются сразу с использованием специальных инструкций скалярного произведения SIMD (например, &lt;code>vdpaddd&lt;/code> или &lt;code>_mm256_madd_epi16&lt;/code>). В конечном аккумуляторе происходит преобразование обратно в FP32 и умножение на масштабный коэффициент, что обеспечивает поразительную пропускную способность.&lt;/p>
&lt;h3 id="52-разгрузка-в-средах-gpu-cublas--cuda">5.2. Разгрузка в средах GPU (cuBLAS / CUDA)
&lt;/h3>&lt;p>Современный llama.cpp имеет надежную поддержку не только CPU, но и GPU NVIDIA (CUBLAS / CUDA).
Можно выгрузить (offload) некоторые или все слои файла GGUF в VRAM (опция &lt;code>--n-gpu-layers&lt;/code>).&lt;/p>
&lt;div class="mermaid">sequenceDiagram
participant User
participant CPU_RAM as CPU и RAM (mmap)
participant VRAM as GPU VRAM
participant Compute as Тензорные ядра
User->>CPU_RAM: Загрузка GGUF (mmap)
CPU_RAM->>VRAM: Выгрузка слоев (напр. 30/32 слоев)
Note over CPU_RAM, VRAM: Данные остаются квантованными в VRAM
User->>Compute: Прямой проход (Входные токены)
Compute->>VRAM: Извлечение квантованных весов
Compute->>Compute: Динамическое деквантование в FP16 в SRAM (on-the-fly)
Compute->>Compute: Умножение матриц (cuBLAS / Пользовательские ядра)
Compute->>User: Вывод логитов (Logits)&lt;/div>
&lt;p>При вычислениях на GPU пропускная способность памяти (Memory Bandwidth) VRAM становится самым большим узким местом. Поскольку веса сжаты с помощью k-quants, объем данных, передаваемых из VRAM в вычислительные блоки GPU (SM: Streaming Multiprocessor или Tensor Cores), сокращается в 3-4 раза. В тот момент, когда веса достигают вычислительного блока, они динамически деквантуются (разворачиваются) в FP16, и с помощью Tensor Cores выполняется сверхбыстрое умножение матриц.
Другими словами, можно сказать, что квантование выполняется &lt;strong>не для &amp;ldquo;уменьшения объема вычислений&amp;rdquo;, а для &amp;ldquo;уменьшения объема передаваемых данных из памяти&amp;rdquo;&lt;/strong>.&lt;/p>
&lt;hr>
&lt;h2 id="6-конкретные-примеры-компромисса-между-использованием-памяти-и-производительностью">6. Конкретные примеры компромисса между использованием памяти и производительностью
&lt;/h2>&lt;p>Здесь, на примере модели Llama 3 8B, давайте посмотрим на требования к спецификациям для каждого уровня квантования GGUF. (Цифры являются приблизительными ориентирами.)&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th style="text-align:left">Модель / Квантование&lt;/th>
&lt;th style="text-align:left">Размер файла&lt;/th>
&lt;th style="text-align:left">Требуемая VRAM/RAM&lt;/th>
&lt;th style="text-align:left">Скорость вывода (ориентир)&lt;/th>
&lt;th style="text-align:left">Снижение Perplexity&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td style="text-align:left">&lt;strong>Llama-3-8B (FP16)&lt;/strong>&lt;/td>
&lt;td style="text-align:left">Около 16 ГБ&lt;/td>
&lt;td style="text-align:left">от 18 ГБ&lt;/td>
&lt;td style="text-align:left">Базовая&lt;/td>
&lt;td style="text-align:left">Нет (Base)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align:left">&lt;strong>Llama-3-8B (Q8_0)&lt;/strong>&lt;/td>
&lt;td style="text-align:left">Около 8.5 ГБ&lt;/td>
&lt;td style="text-align:left">от 10 ГБ&lt;/td>
&lt;td style="text-align:left">Высокая&lt;/td>
&lt;td style="text-align:left">Почти нулевое&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align:left">&lt;strong>Llama-3-8B (Q6_K)&lt;/strong>&lt;/td>
&lt;td style="text-align:left">Около 6.6 ГБ&lt;/td>
&lt;td style="text-align:left">от 8 ГБ&lt;/td>
&lt;td style="text-align:left">Очень высокая&lt;/td>
&lt;td style="text-align:left">Минимальное&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align:left">&lt;strong>Llama-3-8B (Q4_K_M)&lt;/strong>&lt;/td>
&lt;td style="text-align:left">Около 4.9 ГБ&lt;/td>
&lt;td style="text-align:left">от 6.5 ГБ&lt;/td>
&lt;td style="text-align:left">Самая быстрая / Оптимальная&lt;/td>
&lt;td style="text-align:left">В допустимых пределах / Небольшое&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align:left">&lt;strong>Llama-3-8B (Q3_K_M)&lt;/strong>&lt;/td>
&lt;td style="text-align:left">Около 3.9 ГБ&lt;/td>
&lt;td style="text-align:left">от 5.5 ГБ&lt;/td>
&lt;td style="text-align:left">Самая быстрая&lt;/td>
&lt;td style="text-align:left">Немного заметное&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align:left">&lt;strong>Llama-3-8B (Q2_K)&lt;/strong>&lt;/td>
&lt;td style="text-align:left">Около 3.0 ГБ&lt;/td>
&lt;td style="text-align:left">от 4.5 ГБ&lt;/td>
&lt;td style="text-align:left">Высокая&lt;/td>
&lt;td style="text-align:left">Очевидное снижение&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>&lt;strong>Внимание (Влияние KV-кэша):&lt;/strong>
При логическом выводе LLM, когда длина контекста (количество токенов промпта) увеличивается, потребление памяти взрывообразно растет не только из-за весов модели, но и из-за &lt;strong>KV-кэша&lt;/strong>, который сохраняет прошлые состояния Attention.
Например, если длина контекста составляет 8192 токена, только KV-кэш будет потреблять несколько гигабайт. Поэтому при реальном использовании необходимо обеспечить запас (Headroom) в размере &lt;code>Размер файла модели + около 1.5 ГБ ~ 3 ГБ&lt;/code>. Причина, по которой рекомендуется Q4_K_M, заключается в том, что это идеальная линия, при которой даже с учетом резервирования этого KV-кэша модель безопасно работает на типичном графическом процессоре с 8 ГБ VRAM (например, RTX 3060 / 4060).&lt;/p>
&lt;p>В последних версиях llama.cpp также была добавлена &lt;strong>функция квантования самого KV-кэша в Q8_0 или Q4_0&lt;/strong>, и постоянно ведется работа по дальнейшему увеличению длины контекста.&lt;/p>
&lt;hr>
&lt;h2 id="7-заключение">7. Заключение
&lt;/h2>&lt;p>В этой статье мы глубоко погрузились во внутреннюю структуру формата GGUF и технологии квантования k-quants, которые являются сердцем llama.cpp.&lt;/p>
&lt;ol>
&lt;li>&lt;strong>Гибкость GGUF:&lt;/strong> Структура метаданных типа &amp;ldquo;ключ-значение&amp;rdquo; создала надежную экосистему, которая может идти в ногу с быстрой эволюцией LLM (появление новых архитектур моделей) без разрушительных изменений.&lt;/li>
&lt;li>&lt;strong>Экстремальное сжатие с помощью k-quants:&lt;/strong> Иерархическое управление масштабными коэффициентами суперблоков и субблоков позволило добиться удивительного сжатия - в среднем 4.8 бита на вес (Q4_K_M) - с сохранением информации о выбросах.&lt;/li>
&lt;li>&lt;strong>Устранение узкого места пропускной способности памяти:&lt;/strong> Продвинутые реализации ядер для SIMD и CUDA позволили выполнять вычисления с динамическим деквантованием (on-the-fly), что сократило объем передаваемых данных из VRAM и значительно повысило скорость логического вывода.&lt;/li>
&lt;/ol>
&lt;p>Технологическое мастерство llama.cpp, способствующее демократизации ИИ, выходит за рамки простого инструмента и, без преувеличения, является одной из вершин современной программной инженерии. Поняв алгоритм квантования и структуру формата GGUF, вы сможете более точно выбирать оптимальную модель для вашей среды и выполнять настройку производительности.&lt;/p>
&lt;h3 id="полезные-ссылки">Полезные ссылки
&lt;/h3>&lt;ul>
&lt;li>&lt;a class="link" href="https://github.com/ggerganov/llama.cpp" target="_blank" rel="noopener"
>llama.cpp GitHub Repository&lt;/a>&lt;/li>
&lt;li>&lt;a class="link" href="https://github.com/ggerganov/ggml/blob/master/docs/gguf.md" target="_blank" rel="noopener"
>GGUF Format Specification&lt;/a>&lt;/li>
&lt;li>&lt;a class="link" href="https://github.com/ggerganov/llama.cpp/pull/1684" target="_blank" rel="noopener"
>K-quants Implementation PR&lt;/a>&lt;/li>
&lt;/ul>
&lt;p>(Конец)&lt;/p></description></item></channel></rss>