Когда 2 ГБ ≠ 2 ГБ: парадокс квантованных моделей
Открываешь Hugging Face, ищешь модель — допустим, Mistral-7B. Видишь список файлов: Q2_K — 2.8 ГБ, Q3_K_M — 3.3 ГБ, Q4_K_M — 4.1 ГБ. Кажется логичным: меньше бит — меньше размер. Потом скачиваешь Q2_K_S и Q2_K_L. Оба — около 2.8 ГБ. Погоди. Почему почти одинаково?
Здесь начинается магия (и немного безумия) формата GGUF. Ты думаешь, что скачиваешь просто сжатые веса. На самом деле получаешь упакованный контейнер с кучей дополнительных данных, структурой и оптимизациями, которые съедают часть «экономии» от квантования.
Если ты ожидал, что 2-битная модель будет в 8 раз меньше 16-битной — забудь. Реальность жестче. Разница между Q2_K и Q8_0 часто всего в 2-3 раза, а не в 4.
Что на самом деле лежит в GGUF-файле?
GGUF — это не просто архив весов. Это структурированный формат, который llama.cpp и подобные инструменты загружают напрямую в память, без дополнительной обработки. И вот из чего он состоит:
- Заголовок с метаданными — 1-2 МБ информации о модели: архитектура, размер контекста, версия GGUF, токенизатор
- Тензоры (веса) — собственно квантованные параметры модели
- Словарь токенизатора — может весить от 5 до 50 МБ в зависимости от размера словаря
- Служебные структуры — смещения, индексы, информация о группах квантования
- Выравнивание данных — всё выровнено по границам памяти для быстрой загрузки
Математика квантования: почему 4 бита ≠ 4 бита на параметр
Вот где большинство новичков ломается. Думаешь: «Модель имеет 7B параметров. FP16 = 2 байта на параметр = 14 ГБ. Q4_0 = 4 бита = 0.5 байта на параметр = 3.5 ГБ». Красиво. Неправильно.
Реальные схемы квантования в GGUF работают сложнее:
| Формат | Теоретический размер | Реальный размер | Что добавляет вес |
|---|---|---|---|
| Q4_0 | 3.5 ГБ | 4.1-4.3 ГБ | +18-22% (весы масштабов) |
| Q3_K_M | 2.6 ГБ | 3.3-3.5 ГБ | +25-30% (разные блоки) |
| Q2_K | 1.75 ГБ | 2.8-3.0 ГБ | +60-70% (сложная структура) |
Почему такая разница? Потому что квантование — это не просто обрезка битов. Это:
- Масштабы (scales) — отдельные значения, которые умножаются на квантованные веса
- Смещения (zero points) — для асимметричного квантования
- Информация о блоках — как данные разбиты на группы
- Дополнительные биты — для контроля ошибок в критических слоях
K-квантования vs I-квантования: два подхода к сжатию
В GGUF есть две большие семьи форматов, и они по-разному влияют на итоговый размер:
K-квантования (K-quants) — современный стандарт. Используют блочную структуру, где разные части тензора квантуются с разной битностью. Например, в Q3_K_M важные веса могут храниться в 6 битах, а менее важные — в 2. Это увеличивает служебные данные, но сохраняет качество.
I-квантования (I-quants) — устаревший подход, но до сих пор встречается. Проще, меньше служебных данных, но хуже качество при той же битности.
Вот практический пример. Возьмем Llama 3.1 8B:
- Q4_K_M (K-квантование) — 4.7 ГБ, качество ~95% от оригинала
- Q4_0 (простое квантование) — 4.3 ГБ, качество ~88% от оригинала
- Разница всего 0.4 ГБ, но качество проседает заметно
Почему Q2_K_S и Q2_K_L почти одинакового размера?
Теперь к главному вопросу. Берем Mistral-7B:
mistral-7b-instruct-v0.3.Q2_K_S.gguf 2.87 GB
mistral-7b-instruct-v0.3.Q2_K_L.gguf 2.88 GB
Разница — 10 МБ. На что они уходят?
Q2_K_S (Small) — использует маленькие блоки (super-blocks) размером 16 элементов. Меньше служебных данных на блок, но больше блоков всего.
Q2_K_L (Large) — использует большие блоки размером 256 элементов. Больше служебных данных на блок, но меньше блоков всего.
Итоговый размер сходится потому, что:
- Количество весов одинаковое (7B параметров)
- Общий объем служебных данных примерно одинаковый, просто распределен по-разному
- Заголовок и токенизатор идентичны
- Выравнивание добавляет «пустые» байты в обоих случаях
Это как упаковка вещей в чемодан: можно взять много маленьких коробок (Q2_K_S) или несколько больших (Q2_K_L). Объем вещей одинаков, разница только в количестве коробок и пустом пространстве между ними.
Токенизатор — скрытый пожиратель места
Мало кто задумывается, но словарь токенизатора может весить 30-50 МБ. И он одинаковый для всех квантований одной модели. Когда общий размер модели 3 ГБ, эти 50 МБ — почти 2%. При сравнении Q2_K_S (2.87 ГБ) и Q2_K_L (2.88 ГБ) разница в 10 МБ теряется на фоне словаря.
Проверим на реальном файле:
import gguf
# Открываем GGUF-файл
gguf_file = gguf.GGUFReader("mistral-7b.Q2_K_S.gguf")
# Смотрим размеры тензоров
total_tensors_size = 0
for tensor in gguf_file.tensors:
total_tensors_size += tensor.n_bytes
print(f"Общий размер файла: {os.path.getsize('mistral-7b.Q2_K_S.gguf') / 1024**3:.2f} GB")
print(f"Размер тензоров (веса): {total_tensors_size / 1024**3:.2f} GB")
print(f"Размер метаданных и токенизатора: {(os.path.getsize('mistral-7b.Q2_K_S.gguf') - total_tensors_size) / 1024**3:.2f} GB")
Типичный результат для 7B модели:
- Общий размер: 2.87 ГБ
- Веса: ~2.65 ГБ
- Метаданные + токенизатор: ~0.22 ГБ
22 ГБ на служебные данные! Вот почему квантования выглядят «толще», чем ожидалось.
Выравнивание и padding: невидимые байты
GGUF оптимизирован для быстрой загрузки в память. Это значит — всё выровнено по границам, удобным для процессора. Обычно по 32 или 64 байта.
Представь тензор размером 100 байт. Система выровняет его до 128 байт. 28 байт — нули. Они ничего не весят на диске (при сжатии), но в отчете о размере файла учитываются.
В маленьких моделях (1-3B) этот эффект заметнее. В больших (70B+) — теряется на фоне основных данных.
Практические выводы: что выбирать и почему
Итак, теперь ты понимаешь, почему размеры квантований так близки. Что с этим делать?
1 Не гонись за минимальным размером
Разница между Q2_K и Q3_K_M — 0.5 ГБ. Но качество падает на 15-20%. Стоит ли экономить 500 МБ, если модель начинает генерировать бред?
2 Смотри на качество, а не на битность
Q4_K_M часто лучше Q4_0, хотя размер почти одинаковый. Q3_K_M может быть лучше Q4_0 при меньшем размере. Биты — не главное.
3 Проверяй реальную производительность
Скачай обе версии и прогрей на своих данных. Иногда Q2_K_L работает быстрее Q2_K_S из-за лучшей локализации данных в кэше процессора.
4 Учитывай свой hardware
На слабом CPU с малым кэшем Q2_K_S может быть лучше. На GPU с быстрой памятью — Q2_K_L. На Apple Silicon с Unified Memory — вообще другие правила.
Запомни главное: если видишь две модели с разницей в 0.1 ГБ — они практически одинаковы по размеру на диске. Выбирай ту, у которой лучше бенчмарки или которая рекомендована сообществом.
Что будет дальше? Будущее квантования
Форматы развиваются. Новые подходы вроде AWQ и Marlin обещают лучшее соотношение размер/качество. Но проблема служебных данных останется.
Мой прогноз: через год-два мы увидим:
- Адаптивное квантование — разные слои с разной битностью в одном файле
- Сжатие метаданных — токенизаторы будут занимать в 10 раз меньше
- Онлайн-квантование — модель загружается в FP16, а квантуется на лету в памяти
- Специализированные форматы — отдельные версии для CPU, GPU, мобильных устройств
А пока — смирись с тем, что 2-битная модель весит не в 8 раз меньше 16-битной. И выбирай Q3_K_M для большинства задач. Проверено.
P.S. Если скачал модель и она работает плохо — не спеши винить квантование. Возможно, ты просто скачал кривую сборку. Такое бывает.