Практическое руководство по квантованию LLM: INT8, FP8, AWQ, GPTQ — что выбрать и почему
Введение: зачем квантовать LLM?
Большие языковые модели требуют огромного количества памяти. Модель LLaMA 3 70B в BF16 занимает 140 GB VRAM. У вас нет 2x H100? Тогда квантование — ваш единственный вариант.
Квантование — это снижение точности чисел, из которых состоит модель. Вместо 16-битных чисел используем 8-битные, 4-битные или даже меньше.
BF16 (16-bit): 70B params = 140 GB VRAM
FP8 (8-bit): 70B params = 70 GB VRAM
INT8 (8-bit): 70B params = 70 GB VRAM
INT4 (4-bit): 70B params = 35 GB VRAM
NF4 (4-bit): 70B params = 35 GB VRAM
В этом руководстве разберём все основные методы квантования, их плюсы, минусы и когда что применять.
Основы: что такое квантование?
От float к int: простое объяснение
Обычное число (BF16): 3.14159 → 0x4049 (16 бит)
Квантованное число: 3.14 → 180 (8 бит, диапазон 0-255)
Формула де-квантования:
real_value = quantized_value * scale + zero_point
Пример:
quantized = 180
scale = 0.01
zero_point = 0
real_value = 180 * 0.01 + 0 = 1.80
Типы данных для квантования
┌────────┬──────┬────────────┬──────────────────────────┐
│ Format │ Bits │ Range │ Use Case │
├────────┼──────┼────────────┼──────────────────────────┤
│ BF16 │ 16 │ ±65504 │ Training, Inference │
│ FP16 │ 16 │ ±65504 │ Training (NVIDIA Volta+) │
│ FP32 │ 32 │ ±3.4e38 │ Training (baseline) │
│ FP8 │ 8 │ ±57344 │ New: NVIDIA Hopper │
│ INT8 │ 8 │ -128..127 │ Universal quantization │
│ INT4 │ 4 │ -8..7 │ Aggressive quantization │
│ NF4 │ 4 │ 256 values │ Bitsandbytes QLoRA │
│ Q4_0 │ 4 │ custom │ GGUF (llama.cpp) │
│ Q4_K │ 4 │ mixed │ GGUF (mixed precision) │
└────────┴──────┴────────────┴──────────────────────────┘
Методы квантования LLM
1. Post-Training Quantization (PTQ)
Самый простой метод: обучили модель в FP16 → квантовали в INT8.
# Пример PTQ с torch.quantization
import torch.quantization as quantization
# Оригинальная модель (FP16)
model = LlamaForCausalLM.from_pretrained("meta-llama/Meta-Llama-3-70B")
# Квантование после обучения
quantization.quantize_dynamic(
model, # модель для квантования
{torch.nn.Linear}, # какие слои квантовать
dtype=torch.qint8 # целевой тип
)
# Сохраняем квантованную модель
torch.save(model.state_dict(), "model-int8.pt")
PTQ преимущества:
- Быстро (не нужен датасет для калибровки)
- Просто реализовать
- Потеря качества: 1-5%
PTQ недостатки:
- Не работает хорошо ниже INT8
- Чувствительно к распределению данных
2. Quantization-Aware Training (QAT)
Модель обучается с учётом квантования — веса "видят" квантование во время training.
# Пример QAT с torch.ao.quantization
import torch.ao.quantization as quantization
# 1. Калибровка на данных
def calibrate(model, data_loader):
model.eval()
with torch.no_grad():
for images, _ in data_loader:
model(images)
# 2. QAT training
model = get_float_model()
model.quant = quantization.QuantStub()
model.dequant = quantization.DeQuantStub()
# Training loop с симуляцией квантования
for inputs, targets in training_data:
inputs = model.quant(inputs) # симуляция квантования
outputs = model.forward(inputs) # forward
loss = criterion(outputs, targets) # loss
loss.backward() # backward
# optimizer.step()
calibrate(model, calibration_data)
model.float() # возвращаем FP16 для inference с квантованными весами
QAT преимущества:
- Лучшее качество (потеря 0.5-2%)
- Можно квантовать до INT4 без большой потери
QAT недостатки:
- Нужен датасет для калибровки
- Должнее обучение
- Сложнее реализовать
3. AWQ (Activation-Aware Weight Quantization)
Ключевая идея: не все веса одинаково важны. Некоторые веса — "outliers" и их нужно оставить в FP16.
# AWQ алгоритм (упрощённо)
import awq
# 1. Находим важные веса (scale-based)
with torch.inference_mode():
model.quantize(
calib_data, # calibration data (128 samples)
w_bit=4, # 4-bit quantization
group_size=128, # group size
zero_point=True, # use zero-point
exllama_version=2 # use ExLlamaV2 kernel
)
# 2. Результат:
# - Outlier weights (1-3%) остаются в FP16
# - Остальные веса квантованы в INT4
# - Потеря качества: <1%
AWQ параметры:
group_size: 128 (default) — размер группы для scale
exllama_version: 2 — использует ExLlamaV2 kernels
zero_point: True — использует zero-point для точности
AWQ результаты:
Model │ BF16 │ AWQ-4 │ Потеря
───────────────┼───────┼───────┼───────
LLaMA 7B │ 63.6 │ 62.8 │ 0.8
LLaMA 13B │ 68.7 │ 67.9 │ 0.8
LL