⚡ Как сделать модели быстрее? | Время изучения: ~50 минут | Уровень: 🏗️ Продвинутый
- 🎯 Краткое введение
- 👨🏫 Дистилляция: «большой учитель, маленький ученик»
- 🔢 Квантование: «перепакуем числа компактнее»
- 💡 Мини-пример квантования
- 🎯 Когда что выбирать
- 📝 Резюме
- ❓ Вопросы для закрепления
Большие языковые модели (LLM) мощные, но «тяжёлые»: им нужна память и время. На лекции разбираются два главных подхода, как сделать модели легче и быстрее.
- 🎓 Дистилляция — учим маленькую модель вести себя как большая
- 🔢 Квантование — храним и считаем числа меньшей разрядности
- ⚡ LLM-специфичные оптимизации и архитектурные решения
- 🛠️ Практические методы для реального применения
Ключевая идея. Есть модель-учитель (большая, уже обученная) и модель-ученик (меньше и быстрее). Мы передаём ученику знания учителя — не только правильные ответы, но и распределения вероятностей по вариантам ответа (soft labels). Это даёт больше подсказок, чем «жёсткая» метка класса. Аналогия: не просто сказать «ответ — B», а показать всё ранжирование: «B — 0.6, A — 0.3, C — 0.1».
Как это оптимизируется (без боли). К обычной кросс-энтропии добавляют меру «похожести распределений» — KL-дивергенцию между предсказаниями учителя и ученика. В упрощённом виде:
🤔 Простыми словами:
Функция потерь = Учёба на правильных ответах + Копирование стиля учителя
Где:
- Первая часть: ученик должен давать правильные ответы
- Вторая часть: ученик должен быть похож на учителя по "уверенности"
- λ (лямбда): насколько сильно слушаться учителя
Головой воспринимайте так: 1) не забываем верные ответы, 2) копируем стиль уверенности учителя. Параметр лямбда регулирует, «как внимательно слушать учителя».
Живой пример — DistilBERT. Ту же архитектуру BERT упростили (меньше слоёв), получили ≈40% меньше параметров, ≈60% быстрее, при этом ~95% качества исходного BERT по GLUE. Идея: «урезать в глубину» даёт заметный выигрыш по скорости при умеренной потере точности.
Памятка.
-
Soft-лейблы хранят «нюансы» уверенности → ученик лучше имитирует учителя.
-
Хорошая инициализация и баланс между CE и KL важны для стабильности обучения.
Ключевая идея. Храним веса/активации не в float32/float16, а, например, в int8 (8 бит) — экономим память и ускоряем вычисления. Метафора: вы переупаковали чемодан — вещей столько же, но всё компактнее. В LLM это критично: огромные модели иначе просто не помещаются в память.
- Симметричное: ноль остаётся нулём; достаточно одного коэффициента масштаба ss.
Формулы:
🤔 Простыми словами (симметричное):
1. Делим исходное число на масштаб
2. Округляем до целого
3. Чтобы восстановить: умножаем на масштаб обратно
(с обрезкой в допустимый диапазон int8). Простой и быстрый вариант.
- Асимметричное: добавляем смещение zz (zero-point), чтобы подогнать диапазон.
🤔 Простыми словами (асимметричное):
1. Делим исходное число на масштаб + добавляем смещение
2. Округляем и обрезаем в нужный диапазон
3. Восстанавливаем: масштаб × (число - смещение)
Точнее передаёт диапазон, но вычислительно сложнее. Аналогия: не только «масштабируем» линейкой, но и «сдвигаем» начало отчёта.
-
По гранулярности:
per-tensor (быстрее, но грубее), per-channel / по группам (точнее), вплоть до «по-токенно». Для LLM пер-тензор часто грубоват, а групповой вариант — хороший компромисс. -
Что квантовать:
веса — легко (статичны); активации — сложнее (зависят от входа), поэтому используют статическую (по калибровочной выборке) или динамическую калибровку на лету. -
Когда квантовать:
PTQ (после обучения) — быстро и дёшево по данным; QAT (во время обучения) — дороже, но точнее.
В LLM выбросы особенно часто в активациях, и просто «обрезать» их нельзя — это рушит качество. Часто выбросы концентрируются в отдельных столбцах/строках, что позволяет придумать хитрые схемы, где «трудные» части обрабатываются по-особому.
-
LLM.int8 (outlier-aware): «обычные» столбцы считаем в int8, а столбцы с выбросами — во float16 и затем склеиваем результат. Так почти всё быстрее и компактнее, а «трудные» места не портят точность. Порог «что считать выбросом» берут эмпирически (например, значения больше ~6).
-
GPTQ: вместо округления каждого веса по отдельности подбираем квантованную матрицу весов W′W', чтобы ∥WX−W′X∥|WX - W'X| была минимальна на калибровочных примерах. То есть оптимизируем сразу матрицу, а не отдельные числа.
-
SmoothQuant: «перекладываем сложность» с активаций на веса. Масштабируем активации вниз, веса вверх (и наоборот), чтобы пики в активациях сгладить — квантовать становится проще, качество держится. Работает как PTQ для int8, ускоряя инференс при малых потерях точности.
Пусть у нас значения слоя лежат приблизительно в [−1.0,1.0][-1.0, 1.0]. Возьмём симметричное int8: диапазон q∈[−128,127]q \in [-128, 127].
Выберем масштаб s≈1.0127≈0.00787s \approx \frac{1.0}{127} \approx 0.00787.
-
Число x=0.52x=0.52:
q=round(0.52/0.00787)≈66q=\text{round}(0.52/0.00787)\approx 66.
Обратное: x^=s⋅q≈0.519\hat{x}=s\cdot q\approx 0.519. -
Число x=−0.02x=-0.02:
q≈round(−0.02/0.00787)=−3q\approx \text{round}(-0.02/0.00787)=-3,
x^≈−0.0236\hat{x}\approx -0.0236.
Мы слегка теряем точность, но выигрываем в памяти/скорости — особенно заметно на больших матрицах. (В активациях часто лучше идёт асимметрия с нулевой точкой zz.)
-
Нужно резко ускорить инференс без переобучения → начните с PTQ int8 + метод против выбросов (LLM.int8, SmoothQuant).
-
Есть время дообучить → QAT поможет сохранить точность.
-
Сильно урезать модель для продакшена на слабом железе → добавьте дистилляцию (например, уменьшить глубину, как в DistilBERT).
-
Дистилляция учит «маленького» ученика копировать распределения «большого» учителя (CE + KL), что сохраняет качество при меньшем размере и большей скорости. Пример — DistilBERT: меньше параметров, быстрее, почти прежнее качество.
-
Квантование — это компактное представление чисел (int8 и др.): симметричное/асимметричное, per-tensor vs per-channel, веса vs активации, PTQ vs QAT. Главная проблема — выбросы в активациях, их лечат методами LLM.int8, GPTQ, SmoothQuant.
-
В реальных LLM обычно комбинируют несколько подходов (например, дистилляция + PTQ + обработка выбросов) для лучшего баланса «скорость—память—качество».
-
Зачем в дистилляции нужны soft-лейблы и как роль KL-дивергенции отличается от роли кросс-энтропии? (Ответ в одном-двух предложениях.)
-
Дайте формулы прямого/обратного преобразования для асимметричного квантования и объясните простыми словами смысл параметров ss и zz.
-
В чём идея LLM.int8 и SmoothQuant? Чем они помогают именно против выбросов?
- Предыдущая: 🔬 Как обучают модели с нуля?
- Следующая: 🛡️ Безопасность и точность ответов
- Обучение: 🎯 Как обучают современные LLM?
💻 Практика: Попробуйте BitsAndBytes для QLoRA или ONNX Runtime для оптимизации вывода!
💡 Подсказка
LLM.int8: выбросы обрабатывает в float16, остальное — в int8, потом склеивает. SmoothQuant: «перекладывает сложность» — масштабирует активации вниз, веса вверх, чтобы сгладить пики.
- Предыдущая: 🔬 Как обучают модели с нуля?
- Следующая: 🛡️ Безопасность и точность ответов
- Обучение: 🎯 Как обучают современные LLM?
- BitsAndBytes для QLoRA
- ONNX Runtime для оптимизации вывода
- GPTQ - репозиторий с реализацией
- DistilBERT - готовые модели
💡 Практика: Попробуйте BitsAndBytes для QLoRA или ONNX Runtime для оптимизации вывода!