Всем привет! Меня зовут Максим, я NLP‑инженер в red_mad_robot и автор Telegram‑канала Максим Максимов // IT, AI. Рост числа параметров в LLM и других нейронных сетях создает проблему того, что запускать их может все меньшее количество людей. Это связано с тем, что запуск больших моделей требует наличие мощного оборудования, недоступное всем.
Для решения этой проблемы разрабатываются различные виды оптимизации, позволяющие запускать крупные нейронные сети (в частности LLM) на менее мощном оборудовании. Одним из наиболее популярных подходов оптимизации LLM является квантизация.
В этой статье мы рассмотрим на чем строится метод квантизации, немного погрузимся в наиболее популярные ее методы, а также рассмотрим практические примеры, как применить квантизацию LLM для своего проекта.
Форматы хранения чисел в памяти компьютера
Так как квантизация основывается на работе с различными форматами чисел в памяти компьютера, мы рассмотрим, какие форматы чисел бывают и чем они отличаются друг от друга. Также покажем на примере, как меняется размер LLM в зависимости от формата её весов.
Для понимания рассмотрим визуальный пример отличия форматов FP32 и FP16. Их отличие заключается в использовании меньшего количества бит для хранения числа. При уменьшении формата (FP32 -> FP16) сокращается размер мантиссы (дробная часть, определяющая точность) и экспоненты (степень, определяющая диапазон чисел).

Наиболее распространенные форматы данных, их размер и основное применение собрано в таблице ниже

Визуализировать размер каждого из форматов для сравнения можно следующим образом:

Отлично, мы разобрались с форматами данных. Теперь давайте рассмотрим пример сравнения занимаемой памяти LLM при хранении её весов в разных форматах.
В качестве LLM возьмем Llama с 7 миллиардами параметров (~7 000 000 000). Расчеты приводятся примерные, но близкие к реальности.

Можем увидеть, каким образом меняется количество памяти, которое занимает LLM, в зависимости от формата чисел, в котором хранятся её параметры. Эта разница наталкивает на идею хранить LLM в более низком формате данных, но у такого подхода есть нюансы, которые мы рассмотрим далее.
Что такое квантизация?
На этой ноте мы можем перейти к такому понятию, как квантизация.
Мне понравилось определение, которое дается на Hugging Face (перевод):
«Квантование — это метод снижения вычислительных затрат и затрат памяти на инференс модели путем представления весов и активаций с помощью типов данных низкой точности, таких как 8-битное целое число (int8) вместо обычного 32-битного числа с плавающей запятой (float32).»
Сокращение количества бит означает, что результирующая модель требует меньше памяти, потребляет меньше энергии (в теории), а такие операции, как умножение матриц, могут выполняться гораздо быстрее с помощью целочисленной арифметики. Это также позволяет запускать модели на встраиваемых устройствах, которые иногда поддерживают только целочисленные типы данных.“
Визуализировать это можно следующим образом:

То есть посредством квантизации мы можем уменьшить объём памяти, занимаемый LLM, что позволит запускать её на менее мощном оборудовании. Также такие преобразования могут повысить скорость работы модели.
Ценой такой оптимизации является потеря точности модели — это основной минус квантизации. Различные подходы к квантизации позволяют сделать потерю точности незначительной, при этом уменьшив размер LLM и ускорив её работу.
Популярные виды квантизации
Рассмотрим различные виды квантования:
Квантование после обучения (PTQ) — это метод, при котором квантизация модели происходит после её обучения. Например: вы обучили LLM в формате FP16, после этого применяете квантизацию, которая снижает формат хранения весов до INT4/INT8.
Квантование во время обучения (QAT) — при таком подходе квантизация модели происходит непосредственно во время её обучения. Грубо говоря, можно сказать, что мы «учим» модель с учётом того, что в будущем она будет «инференсить» на более низком формате весов.
Ниже представлена табличка для сравнения плюсов и минусов каждого из подходов.

Рассмотрим несколько примеров применения квантования PTQ на своих моделях в проекте. Все нижеперечисленные эксперименты проводились в облаке с использованием GPU RTX 4090 (16 ГБ VRAM).
Целочисленное квантование
Для понимания способа перевода параметров LLM с плавающей запятой в целочисленный формат рассмотрим простые методы симметричного и асимметричного квантования. Изобразить их можно следующим образом:

Суть заключается в том, что мы при помощи масштабирования приводим числа весов из формата с плавающей запятой (который имеет намного больший диапазон значений) к диапазону целочисленных значений от -128 до 127.
При симметричном квантовании ноль вещественных чисел совпадает с нулём целочисленных, что упрощает вычисления. При асимметричном — ноль может быть смещен, что позволяет точнее квантовать данные со смещенным распределением.
Более подробную теоретическую информацию можно получить здесь.
Далее рассмотрим наиболее простой способ реализовать перевод модели в INT8-формат. Для этого можно использовать библиотеку bitsandbytes.
На просторах Hugging Face вы сможете найти достаточно много моделей в формате bnb (bitsandbytes), чтобы использовать их в своих проектах. Для этого введите в поиске название модели и добавку «bnb».

Модель в формате bnb можно использовать для запуска языковой модели при помощи vllm, для этого нужно использовать аргумент --quantization bitsandbytes. Например:
vllm serve "monsterapi/opt-350m_4bit_bnb" --quantization bitsandbytes
Кстати, я уже рассказывал о vllm и других инструментах развертывания LLM в своей прошлой статье.
Также вы можете перевести свою модель в формат bnb, используя библиотеку transformers.
from transformers import AutoModelForCausalLM, AutoTokenizer, BitsAndBytesConfig
# конфигурация квантования load_in_8bit - перевод в 8 бит
quantization_config = BitsAndBytesConfig(load_in_8bit=True)
# Загрузка модели с учетом квантования
model = AutoModelForCausalLM.from_pretrained(
"bigscience/bloom-560m",
device_map="auto",
quantization_config=quantization_config)
И воспользоваться ей
input_text = "If you want to win, then you"
# Токенизация текста
input_ids = tokenizer.encode(input_text, return_tensors="pt")
# Переместите input_ids на то же устройство, что и модель.
input_ids = input_ids.to(model.device)
# Генерация текста
output = model.generate(input_ids, max_length=50, num_return_sequences=1)
# Декодирование и вывод сгенерированного текста.
generated_text = tokenizer.decode(output[0], skip_special_tokens=True)
print(generated_text)
Напишу здесь одну полезную мысль, которая мне не сразу была очевидна. Когда я начал тестировать скорость работы LLM с квантованием bnb и без, то заметил странную вещь: скорость не увеличивается, а в некоторых случаях даже ухудшается.
При изучении этого вопроса до меня дошло, что квантизация не всегда увеличивает скорость вывода модели. Чаще всего она уменьшает именно потребление памяти, но не всегда положительно влияет на скорость.
FP8
Для квантования в формат FP8 можно воспользоваться библиотекой llm‑compressor. Это популярная библиотека от vllm, которая позволяет производить оптимизацию моделей.
Работу этой библиотеки можно визуализировать следующим образом:

Рассмотрим пример 8-битной квантизации при помощи этой библиотеки.
# pip install transformers llmcompressor vllm
from transformers import AutoModelForCausalLM, AutoTokenizer
from llmcompressor import oneshot
from llmcompressor.modifiers.quantization import QuantizationModifier
from llmcompressor.utils import dispatch_for_generation
MODEL_ID = "google/gemma-2-2b-it" # здесь может быть ваша модель
OUTPUT_DIR = "./quantized_gemma_model"
# 1) Загрузка модели
model = AutoModelForCausalLM.from_pretrained(MODEL_ID, torch_dtype="auto")
tokenizer = AutoTokenizer.from_pretrained(MODEL_ID)
# 2) Настройка и выбор алгоритма квантования
recipe = QuantizationModifier(
targets="Linear", scheme="FP8_DYNAMIC", ignore=["lm_head"]
)
# 3) Применение квантования и сохранение результата.
oneshot(
model=model,
recipe=recipe,
tokenizer=tokenizer,
output_dir=OUTPUT_DIR
)
Примечание: после некоторых тестов я обнаружил, что не все виды квантования могут совместимы с vllm, для того чтобы узнать совместимость, читайте официальную документацию vllm по квантованию здесь.
Для того чтобы в vllm произвести FP8 квантование, можно запустить модель с аргументом –quntization fp8
vllm serve --model quantized_gemma_model --quantization fp8
В официальной документации vllm, имеется примечание, которое стоит учитывать при использовании этого метода квантования: «Currently, we load the model at original precision before quantizing down to 8-bits, so you need enough memory to load the whole model.»
После запуска можно работать с моделью:
curl http://localhost:8000/v1/chat/completions -H "Content-Type: application/json" -d
'{
"model": "quantized_gemma_model",
"messages": [
{"role": "user", "content": "Напиши короткое приветствие."}
],
"max_tokens": 100,
"temperature": 0.7
}' | jq -r '.choices[0].message.content'
# Привет! ?
AWQ
Давайте рассмотрим ещё один тип квантизации — Activation‑aware Weight Quantization, или AWQ. Это метод квантования весов, который позволяет переводить модели в 4-битный формат без значительных потерь в точности. Для этого алгоритм AWQ, помимо весов модели, использует статистику по активации наиболее важных весов, которая происходит при инференсе модели на калибровочных данных. Более подробную теоретическую информацию можно найти здесь.
Чтобы найти открытую модель на Hugging Face в формате AWQ, в поиске введите название модели, а также приписку «AWQ».

Если же вы хотите перевести свою кастомную LLM в AWQ, также можно использовать библиотеку llm‑compressor. Сделать это можно следующим образом
from llmcompressor.modifiers.awq import AWQModifier
from llmcompressor import oneshot
from datasets import load_dataset
from transformers import AutoModelForCausalLM, AutoTokenizer
# Скачивание LLM
MODEL_ID = "TinyLlama/TinyLlama-1.1B-Chat-v1.0"
model = AutoModelForCausalLM.from_pretrained(MODEL_ID, torch_dtype="auto")
tokenizer = AutoTokenizer.from_pretrained(MODEL_ID)
# Выбор и настройка калибровочного датасета
DATASET_ID = "HuggingFaceH4/ultrachat_200k"
DATASET_SPLIT = "train_sft"
NUM_CALIBRATION_SAMPLES = 256
ds = load_dataset(DATASET_ID, split=f"{DATASET_SPLIT}[:{NUM_CALIBRATION_SAMPLES}]")
ds = ds.shuffle(seed=42)
def preprocess(example):
global tokenizer
return {
"text": tokenizer.apply_chat_template(
example["messages"],
tokenize=False,
)
}
ds = ds.map(preprocess)
# Выбор и настройка параметров алгоритма квантования
recipe = [
AWQModifier(ignore=["lm_head"], scheme="W4A16", targets=["Linear"]),
]
# Запуск квантования
oneshot(
model=model,
dataset=ds,
recipe=recipe,
output_dir="TinyLlama-1.1B-Chat-v1.0-AWQ",
max_seq_length=2048,
num_calibration_samples=512,
)
Примечания: После квантования таким способом у меня возникли проблемы при разворачивании (APIServer pid=6470) Value error, Quantization method specified in the model config (compressed‑tensors) does not match the quantization method specified in the quantization argument (awq). [type=value_error, input_value=ArgsKwargs((), {'model':...rocessor_plugin': None}), input_type=ArgsKwargs]
Я начал копаться в чем дело, и обнаружил, что в config.json квантованной LLM конфигурация quantization_config имела значение "quant_method": "compressed-tensors", то есть не AWQ. Далее я залез в исходный код vllm и в ModelConfig при инициализации нашел следующее
quantization: SkipValidation[QuantizationMethods | None] = None
"""Method used to quantize the weights. If `None`, we first check the
`quantization_config` attribute in the model config file. If that is
`None`, we assume the model weights are not quantized and use `dtype` to
determine the data type of the weights."""
То есть, в случае запуска LLM в vllm без аргумента --quantization, vllm сам считает конфиг модели и запустит с заданной квантизацией.
Я попробовал квантизировать модель в AWQ использую библиотеку AutoAWQ
from awq import AutoAWQForCausalLM
from transformers import AutoTokenizer
model_path = 'TinyLlama/TinyLlama-1.1B-Chat-v1.0'
quant_path = 'TinyLlama/TinyLlama-1.1B-Chat-v1.0-autoawq'
quant_config = { "zero_point": True, "q_group_size": 128, "w_bit": 4, "version": "GEMM" }
# Загрузка модели
model = AutoAWQForCausalLM.from_pretrained(model_path)
tokenizer = AutoTokenizer.from_pretrained(model_path, trust_remote_code=True)
# Квантование
model.quantize(tokenizer, quant_config=quant_config)
# Сохранение квантованной модели
model.save_quantized(quant_path)
tokenizer.save_pretrained(quant_path)
После успешной квантизации конфигурация quantization_config LLM имела «quant_method»: «awq». Но после повторного запуска vllm с параметром ‑quantization awq ошибка осталась.
Решение, к которому я пришел, это развертывать LLM после квантизации без параметра quantization, а дать ему самому считать тип квантизации с конфига модели, или же указывать явно параметр --quantization compressed-tensors
vllm serve --model TinyLlama-1.1B-Chat-v1.0-AWQ --quantization compressed-tensors
GTPQ
Это ещё один популярный PTQ‑метод квантования моделей до 4 бит. Он использует асимметричное квантование и делает это слой‑за‑слоем таким образом, что каждый слой обрабатывается независимо, прежде чем перейти к следующему. Изобразить это можно следующим образом:

Более подробный теоретический материал можно найти здесь.
На Hugging Face также можно найти множество открытых моделей в формате GPTQ.

Для того чтобы квантовать свою LLM можно воспользоваться llm‑compressor следующим образом:
from transformers import AutoTokenizer, AutoModelForCausalLM
from datasets import load_dataset
from llmcompressor import oneshot
from llmcompressor.modifiers.quantization import GPTQModifier
# Загрузка модели и токенайзера
MODEL_ID = "TinyLlama/TinyLlama-1.1B-Chat-v1.0"
model = AutoModelForCausalLM.from_pretrained(MODEL_ID, torch_dtype="auto")
tokenizer = AutoTokenizer.from_pretrained(MODEL_ID)
# Загрузка калибровачного датасета
NUM_CALIBRATION_SAMPLES=50
MAX_SEQUENCE_LENGTH=2048
ds = load_dataset("HuggingFaceH4/ultrachat_200k", split=f"train_sft[:{NUM_CALIBRATION_SAMPLES}]")
ds = ds.shuffle(seed=42)
# Предобработка данных
def preprocess(example):
print(example)
return {"text": tokenizer.apply_chat_template(example["messages"], tokenize=False,)}
ds = ds.map(preprocess)
# Конфигурация GPTQ
recipe = GPTQModifier(targets="Linear", scheme="W4A16", ignore=["lm_head"])
# Квантование модели
oneshot(
model=model, dataset=ds,
recipe=recipe,
max_seq_length=MAX_SEQUENCE_LENGTH,
num_calibration_samples=NUM_CALIBRATION_SAMPLES,
)
# Сохранение квантованной модели
SAVE_DIR = "TinyLlama/TinyLlama-1.1B-Chat-v1.0-GPTQ"
model.save_pretrained(SAVE_DIR, save_compressed=True)
tokenizer.save_pretrained(SAVE_DIR)
Далее также поднимаем LLM с помощью vllm командой, используя параметр --quantization compressed-tensors
vllm serve --model TinyLlama-1.1B-Chat-v1.0-GPTQ --quantization compressed-tensors
GGUF
GGUF — это один из популярных форматов хранения LLM, который упрощает их использование и развёртывание. Этот формат был разработан специально для работы с llama.cpp — популярным движком для инференса LLM (также он доступен для использования с Ollama, LM Studio, vllm, GPT4All). Кроме того, GGUF позволяет запускать квантованные модели на слабом оборудовании (в частности — на CPU).
Визуализация этого формата выглядит следующим образом:

На Hugging Face можно найти модели в этом формате, добавив подпись GGUF к названию в поисковике

Для того чтобы перевести модель в формат GGUF можно воспользоваться самой библиотекой llama.cpp. Ниже написана инструкция по ее настройке и квантованию модели в формат GGUF:
Создайте окружение
python -m venv venv
Склонируйте репозиторий
git clone https://github.com/ggerganov/llama.cpp.git
Установите библиотеки
pip install -r llama.cpp/requirements.txt
Перевод модели Hugging Face в GGUF
python ./llama.cpp/convert_hf_to_gguf.py TinyLlama/TinyLlama-1.1B-Chat-v1.0 \
--outfile TinyLlama-1.1B-Chat-v1.0-GUFF \
--outtype q8_0 # настройка квантования
После перевода вы можете развернуть эту модель при помощи llama.cpp (ссылка на доку). Для этого сделаем следующее:
Произведем сборку cmake командами
cmake -B build -DGGML_CUDA=ON -DCMAKE_CUDA_ARCHITECTURES=native
cmake --build build --config Release -j $(nproc)
Далее запускаем сервер с моделью
./build/bin/llama-server -m ../TinyLlama-1.1B-Chat-v1.0-GUFF.gguf --port 8080
Либо можно развернуть GGUF модели с помощью vllm командой
vllm serve ./TinyLlama-1.1B-Chat-v1.0-GUFF.gguf --tokenizer TinyLlama/TinyLlama-1.1B-Chat-v1.0
И после использовать эту модель у себя в проекте.
Заключение
В этой статье была дана небольшая теоретическая и практическая вводная по квантованию LLM. Мы рассмотрели различные форматы данных, в которых могут храниться модели, ознакомились с наиболее популярными методами квантования, а также разобрали практические примеры квантования LLM для запуска их в своих проектах. Целью данной работы было самостоятельно погрузиться в эту тему, а также дать ориентир тем, кому это интересно.
Над материалом работали:
текст — Максимов Максим
иллюстрации — Саша Буяк
Подписывайтесь на мой Telegram‑канал, в котором я рассказываю об интересных вещах в IT и AI.