Практическое руководство по квантованию LLM: INT8, FP8, AWQ, GPTQ — что выбрать и почему

opensourceaillmquantizationgpuit
← Back to Blog

Введение: зачем квантовать 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