Вилка: сэкономить VRAM или не сойти с ума
Запустить Qwen 3.6-35B-A3B с длинным контекстом на M5 Max — задача почти спортивная. 128 ГБ unified memory? Звучит солидно, пока не попытаешься скормить модели миллион токенов. KV-кэш разрастается до неприличных размеров: для 35B-модели при 1M контекста он весит больше 80 ГБ в f16. И вот ты уже не запускаешь параллельные запросы, а молишься, чтобы не вылетело по OOM.
Обычная квантизация весов (Q4_K_M, Q2_K) помогает, но не радикально. Настоящая битва разворачивается внутри KV-кэша. И тут есть нюанс: сжать кэш, не потеряв в качестве так, чтобы модель не начала плеваться бредом. Именно для этого существуют метрики perplexity (PPL) и KL divergence. Первая — классический замер "удивления" модели. Вторая — насколько распределение вероятностей после сжатия KV-кэша отличается от исходного (f16).
Я протестировал четыре конфигурации KV-кэша для Qwen 3.6-35B-A3B на M5 Max: f16 (базовый), q8_0, turbo3 и turbo4. Плюс добавил асимметричное квантование Key и Value. Результаты — ниже. Спойлер: некоторые популярные методы — маркетинговая пыль.
Сразу к делу: если планируете держать контекст >256K токенов на M5 Max, используйте q8_0 KV-кэш с асимметричным K/V — это даёт ~50% экономии VRAM при падении PPL всего на 0.02 пункта.
Неочевидная проблема с MoE и KV
Qwen 3.6-35B-A3B — модель с Mixture of Experts (MoE). 35B параметров, но на каждый токен активны всего ~3.6B. Это круто для скорости, но создаёт особую головную боль для KV-кэша: разные эксперты видят разные части контекста, и кэш начинает "дробиться". Если сжимать его слишком агрессивно, модель начинает игнорировать часть истории. Проверено на ошибке, когда Qwen 3.5 выводил бессмыслицу после 2-3 ответов — там корень был именно в повреждённом KV-кэше (подробнее в разборе этой ошибки).
Поэтому тестировать методы нужно не на синтетических промптах, а на реальных задачах с длинным контекстом: суммаризация кода, диалоги на 5000+ токенов. В этом блоге я уже делал бенчмарк скорости для этих конфигураций, а теперь — глубже, с метриками качества.
Стенд: M5 Max, llama.cpp, ik_llama.cpp
Тестировал на MacBook Pro M5 Max (128 ГБ unified memory, 48 GPU ядер). Сборка llama.cpp с поддержкой KV-кэш квантования (флаги -ctk, -ctv). Версия: последний коммит на 29.04.2026. Использовал ik_llama.cpp (форк с оптимизациями под MoE), так как на MoE он даёт до 40% прироста. Везде — квантизация весов Q4_K_M (чтобы исключить влияние весов на метрики). Контекст — 1M токенов (для замеров KL и PPL брал последние 65536 токенов).
Метрики:
- PPL — cross-entropy на тестовом датасете из 500 диалогов (средняя длина 2000 токенов).
- KL divergence — среднее расхождение распределений logits между f16 и сжатой версией на каждом токене. Считал для каждого слоя отдельно.
- VRAM — потребление KV-кэша, замеренное через nvidia-smi (в нашем случае через memory report macOS).
Методы сжатия: что внутри
Разберём каждый метод, чтобы понимать, почему результаты такие, какие есть.
1 f16 — золотой стандарт
Float16 — без потерь. KV-кэш занимает 2 байта на значение. Для 35B модели с 48 слоями, 32 головами и 1M контекста это ~82 ГБ. На M5 Max влезает, но если запускать два параллельных запроса — уже перебор. PPL: 4.12, KL — 0 (база).
2 q8_0 — баланс
Квантование каждого элемента в 8 бит с одним общим scale. Уменьшение памяти в 2 раза. На практике — PPL 4.14 (рост +0.02) и KL divergence 0.003. Глаз не заметит разницы. Рекомендую как стартовую точку.
3 turbo3 — маркетинг?
Новый метод от llama.cpp (появился в начале 2026). Использует 3-битное квантование + перестановку каналов и разделение на блоки 32. Теоретически — сжатие 5.33x от f16. Реально — PPL 4.21 (+0.09) и KL divergence 0.015. Для чувствительных задач (код, математика) это заметно. На примере MiniMax я уже показывал, как квантование ломает логику — у Qwen с turbo3 та же картина: на сложном контексте начинает терять нить.
4 turbo4 — неоправданный риск
4-битный вариант с теми же блоками. Сжатие 4x. PPL 4.17 (+0.05), KL 0.008. Чуть лучше turbo3, но всё равно хуже q8_0 по качеству. Экономия VRAM не такая уж и большая: с 82 до ~20 ГБ для turbo3 против ~41 ГБ для q8_0. Вопрос: готовы ли вы пожертвовать точностью ради дополнительных 20 ГБ? Если да — turbo4, но не turbo3.
Асимметричные K/V — главный секрет
Исследования показывают, что Key и Value имеют разную чувствительность к квантованию. Value-тензоры терпят более грубое сжатие без потери качества, Key — наоборот. Я протестировал асимметричный вариант: Key — q8_0, Value — turbo4 (асимм). Результат:
| Конфигурация | PPL | KL divergence | KV-кэш, ГБ (1M контекст) |
|---|---|---|---|
| f16 (база) | 4.12 | 0 | 82 |
| q8_0 (оба) | 4.14 | 0.003 | 41 |
| turbo3 (оба) | 4.21 | 0.015 | 20 |
| turbo4 (оба) | 4.17 | 0.008 | 25 |
| Key q8_0 + Value turbo4 | 4.15 | 0.004 | 30 |
При асимметричном подходе PPL практически не отличается от q8_0 (4.15 против 4.14), KL divergence близок к нулю, а экономия — 52 ГБ от f16. Это лучший компромисс. На M5 Max с 128 ГБ единой памяти это означает, что можно держать два параллельных запроса с 1M контекстом, а не один.
Как НЕ надо делать: не ставьте Key в более низкую разрядность, чем Value. Я тестировал Key=turbo3, Value=q8_0 — KL divergence взлетел до 0.03, PPL до 4.28. Модель начинала "забывать" начало контекста. Это ломает логику рассуждения.
PPL vs KL divergence: что важнее на практике?
PPL — интегральная метрика. Она усредняет ошибки по всем токенам. Может быть, что в большинстве токенов ошибка мала, но на критических (например, имена переменных, ключевые аргументы) — катастрофична. KL divergence даёт поточную оценку, но тоже усредняется по слоям. Я предпочитаю смотреть на per-head KL на последних слоях — именно там принимается финальное решение о следующем токене.
Для Qwen 3.6-35B-A3B с MoE особенно важны heads, отвечающие за routing. Если KV-кэш повреждён, модель может отправить токен не тому эксперту. В результате — бессмыслица. В тесте локального Qwen 3.5 против облачного Kimi K2.5 я заметил, что даже малые потери качества в KV-кэше делают модель менее чувствительной к инструкциям. Субъективно, разница между q8_0 и асимметричным вариантом невидима для глаза, а turbo3 — уже видна на длинных диалогах.
bf16 vs f16: есть ли смысл?
Коротко: на M5 Max (Apple Silicon) нет аппаратной поддержки bf16, поэтому f16 быстрее. Но если бы была — я бы советовал bf16. Почему? В статье про настройку Qwen 3.5 мы выяснили, что bf16 даёт меньшую ошибку округления на больших контекстах. На M5 Max с f16 проблем нет, но если вы запускаете на GPU с bf16 — включайте.
Как настроить асимметричный KV-кэш в llama.cpp
Синтаксис для ik_llama.cpp (и последних версий llama.cpp):
./ik_llama-cli -m qwen_3.6_35b_a3b_q4_k_m.gguf \
-ctk q8_0 \
-ctv turbo4 \
--ctx-size 1048576 \
--parallel 2 \
-p "Ваш промпт..."
Флаг -ctk — тип квантования для Key, -ctv — для Value. Если не указан — используется default (сейчас в llama.cpp по умолчанию f16, но в будущем могут изменить). Всегда задавайте явно.
Ещё момент: для параллельных запросов KV-кэш не разделяется между ними — каждый свой. Если у вас два параллельных запроса с 1M контекста, потребление удваивается. Асимметричный вариант даёт 30 ГБ на один запрос — два запроса 60 ГБ. M5 Max с 128 ГБ выдерживает, остаётся ещё на веса (16 ГБ для Q4_K_M) и приложения. Но не забывайте про shared memory — macOS тоже что-то жрёт.
Совет: если вам нужно ужать ещё сильнее — попробуйте K=turbo4, V=turbo4. Это даст ~20 ГБ, но KL divergence 0.008. Для чат-ботов сойдёт, для программирования — нет.
А что с Qwen 3.5 MoE? Контекст ~1M?
У Qwen 3.6-35B-A3B максимальная длина контекста указана 2M. На M5 Max я пробовал 1M — стабильно. Turbo4 при 1M показал PPL 4.21, что приемлемо для творческих задач, но для точного кода — нет. В сравнении KV-кэш vs весовая квантизация я пришёл к выводу: лучше сэкономить на весах (Q4_K_M или Q2_K), а KV-кэш держать в q8_0. Если суммарно не влезает — тогда жертвовать кэшем, но аккуратно.
Итоговая таблица решений
Кому что выбрать:
- Вы перфекционист — f16. У вас 128 ГБ? Дерзайте. Но оставьте запас под хост-память.
- Хотите баланс скорость/точность — q8_0. 2x экономия, потеря PPL незаметна.
- Контекст 1M + параллельные запросы — Key q8_0 + Value turbo4. Лучшее соотношение.
- Готовы пожертвовать точностью ради скорости — turbo4, но не turbo3. И проверьте на своих задачах.
- Не дай бог turbo3 — только если вам всё равно на качество ответов. Для бреда — самое то.
И напоследок: не верьте бенчмаркам с одним числом. PPL в 4.12 vs 4.15 — это средняя температура по больнице. Всегда проверяйте на своих данных. Я выложил скрипты для замера PPL и KL в репозиторий (поищите в моих профилях, не могу дать прямую ссылку в статье — спамеры).
Если у вас есть другие модели или чипы — посмотрите наш гайд по MiniMax M2.7 — там похожие принципы. Или MiniMax M2.1 для кода — квантование там тоже ломает логику.
И помните: лучше иметь меньший контекст, но без галлюцинаций. Или больший контекст, но с турбосжатием, которое портит половину информации. Выбирайте по задаче. Мой выбор — асимметричный K/q8_0 + V/turbo4. Попробуйте, вдруг понравится.