Симптомы: как выглядит проблема
Вы запускаете GGUF-модель через llama.cpp с флагом -ngl 99 на GPU с 24 ГБ VRAM, например NVIDIA A5000. Модель загружается, инференс идёт, но во время генерации вы замечаете: GPU загружен полностью, а CPU периодически подскакивает до высоких значений. Скорость генерации нестабильна, появляются лаги. Такое поведение сбивает с толку: если модель в VRAM, почему процессор всё ещё работает?
Это типичная ситуация для llama.cpp и аналогичных инструментов. Причины могут быть разными: от неполного оффлоада слоёв до скрытой работы операционной системы. Разберём каждую и дадим практические рекомендации, как снизить нагрузку на CPU и получить стабильную производительность.
Основные причины нагрузки на CPU
Нагрузка на CPU при загруженной в VRAM модели возникает из-за четырёх основных факторов:
- неполное перемещение всех слоёв модели в VRAM;
- подкачка страниц видеопамяти операционной системой;
- выполнение на CPU операций, которые не поддерживаются GPU;
- влияние размера контекста и настроек параллельных вычислений.
Рассмотрим каждый подробнее.
Неполный оффлоад слоев: почему -ngl 99 не всегда работает
Флаг -ngl (n-gpu-layers) указывает, сколько слоёв модели загрузить в VRAM. Значение 99 обычно означает «загрузить все слои», но фактическое количество может быть меньше. Причины:
- Нехватка VRAM с учётом контекста и коммуникационных буферов. Даже если модель весит меньше 24 ГБ, память нужна ещё под KV-кэш, вычисления и служебные данные. При нехватке llama.cpp автоматически оставляет часть слоёв на CPU.
- Ограничения архитектуры модели. Некоторые модели имеют слои, которые нельзя оффлоадить на GPU, например из-за нестандартных операций.
В логах запуска llama.cpp выводит строку вида llm_load_tensors: offloaded 24/28 layers to GPU. Если число меньше общего количества слоёв, часть вычислений выполняется на CPU. Для модели 27B на 24 ГБ VRAM часто оптимально указывать -ngl 24, оставляя запас под контекст. Это может дать более стабильную работу, чем попытка загрузить все слои.
Подкачка страниц видеопамяти: скрытая работа ОС
Операционная система может выгружать часть данных из VRAM в системную память, если видеопамяти не хватает. Это называется подкачкой страниц (paging). При обращении к выгруженным данным возникает задержка и нагрузка на CPU, так как данные нужно вернуть в VRAM. Подкачка может происходить даже при, казалось бы, достаточном объёме VRAM из-за фрагментации памяти или пиковых нагрузок.
Чтобы проверить, происходит ли подкачка, следите за использованием памяти GPU и CPU во время инференса. Если CPU активно работает с диском или памятью, а GPU периодически простаивает, вероятна подкачка.
Операции, которые всегда выполняются на CPU
Некоторые компоненты модели или операции не поддерживаются GPU и всегда выполняются на CPU. Это может быть:
- отдельные слои, например нормализация или эмбеддинги, если их реализация не оптимизирована под GPU;
- определённые типы квантования, которые требуют вычислений на CPU;
- операции с токенами, такие как токенизация и детокенизация.
Даже при полном оффлоаде всех слоёв эти операции будут создавать периодическую нагрузку на CPU. Обычно она невелика, но может стать заметной при длинных последовательностях.
Влияние контекста и параллельных вычислений
Размер контекста напрямую влияет на объём вычислений и памяти. Большой контекст увеличивает KV-кэш, который может не поместиться в VRAM и частично обрабатываться на CPU. Также настройки параллельных вычислений, такие как количество потоков CPU (-t), могут влиять на распределение нагрузки. Если потоков слишком много, они могут конкурировать с GPU за ресурсы, вызывая задержки.
Как проверить, сколько слоев реально загружено в GPU
Самый простой способ - посмотреть вывод llama.cpp при запуске. Найдите строку с информацией об оффлоаде слоёв. Пример:
llm_load_tensors: offloaded 24/28 layers to GPUЕсли числитель меньше знаменателя, часть слоёв осталась на CPU. Это основная причина нагрузки на CPU во время инференса. Также обратите внимание на строки, указывающие использование памяти, например llm_load_tensors: VRAM used: 18.2 GB. Если VRAM почти заполнена, возможна подкачка.
Практические способы снизить нагрузку на CPU
Вот конкретные шаги для оптимизации. Применяйте их по одному, проверяя эффект.
Корректировка -ngl: подбор оптимального количества слоев
Не всегда нужно загружать все слои. Начните с меньшего значения, например -ngl 20, и постепенно увеличивайте, следя за использованием VRAM и логами. Цель - найти максимальное число слоёв, при котором VRAM не переполняется и не возникает подкачки. Для модели 27B на 24 ГБ VRAM часто подходит -ngl 24. Оставляйте запас 1-2 ГБ под контекст и коммуникации.
Использование --no-mmap для ускорения загрузки
По умолчанию llama.cpp использует mmap для отображения файла модели в память. Это экономит RAM, но может вызывать подкачку страниц при обращении к модели. Если у вас достаточно оперативной памяти (например, 64 ГБ), попробуйте запустить с флагом --no-mmap. Модель будет загружена целиком в RAM, что исключит подкачку и снизит нагрузку на CPU. Однако это увеличит потребление RAM и время загрузки.
Настройка потоков CPU (-t/--threads)
Параметр -t задаёт количество потоков для вычислений на CPU. Если часть слоёв осталась на CPU, увеличение потоков может ускорить их обработку. Но слишком большое число потоков создаст конкуренцию с GPU. Начните с числа физических ядер и тестируйте. Например, для 8-ядерного процессора попробуйте -t 4, -t 6, -t 8 и сравните скорость.
Эксперименты с --batch-size
Параметр --batch-size определяет количество токенов, обрабатываемых за один шаг. Больший размер пакета увеличивает нагрузку на GPU, но может уменьшить частоту обращений к CPU. Однако он также требует больше памяти. Попробуйте значения 512, 1024, 2048 и измерьте скорость генерации. Оптимум зависит от модели и оборудования.
Заключение: баланс между CPU и GPU
Полностью исключить нагрузку на CPU в llama.cpp часто невозможно из-за архитектурных ограничений и необходимости выполнять некоторые операции на процессоре. Но её можно минимизировать. Проверьте логи, скорректируйте -ngl, используйте --no-mmap при достаточной RAM, настройте потоки и размер пакета. Экспериментируйте и мониторьте использование ресурсов. Так вы добьётесь стабильной работы и максимальной скорости на вашем оборудовании.
Дополнительные материалы по теме: как VRAM, PLE и контекст меняют скорость в llama.cpp, запуск больших LLM на 16 ГБ VRAM, как mmap помогает запускать модели на 16 ГБ VRAM, почему tensor-read-lazy не спасает от нехватки памяти.