Большие языковые модели (LLM, Large Language Model), как подсказывает их название, часто отличаются значительными размерами и слишком велики для того, чтобы нормально работать на обычных компьютерах. Масштабы этих моделей могут измеряться миллиардами параметров. Обычно для обеспечений достойной скорости их работы необходимы GPU с серьёзными объёмами видеопамяти (VRAM).

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

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

В этом материале более 50 рисунков, которые помогут вам приблизиться к интуитивному пониманию разных аспектов квантования!

Часть 1. «Проблема» LLM

Как уже говорилось, LLM, большие языковые модели, названы так из-за огромного количества содержащихся в них параметров. В наши дни речь обычно идёт о миллиардах параметров (это, в основном, веса — weight), хранение которых может быть достаточно дорогим удовольствием. В ходе работы модели (этот этап жизненного цикла моделей часто называют «инференсом» — от «inference») создаются значения активации (их нередко называют просто «активациями» — от «activations»), представляющие собой, если не вдаваться в подробности, результат умножения входных данных и весов модели. Для хранения активаций тоже может понадобиться довольно много места.

В результате перед нами встаёт задача эффективного представления миллиардов чисел. Нужно минимизировать объём памяти, необходимый для хранения каждого конкретного значения.

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

Как числовые значения представляются в компьютерах

Числовые значения часто хранят в формате числа с плавающей запятой (floating point number, float). Это — положительные или отрицательные числа с десятичным разделителем (decimal point).

Такие значения хранятся в виде «битов», или двоичных цифр. Стандарт IEEE-754 описывает то, как именно биты могут представлять числовые значения, играя одну из трёх ролей: бит знака (sign), поле порядка или показателя степени (exponent) и поле значащей части числа (significand) (ещё называемой дробной частью (fraction), или мантиссой (mantissa)).

Обладая сведениями об этих трёх составных частях двоичного представления числа, можно узнать о том, что это за число, вычислить его десятичное значение:

Чем больше битов используется для представления некоего значения — тем оно, обычно, точнее:

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

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

Интервал представимых чисел, которые можно закодировать в значениях некоего формата, называется его динамическим диапазоном (dynamic range), а расстояние между двумя соседними значениями называется точностью (precision).

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

Учитывайте, что на то, сколько памяти или видеопамяти понадобится во время практического использования модели, влияют и другие факторы — такие, как размер контекста (context size) и архитектура модели.

Представим модель с 70 миллиардами параметров. Большинство моделей, в их исходном виде, используют 32-битные числа типа float (их ещё называют числами одинарной точности, single-precision numbers; их так же называют full-precision numbers). В результате только для того, чтобы загрузить такую модель в память, понадобится 280 Гб памяти.

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

Итак: нужно уменьшить количество битов, представляющих числа, и при этом не ухудшить точность работы модели… Именно тут нам на помощь приходит квантование!

Часть 2. Введение в квантование

Цель квантования заключается в том, чтобы уменьшить точность параметров модели, превращая числа более высокой разрядности (например — 32-битные числа с плавающей запятой) в числа более низкой разрядности (например — в 8-битные целые числа).

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

Для того чтобы наглядно проиллюстрировать этот эффект, можно взять любое изображение и «перерисовать» его, используя всего 8 цветов (оригинал взят отсюда).

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

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

Распространённые типы данных

Для начала рассмотрим распространённые типы данных и поговорим о том, как подействует на модель использование именно их, а не 32-битных чисел с плавающей запятой (FP32).

FP16

Рассмотрим пример, где показан переход от 32-битных к 16-битным числам (к числам с половинной точностью, half-precision numbers, FP16) с плавающей запятой.

Обратит внимание на то, что диапазон чисел, которые помещаются в значения FP16, существенно меньше, чем в значения FP32.

BF16

Для того чтобы выйти на тот же диапазон значений, который соответствует исходному формату FP32, был создан формат bfloat16 (brain floating point), который представляет собой «урезанный FP32».

Формат BF16 использует то же количество битов, что и FP16, но с его помощью можно представлять числа, лежащие в более широком диапазоне. BF16 часто используется в моделях глубокого обучения.

INT8

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

Целочисленные вычисления, что зависит от аппаратного обеспечения, могут выполняться быстрее, чем вычисления с плавающей запятой. Но это не всегда так. Правда, вычисления, где применяются типы данных с меньшим количеством бит, обычно выполняются быстрее.

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

На практике нет нужды в отображении всего диапазона FP32 [-3.4e38, 3.4e38] на диапазон INT8. Достаточно найти способ отображения диапазона имеющихся данных (параметров модели) на INT8.

Обычные методы сжатия/отображения представлены симметричным и асимметричным квантованием, которые являются разновидностями линейного отображения.

Исследуем эти методы квантования, позволяющие перейти от чисел формата FP32 к числам INT8.

Симметричное квантование

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

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

Хорошим примером разновидности симметричного квантования является так называемое квантование с использованием максимальных абсолютных значений (absolute maximum (absmax) quantization).

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

Обратите внимание на то, что диапазон значений [-127, 127] — это симметричный диапазон. А [-128, 127] — это асимметричный диапазон. Применяемый диапазон значений INT8 зависит от метода квантования.

Так как это — линейное отображение, центрированное вокруг нуля, формула для его выполнения получается довольно-таки простой:

Сначала вычисляем коэффициент масштабирования (s), используя следующие данные:

  • b — это количество байтов, к которому мы хотим привести исходное значение в ходе квантования (8).

  • α — это самое большое абсолютное значение.

Затем используем s для квантования входных данных x:

Подставляя в формулы имеющиеся значения — получим следующее

Для восстановления исходных FP32-значений мы можем использовать уже вычисленный коэффициент масштабирования (s). Так мы проводим обратное квантование (деквантование, dequantize) ранее квантованных значений.

Вот что получается, когда сначала применяют квантование, а потом — деквантование для восстановления исходных значений:

Можно заметить, что некоторые числа, вроде 3,08 и 3,02, назначаются одному и тому же значению INT8, а именно — числу 36.

При обратном квантовании этих значений для возврата к FP32 они теряют некоторую долю точности и их больше нельзя отличить друг от друга.

Часто это называют ошибкой квантования (quantization error). Вычислить эту ошибку можно, найдя разницу между исходными и деквантованными значениями.

В целом можно сказать, что чем меньше количество битов — тем больше ошибка квантования, с которой можно столкнуться.

Асимметричное квантование

Асимметричное квантование, в отличие от симметричного, не отличается свойством симметричного расположения значений вокруг нуля. При его применении выполняется сопоставление минимального (β) и максимального (α) значений из диапазона float-чисел, с минимальным и максимальным значениями квантованного диапазона.

Метод, который мы исследуем, называется квантованием относительно нуля (zero-point quantization).

Заметили, что позиция нуля сдвинута? Потому это и называется «асимметричным квантованием». Минимальное и максимальное значения находятся на разном расстоянии от нуля в диапазоне [-7,59, 10,8].

Из-за того, что позиция нуля сдвинута, для выполнения линейного отображения необходимо вычислить нулевую точку диапазона INT8. Нам, как и ранее, необходимо найти коэффициент масштабирования (s), но теперь в соответствующей формуле используется разность между границами диапазона значений INT8 — [-128, 127].

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

Теперь, как и прежде, подставим в формулы реальные значения:

Для обратного квантования значений из INT8 в FP32 нам понадобится ранее вычисленный коэффициент масштабирования (s) и нулевая точка (z). 

Что касается остального — тут всё так же просто и понятно, как раньше:

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

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

Отображение диапазона и обрезка значений

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

Представим, что имеется следующий вектор:

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

Тут показаны результаты ранее рассмотренного метода квантования absmax. Так же ведёт себя и асимметричное квантование в том случае, если перед его проведением не прибегнуть к обрезке значений.

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

В нижеприведённом примере, если вручную задать динамический диапазон как [-5, 5], то все числа, находящиеся за его пределами, независимо от их значений, будут приведены к значениям -127 или 127:

Главный плюс обрезки выбросов заключается в значительном уменьшении ошибки квантования для тех значений, которые выбросами не являются. Но ошибка квантования значений-выбросов при этом возрастает.

Калибровка

В рассмотренном примере показан безыскусный метод, где диапазон [-5, 5] был выбран произвольным образом. Процесс выбора подобных диапазонов называется калибровкой (calibration). Его цель заключается в поиске такого диапазона, который включает в себя как можно больше значений, благодаря чему минимизируется ошибка квантования.

Калибровка выполняется по-разному для разных видов параметров моделей.

Веса (и смещения)

Веса (weights) и смещения (biases) LLM можно рассматривать как статические значения, так как они известны до запуска модели. Например, файл Llama 3 размером примерно в 20 Гб состоит, в основном, из весов и смещений модели.

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

  • Самостоятельный выбор процентиля входного диапазона.

  • Оптимизация среднеквадратичной ошибки (mean squared error, MSE) между исходными и квантованными весами.

  • Минимизации энтропии (KL-дивергенции) между исходными и квантованными значениями.

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

Значения активации

Данные, которые постоянно обновляются при работе LLM обычно называют значениями активации.

Обратите внимание на то, что эти значения называются значениями активации из-за того, что их часто «пропускают» через те или иные функции активации, вроде сигмоиды или ReLU.

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

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

Если не вдаваться в подробности, то существует два метода калибровки системы квантования, применяемой для работы с весами и значениями активации:

  • Post-Training Quantization (PTQ).

    • Квантование выполняется после обучения модели.

  • Quantization Aware Training (QAT).

    • Квантование выполняется в процессе обучения или дообучения модели.

Часть 3. Квантование, выполняемое после обучения модели

Один из самых популярных подходов к квантованию — это Post-Training Quantization (PTQ) — квантование, выполняемое после обучения модели. Речь идёт об обработке весов и значений активации.

Квантование весов выполняется с использованием симметричного или асимметричного подходов.

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

Для квантования значений активации применяют два основных подхода:

  • Динамическое квантование.

  • Статическое квантование.

Динамическое квантование

После того, как данные прошли через скрытый слой, мы собираем вычисленные значения активации:

Затем распределения значений активации используются для вычисления нулевой точки (z) и коэффициента масштабирования (s), которые необходимы для квантования.

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

Статическое квантование

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

Для нахождения этих значений используется калибровочный набор данных (calibration dataset). Он передаётся модели не с целью получения от неё, например, ответа на некий вопрос, а с целью сбора потенциальных распределений значений активации.

После сбора этих значений можно вычислить s и z для выполнения квантования во время реальной работы модели.

А вот когда модель занимается настоящей работой — значения s и z повторно не вычисляются. Они используются в глобальном масштабе и применяются для квантования всех значений активации.

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

Статическое же квантование, в отличие от динамического, не такое точное, но более быстрое, так как при его применении значения s и z уже известны.

Мир 4-битного квантования

Квантование с использованием значений, разрядность которых меньше 8 битов, зарекомендовало себя как сложная задача. Всё дело в том, что с пропаданием каждого бита растёт ошибка квантования. К счастью, существует несколько хитрых способов квантования, предусматривающих использование 6-ти, 4-х и даже 2-х битных значений (хотя уходить ниже 4 битов при использовании этих методов обычно не рекомендуется).

Мы исследуем два подобных метода, которые обычно всплывают на HuggingFace:

  • GPTQ (вся модель работает на GPU).

  • GGUF (можно переносить некоторые слои в оперативную память и обрабатывать их с помощью CPU).

GPTQ

GPTQ — это, вероятно, один из самых известных среди используемых на практике методов 4-битного квантования (Frantar, Elias, et al. «Gptq: Accurate post-training quantization for generative pre-trained transformers.» arXiv preprint arXiv:2210.17323 (2022)).

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

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

Проще говоря — она демонстрирует важность (обратную) каждого веса в слое: чем меньше значение, соответствующее весу, тем важнее этот вес.

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

Теперь квантуем и деквантуем веса первой строки матрицы весов:

Этот процесс позволяет вычислить ошибку квантования (q), которую можно взвесить, используя обратную матрицу Гессиана (h_1).

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

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

Например, если сделать это для второго веса, для .3 (x_2), то получится, что мы добавляем к нему ошибку квантования (q), умноженную на соответствующее значение из обратной матрицы Гессиана (h_2).

Можно сделать то же самое и для третьего веса в том же ряду:

Мы продолжаем выполнять этот процесс перераспределения взвешенной ошибки квантования до тех пор, пока не завершится квантование всех значений.

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

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

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

GGUF

GPTQ — это прекрасный метод квантования, который подходит для обработки полных LLM на GPU. Но у GPU не всегда имеется достаточно ресурсов для подобных расчётов. В таких ситуациях можно, вместо GPTQ, прибегнуть к методу GGUF. Он предусматривает перенос слоёв LLM в оперативную память и их обработку на CPU (подробности о GGUF можно найти здесь).

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

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

Сначала веса слоя разбивают на «суперблоки» («super» block), каждый из которых содержит набор «подблоков» («sub» block). Эти блоки используются для вычисления коэффициента масштабирования (s) и значения «альфа» (α). 

Для квантования «подблока» можно использовать рассмотренный ранее метод absmax. Напомним, что при его применении веса умножают на коэффициент масштабирования (s):

Коэффициент масштабирования вычисляется с применением информации из «подблоков», но его квантование выполняется с использованием информации из «суперблока», у которого имеется собственный коэффициент масштабирования:

Такое блочное квантование использует коэффициент масштабирования (s_super) из «суперблока» для квантования коэффициента масштабирования (s_sub) из «подблока».

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

В качестве примера рассмотрим несколько уровней квантования (2-битное, 4-битное и 6-битное):

Обратите внимание на то, что, в зависимости от типа квантования, необходимо дополнительное минимальное значение (m) для корректировки нулевой точки. Квантование этих значений выполняется так же, как и квантование коэффициента масштабирования (s).

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

Часть 4. Обучение моделей с учётом квантования

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

Именно тут уместно вспомнить об обучении моделей с учётом квантования (Quantization Aware Training, QAT). При таком подходе вместо того, чтобы квантовать модель после обучения (Post-Training Quantization, PTQ), стремятся к тому, чтобы находить соответствующие значения в процессе обучения моделей. Ниже показано, как модель «изучает» параметры квантования во время обратного прохода.

Применение QAT обычно даёт более точные результаты, чем применение PTQ, так как квантование уже учитывается в ходе обучения модели. Вот как это всё работает.

В процессе обучения модели выполняются так называемые «фиктивные» (fake) операции квантования. Это — когда сначала квантуют FP32-веса, например, в INT4, а потом деквантуют обратно в FP32:

Это позволяет модели учесть процесс квантования во время обучения, при вычислении функции потерь и при обновлении значений весов.

При применении QAT делается попытка исследования ландшафта функции потерь в поиске «широких» минимумов. Это позволяет уменьшить ошибки квантования, так как работа с «узкими» минимумами обычно приводит к увеличению ошибок квантования.

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

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

В результате, хотя применение PTQ приводит к меньшим потерям при использовании значений высокой точности (например — FP32), применение QAT приводит к меньшим потерям при использовании значений низкой точности (например — INT4). А это — именно то, что нам нужно.

Эпоха 1-битных LLM: BitNet

Переход к четырём битам, как мы уже видели, означает работу со значениями достаточно скромных размеров. А что если пойти ещё дальше?

А дальше нас ждёт BitNet. В этой модели для представления весов используются 1-битные значения. Каждый конкретный вес может равняться либо -1, либо 1.

Достигается это путём внедрения процесса квантования непосредственно в трансформенную архитектуру.

Напомним, что трансформеры — это основа большинства LLM. Применение трансформеров предусматривает выполнение вычислений, в которых используются линейные (linear) слои:

Эти линейные слои обычно представлены с использований чисел достаточно высокой точности, вроде FP16, и именно в этих слоях и размещено большинство весов модели.

В модели BitNet линейные слои заменяют на сущности, которые разработчики этой модели называют BitLinear-слоями:

BitLinear-слой работает так же, как и обычный линейный слой — он вычисляет выходные данные, перемножая веса на активации.

Но, в отличие от линейных слоёв, в слое BitLinear веса модели представлены с помощью 1-битных значений, а значения активации — с помощью INT8-чисел:

В слое BitLinear, как и в ходе работы QAT, проводятся «фиктивные» операции квантования, что позволяет проанализировать эффект, который оказывает на работу модели квантование весов и активаций:

В публикации о BitNet вместо α используется γ, но, так как в вышеприведённых примерах используется α, здесь мы продолжим эту традицию. Кроме того, обратите внимание на то, что β здесь — это не то же самое значение, которое мы использовали при квантовании относительно нуля. Здесь это — среднее абсолютное значение.

Поговорим о том, как именно работают BitLinear-слои.

Квантование весов

В ходе обучения модели веса хранятся с использованием INT8-чисел, а затем производится их квантование в 1-битные значения с использованием простой стратегии, реализуемой посредством знаковой функции (signum function).

Смысл этого в том, что распределение весов смещается так, чтобы его центром стал бы 0, а после этого всему, что левее нуля, назначется -1, а всему, что правее — 1:

В дополнение к этому тут запоминается значение β (среднее абсолютное значение), которым пользуются для деквантования.

Квантование значений активации

Для обработки значений активации в BitLinear применяется absmax-квантование. Активации преобразуются из типа FP16 в тип INT8, так как они, для целей умножения матриц (×), должны быть представлены с более высокой точностью.

Здесь, кроме того, запоминается значение α (самое большое абсолютное значение), которое позже понадобится для деквантования.

Деквантование

Мы запомнили значения α (самое большое абсолютное значение) и β (среднее абсолютное значение), так как они понадобятся нам для деквантования значений активации и возврата их в формат FP16.

Выходные значения активации перемасштабируют с использованием {α, γ}, что позволяет деквантовать их, преобразовав к числам, точность которых соответствует их исходному виду:

Вот и всё! Эта процедура сравнительно проста, она позволяет описывать модели, используя всего два значения — -1 и 1.

Воспользовавшись этим подходом, авторы заметили, что чем больше становится размер модели — тем меньше оказывается разрыв между эффективностью работы её 1-битного и FP16-варианта.

Но это справедливо только для достаточно больших моделей (>30 миллиардов параметров). Эффективность моделей меньшего размера страдает сильнее.

1,58 бита всем LLM

Модель BitNet 1.58b была выпущена для того, чтобы решить вышеупомянутую проблему, связанную с масштабированием (Ma, Shuming, et al. «The era of 1-bit llms: All large language models are in 1.58 bits.» arXiv preprint arXiv:2402.17764 (2024).).

Суть применяемого в ней нового метода заключается в том, что веса модели теперь, помимо значений -1 и 1, могут принимать и значение 0. При работе с моделью используется тернарный подход к представлению весов. Интересно то, что столь простое решение — добавление к возможным значениям весов обычного ноля — привело к значительным улучшениям по сравнению с BitNet и позволило серьёзно ускорить вычисления.

Мощь ноля

Почему начало использование значения 0 привело к столь серьёзным улучшениям?

Всё дело — в особенностях умножения матриц!

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

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

Создателям BitNet 1.58b удалось отказаться от операции умножения. Смысл значений, которые могут принимать веса, заключается в следующем:

  • 1: прибавить это значение.

  • 0: это значение не нужно.

  • -1: вычесть это значение.

В результате, если веса модели были подвергнуты квантованию, в ходе которого их преобразовали в 1,58-битный формат, умножение матрицы весов и вектора входных данных будет выглядеть так:

Этот подход не только значительно ускоряет вычисления, но ещё и позволяет выполнять фильтрацию признаков (feature filtering).

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

Квантование

Для квантования весов в BitNet 1.58b используется метод absmean — разновидность метода absmax, о котором мы уже говорили.

В ходе работы сжимают распределение весов и используют среднее абсолютное значение (α) для квантования других значений. Каждое значение, в итоге, сводится к одному из трёх вариантов — -1, 0 или 1:

 

В BitNet 1.58b активации квантуются почти так же, как в BitNet. Единственное различие заключается в том, что для масштабирования этих значений применяется диапазон [-2ᵇ⁻¹, 2ᵇ⁻¹], а не [0, 2ᵇ⁻¹]. Для квантования используется метод absmax.

На этом всё! Для квантования значений в 1,58-битный формат нужно (в основном) прибегнуть к двум приёмам:

  • Надо воспользоваться тернарным представлением весов, добавив 0 к их возможным значениям, которые теперь могут быть представлены числами [-1, 0, 1].

  • Надо воспользоваться методом absmean для квантования весов.

Модель BitNet b1.58 с 13 миллиардами параметров эффективнее в смысле задержек, использования памяти и потребления энергии, чем LLM, использующая формат FP16 с 3 миллиардами параметров.

Как результат — мы получаем легковесные модели, на каждый параметр которых, в среднем, нужно 1,58 бита. Это не только экономит память, необходимую для их хранения, но и повышает эффективность использования вычислительных ресурсов при работе с ними.

Итоги

Вот и кончилось наше путешествие по миру квантования! Надеюсь, этот материал позволил вам лучше понять возможности и перспективы сжатия LLM, помог разобраться в таких вещах, как GPTQ, GGUF и BitNet. Кто знает — до чего дойдёт прогресс в деле уменьшения размеров и повышения эффективности больших языковых моделей?

Если хотите ещё картинок, посвящённых разным аспектам LLM — можете взглянуть на мою книгу «Hands-On Large Language Models».

О, а приходите к нам работать? ? ?

Мы в wunderfund.io занимаемся высокочастотной алготорговлей с 2014 года. Высокочастотная торговля — это непрерывное соревнование лучших программистов и математиков всего мира. Присоединившись к нам, вы станете частью этой увлекательной схватки.

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

Сейчас мы ищем плюсовиков, питонистов, дата-инженеров и мл-рисерчеров.

Присоединяйтесь к нашей команде

Комментарии (0)