Инструкция: как уменьшить задержку (TTFT) при квантовании моделей до 4-бит
Пошаговое руководство по квантованию LLM до 4 бит с сохранением качества: выбираем метод, замеряем TTFT и скорость генерации, проверяем деградацию и запускаем модель локально.
Зачем квантовать?
Большие языковые модели (7B+ параметров) требуют значительных вычислительных ресурсов. Полноточная модель (FP16) на 7B параметров занимает ~14 ГБ VRAM. Квантование весов до 4 бит сокращает это примерно до 4 ГБ — достаточно для потребительской видеокарты.
Важная оговорка: квантование в первую очередь экономит память. Выигрыш по задержке зависит от того, какие вычислительные ядра выполняют модель. GPTQ и AWQ со специализированными ядрами (Marlin, ExLlama) обычно ускоряют генерацию, а 4-битный режим bitsandbytes экономит память, но при одиночных запросах может работать даже медленнее FP16. Поэтому задержку нужно не предполагать, а измерять.
Измерять будем две метрики, которые отвечают двум фазам генерации:
- TTFT (Time To First Token) — время обработки промпта (prefill). Зависит от длины промпта и вычислительной мощности.
- Tokens/sec — скорость генерации (decode). Ограничена в основном пропускной способностью памяти, поэтому 4-битные веса здесь помогают сильнее всего.
Если хочется разобраться, откуда берутся эти две фазы и как KV-кэш экономит вычисления на каждом шаге, загляните в LLM Arch Research — там генерация с KV-кэшем разобрана построчно.
Подготовка
pip install torch transformers accelerate bitsandbytes
Шаг 1: Замер baseline
Сначала замеряем модель без квантования. TTFT — это время генерации одного токена, скорость декодирования — время остальных токенов:
import time
import torch
from transformers import AutoModelForCausalLM, AutoTokenizer
MODEL = "Qwen/Qwen2.5-7B-Instruct"
def measure(model, tok, prompt, new_tokens=128):
inputs = tok(prompt, return_tensors="pt").to(model.device)
model.generate(**inputs, max_new_tokens=8) # прогрев
torch.cuda.synchronize()
t0 = time.perf_counter()
model.generate(**inputs, max_new_tokens=1, do_sample=False)
torch.cuda.synchronize()
ttft = time.perf_counter() - t0
t0 = time.perf_counter()
model.generate(**inputs, max_new_tokens=new_tokens, min_new_tokens=new_tokens, do_sample=False)
torch.cuda.synchronize()
decode = time.perf_counter() - t0 - ttft
return {
"ttft_ms": ttft * 1000,
"tokens_per_sec": (new_tokens - 1) / decode,
"peak_mem_gb": torch.cuda.max_memory_allocated() / 1e9,
}
tok = AutoTokenizer.from_pretrained(MODEL)
fp16 = AutoModelForCausalLM.from_pretrained(MODEL, torch_dtype=torch.float16, device_map="auto")
print(measure(fp16, tok, "Объясни, что такое квантование нейросетей."))
Шаг 2: Быстрое квантование NF4 (bitsandbytes)
Самый простой путь — квантовать веса прямо при загрузке, без калибровки:
from transformers import BitsAndBytesConfig
bnb = BitsAndBytesConfig(
load_in_4bit=True,
bnb_4bit_quant_type="nf4",
bnb_4bit_compute_dtype=torch.bfloat16,
bnb_4bit_use_double_quant=True,
)
int4 = AutoModelForCausalLM.from_pretrained(MODEL, quantization_config=bnb, device_map="auto")
print(measure(int4, tok, "Объясни, что такое квантование нейросетей."))
Ключевые параметры:
bnb_4bit_quant_type="nf4"— формат NormalFloat4, точнее обычного INT4 для нормально распределённых весов.bnb_4bit_compute_dtype=torch.bfloat16— вычисления в bf16; по умолчанию они идут в fp32, и это заметно замедляет модель.bnb_4bit_use_double_quant— квантует и сами коэффициенты масштаба, экономя ещё около 0,4 бита на параметр.
Шаг 3: Если нужна именно низкая задержка — GPTQ или AWQ
GPTQ и AWQ квантуют веса с калибровкой на небольшом наборе данных и запускаются на быстрых ядрах. Для популярных моделей готовые чекпоинты уже опубликованы, а сервер инференса вроде vLLM подхватывает их сам:
pip install vllm
vllm serve Qwen/Qwen2.5-7B-Instruct-AWQ
Если квантуете сами, главные параметры такие:
- group-size — сколько весов делят один коэффициент масштаба. 128 — разумный баланс; 64 и 32 точнее, но увеличивают размер модели.
- Калибровочные данные — 128–512 примеров, похожих на ваши реальные запросы.
Шаг 4: Проверка качества
Скорость без качества ничего не стоит. Сравните исходную и квантованную модели на одних и тех же задачах — например, с помощью lm-evaluation-harness:
lm_eval --model hf \
--model_args pretrained=Qwen/Qwen2.5-7B-Instruct,load_in_4bit=True \
--tasks mmlu,hellaswag \
--batch_size 8
Сведите в одну таблицу TTFT, tokens/sec, пиковую память и точность. Для 7B-моделей при аккуратном 4-битном квантовании просадка на общих бенчмарках обычно укладывается в 1–2 пункта, но на ваших доменных задачах она может быть другой — проверяйте именно их.
Шаг 5: Упростите системный промпт
После квантования модель может хуже следовать длинным и запутанным инструкциям. Упростите системный промпт: разбейте его на короткие пункты и уберите лишний контекст. Бонус — более короткий промпт сокращает prefill, а значит, и TTFT. Проверяйте варианты промпта A/B-тестом на своём наборе запросов.
Запуск локально
Квантованные модели в формате GGUF удобно запускать через Ollama или LM Studio. А подключив их к CodeLab, который поддерживает Ollama и LM Studio как LLM-провайдеров, вы получите AI-ассистента для кода, который работает полностью на вашей машине.
Рекомендации
- Начинайте с group-size 128; уменьшайте его, только если просадка качества неприемлема.
- Калибруйте на репрезентативных данных, а не на случайных текстах.
- Проверяйте качество на доменно-специфичных тестах, а не только на общих бенчмарках.
- Замеряйте задержку на целевом железе и с реальной нагрузкой: результаты при одиночном запросе и при батче могут отличаться в разы.