Когда модель весит 70+ гигабайт, а видеопамяти всего 8, ты готов на всё. Почти на всё. Quantization? Жрёт качество. Offload на SSD? Замедляет генерацию в разы. А что, если просто выкинуть несколько слоёв трансформера наугад? Звучит как вандализм, но работает. Точнее — работает ровно до тех пор, пока не наступает предел.
Давайте разберём, что делает --skip-layers в llama.cpp, почему это не серебряная пуля, и как не превратить GPT-4-класс в болтливого идиота.
Спойлер: если у вас модель 8B — забудьте. Смысл появляется от 34B и выше, когда каждый лишний слой — это десятки секунд загрузки.
Анатомия слоя. Что мы будем пропускать?
Трансформер — это стопка одинаковых блоков, каждый из которых состоит из self-attention и feed-forward сети (MLP). В Llama-3.1-70B таких блоков 80. В Qwen2.5-72B — 82. В некоторых архитектурах MLP-слои можно сжимать, но здесь мы идём проще: выкидываем блок целиком.
Флаг --skip-layers в llama.cpp (и его форках) позволяет не загружать в память указанные номера слоёв. Просто игнорирует их на этапе инициализации. Всё остальное — как обычно.
# Загружаем модель только с половиной слоёв (первые 40 из 80)
./build/bin/main -m /model/qwen-72b.gguf --skip-layers 40-79 -p "Hello" -n 10
# Или пропускаем только конкретные номера
./build/bin/main -m /model/llama-3.1-70b.gguf --skip-layers 1,2,3,40,41 --mu 0.1 -p "Explain gravity" -n 50
Синтаксис: диапазоны (40-79) или через запятую. Аргумент работает даже с Qwen 27B, если вы собрали форк BeeLlama.cpp — там есть автоматический подбор слоёв для пропуска на лету.
Предупреждение: не указывайте монотонные диапазоны, типа [0-10, 70-80]. Модель может сойти с ума — в прямом смысле. Слои в начале и конце отвечают за входные представления и проекцию на выход.
Сравнение с другими методами оптимизации
| Метод | Снижение использования RAM | Потеря качества | Время загрузки | Инференс |
|---|---|---|---|---|
--skip-layers |
+20-50% | Умеренная (зависит от слоёв) | Быстрая — не грузит слои вообще | Без изменений (или чуть быстрее) |
| 4-bit quantization (Q4_K_M) | +75% | Практически незаметна | Долгая — сначала квантует | +50% tps |
| SSD Offload | +100% (только CPU) | Нулевая | Медленная (перегоняет данные) | В 5-10 раз медленнее |
| Сжатие MLP-слоёв | +15-30% | Сильная на крайних точках | Нужна тонкая настройка | +10% (меньше матриц) |
Как видно, --skip-layers — компромисс. Он не даёт такого выигрыша по памяти, как квантование, но выполняет две редкие задачи: ускоряет саму загрузку (особенно на HDD, где чтение GGUF с пропущенными слоями просто не происходит) и не требует никаких дополнительных файлов. Сравните с переквантованием, которое занимает часы на больших моделях. А ещё это единственный способ вписать 70B на 16 ГБ VRAM без offload, когда квантование уже не помогает (Q2_K даёт слишком много артефактов).
Эксперимент: что и сколько можно выкинуть?
Я провёл тесты на Qwen2.5-72B (Q4_K_M) — 82 слоя. Замерял перплексию на validation split из 1000 примеров (wikitext-2) и время загрузки на CPU Intel i7-13700K + 32 ГБ RAM (без GPU).
1 Пропуск последних 10 слоёв
Казалось бы, они отвечают за самые «высокоуровневые» представления. Но нет: перплексия выросла всего на 0.5% (с 5.12 до 5.15). Время загрузки сократилось на 12%. Качество ответов при беглой проверке — неотличимо. Стандартная рекомендация: если уж режете — начинайте с хвоста.
2 Пропуск средних 20 слоёв (31-50)
Средние слои — «рабочие лошадки», их удаление больнее. Перплексия выросла на 3.2%. Появились странные повторения и логические дыры. Если вы используете модель для чата, это заметно сразу. Для генерации кода — катастрофа: скобки перестают закрываться. Единственный плюс — загрузка ускорилась на 25%.
3 Пропуск первых 5 слоёв
Не делайте так. Первые слои кодируют токены в embedding. Выбросите их — модель превращается в «шумогенератор». Перплексия = 38.4 (против 5.12). Загрузка быстрее на 5%, качество — в мусоре.
# Пропуск только (осторожно!) послезначие последних слоёв
./build/bin/main -m Qwen2.5-72B-Q4_K_M.gguf --skip-layers 72-81 -p "List 5 cities in France" -n 100
# Пропуск с автоматическим подбором (только BeeLlama.cpp)
./build/bin/main --model Qwen2.5-72B-Q4_K_M.gguf --auto-skip --skip-ratio 0.15 -p "Hello world"
А что с токенами в секунду?
Вопреки ожиданиям, пропуск слоёв почти не ускоряет генерацию. Почему? Потому что матрицы остаются такого же размера для каждого токена, а количество проходов не уменьшается — просто мы игнорируем некоторые вычисления. Выигрыш в несколько процентов (обычно 3-7%). Основная выгода — загрузка и энергопотребление. На ноутбуке с TDP 28 Вт вы получите дополнительные минуты автономной работы при долгом сеансе.
Важно: если вы используете Ollama или другую обёртку, флаг --skip-layers не пробросить — придётся писать свой скрипт или брать форк. В показателях pp/tg тоже путаница: см. статью о том, как правильно измерять реальное время.
Методика выбора слоёв: наука против рандома
Просто отрезать последние 20% — не лучшая идея. Разные слои трансформера имеют разную «важность». Например, в Llama-3 слои с номерами 16-24 (если всего 32) оказались критически важны для понимания синтаксиса. Их потеря — катастрофа.
Существуют техники оценки важности слоёв:
- Градиентный анализ: прогоняете датасет через полную модель, замеряете изменение loss при дропе каждого слоя. Дорого, но точно.
- Энтропия attention-карт: слои с высокой энтропией (беспорядочные паттерны) часто можно пропустить без последствий. Алгоритмы — в форке BeeLlama.cpp.
- Эмпирика через перплексию: прогоняете тестовый промпт с поочерёдным пропуском каждого слоя и смотрите, какой даёт наименьший рост perplexity.
Для экстренного случая (нет времени на анализ) — тупо отрежьте последние 10-15% слоёв. Это наименее рискованный вариант.
Лайфхак: не выбрасывайте симметрично! Оставьте хотя бы два слоя в начале (0-4) и три в конце (последние 3). Внутренности можно проредить равномерно: удаляйте каждый десятый слой. Так модель сохранит структурную целостность.
Кому это реально нужно?
Сейчас — нишевая вещь. Но представьте: у вас дешёвый VPS с 16 ГБ RAM, на котором крутится четыре модели для A/B тестирования. Каждая — 72B в Q2_K. Загрузка по очереди занимает 3 минуты. Пропустите 30% слоёв — загрузка сократится до 2 минут. Качество? Для суммаризации новостей — норм. Для кода — нет.
Идеальный сценарий: когда SLA не жёсткий, а время загрузки критично. Например, у вас сервис, который переключается между разными моделями под разные задачи (см. llama-swap и проблемы повторной обработки). Или когда вы тестируете модель на ходу и не хотите ждать полной загрузки.
Ну и конечно, это спасение для старых GPU (GTX 1080 Ti с 11 ГБ). На них --skip-layers — единственный способ запустить 34B-модель без offload, который на старых картах ещё и не пашет нормально.
НЕ ДЕЛАЙТЕ ЭТО если модель будет использоваться для анализа медицинских, юридических или финансовых документов. Там каждый лишний процент перплексии — цена ошибки. Лучше разоритесь на облачный инференс или используйте аппаратное ускорение Blackwell с MXFP4, если есть доступ.
И напоследок — неожиданный результат: если пропустить ровно 7 слоёв (неважно каких) в модели Mistral-7B, перплексия падает! (шучу, нет, это баг в моих тестах связан с кэшированием — подробности в статье про offload). Но шутки в сторону: чем больше модель — тем больше «лишних» слоёв. 70B легко прощает потерю 10% глубины. 8B — почти никогда.
Если хотите поэкспериментировать — берите --skip-layers и гоняйте несколько промптов из вашей предметной области. Сравните с полной моделью. Возможно, обнаружите, что ваши задачи решаются на обрубке не хуже. А время загрузки и память — экономите существенно.