MAATRIX / Блог / Модель загрузилась, но GPU простаивает: разбор по слоям

Модель загрузилась, но GPU простаивает: разбор по слоям

MAATRIX

Вы запускаете локальную модель, в логах ни одной ошибки, процесс инференса стартует, чат отвечает — вроде бы всё в порядке. Но открываете nvidia-smi и видите, что видеокарта загружена на несколько процентов, а генерация ползёт со скоростью, которую вы обычно видите на голом CPU. Это не редкая аномалия, а один из самых частых сценариев в эксплуатации локальных LLM: успешный запуск и эффективное использование GPU — совершенно разные вещи, и движок инференса вам об этом почти никогда не сообщит прямым текстом.

Почему "запустилось без ошибок" ничего не гарантирует

Инференс-движки вроде llama.cpp, Ollama (которая использует его под капотом), vLLM и им подобные спроектированы так, чтобы быть отказоустойчивыми: если что-то пошло не так с GPU-путём, они по умолчанию стараются не падать, а продолжить работу — пусть даже на CPU. Это разумное поведение с точки зрения надёжности сервиса, но опасное с точки зрения диагностики, потому что тихий откат выглядит снаружи как обычный успешный старт.

В логе вы увидите что-то вроде model loaded successfully, сервер поднимется на нужном порту, API будет отвечать на запросы. Ни один из этих сигналов не говорит о том, где именно происходят вычисления — на GPU, на CPU или частично и там, и там. Это ключевая ловушка: отсутствие ошибки в логе воспринимается как подтверждение корректной работы, хотя это всего лишь подтверждение того, что процесс не упал.

Особенно обманчиво это выглядит, если до этого GPU-инференс уже когда-то работал на этом сервере — например, на другой модели или до обновления пакетов. Мозг достраивает "раз работало вчера, значит работает и сегодня", а по факту между вчера и сегодня могло тихо поменяться что угодно: драйвер, версия рантайма, свободный объём VRAM после того, как на карте начал крутиться ещё один процесс.

Что на самом деле нужно проверять: слои, а не факт запуска

Ключевой параметр, который определяет, работает ли GPU, — это количество слоёв модели, реально выгруженных на видеокарту. В llama.cpp и всех движках на его основе (Ollama, LM Studio, koboldcpp и другие) это явный параметр запуска, который в разных интерфейсах называется по-разному, но суть одна — n-gpu-layers (или --n-gpu-layers, num_gpu в API Ollama). Он задаёт, сколько слоёв трансформера будет считаться на GPU, а сколько — на CPU.

Если этот параметр не выставлен явно, оставлен по умолчанию, задан слишком маленьким или вовсе проигнорирован из-за ошибки в конфиге — модель формально загружена и работает, но подавляющая часть вычислений идёт на процессоре. Внешне это выглядит как рабочий сервис. По факту — это CPU-инференс с декоративным использованием GPU на пару процентов, которые уходят на эмбеддинги или отдельные вспомогательные операции.

Первое, что стоит сделать при подозрении на такую ситуацию, — не гадать, а прочитать лог запуска целиком, а не только последнюю строку про успех. У большинства движков на базе llama.cpp при старте в лог явно пишется распределение слоёв между устройствами: сколько всего слоёв в модели и сколько из них выгружено на GPU. Если видите что-то вроде "offloaded 0/33 layers to GPU" или "offloaded 12/33 layers to GPU" при том, что вы ожидали полную выгрузку, — вот и причина.

Второй шаг — смотреть на nvidia-smi (или rocm-smi для AMD) не разово, а в момент активной генерации токена, а не в состоянии простоя. Разовый снимок в состоянии ожидания ничего не покажет — нужно смотреть загрузку именно во время ответа модели:

watch -n 0.5 nvidia-smi

Если во время генерации утилизация GPU (GPU-Util) скачет до 90-100%, а nvidia-smi показывает существенное потребление VRAM процессом инференса — GPU действительно используется. Если утилизация почти не шевелится, а нагружен в основном CPU (проверить htop параллельно) — модель считается практически полностью на процессоре, независимо от того, что написано в интерфейсе чата.

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Развернуть ИИ на сервере

Типичная причина №1: параметр выгрузки слоёв не задан явно

Самая частая причина такой ситуации — банальная: никто явно не указал, сколько слоёв выгружать на GPU, и движок принял решение за вас. В разных инструментах поведение по умолчанию отличается, и не всегда оно оптимально для вашей связки модели и карты.

Например, при запуске через llama-server (бинарник llama.cpp) параметр нужно указывать явно:

./llama-server \
  -m /path/to/model.gguf \
  --n-gpu-layers 999 \
  --ctx-size 8192 \
  --host 0.0.0.0 --port 8080

Значение 999 — это распространённый приём "выгрузить максимум, сколько влезет" (движок сам ограничит его реальным числом слоёв модели). Если этот флаг не передан вовсе, поведение по умолчанию зависит от сборки и версии — где-то это будет 0 слоёв на GPU (то есть чистый CPU), где-то — попытка автоматического определения, которая не всегда угадывает правильно для вашей карты.

В Ollama логика похожая, но спрятана на уровень глубже: сам движок пытается оценить, сколько слоёв влезет в доступную VRAM, исходя из размера модели и объёма памяти видеокарты. Если оценка сделана неточно (например, часть VRAM уже занята другим процессом, а Ollama об этом не знает на момент расчёта), результат — частичная или нулевая выгрузка без явного предупреждения пользователю. Проверить, что реально произошло, можно через:

ollama ps

Команда покажет, сколько модель занимает памяти и на каком устройстве она сейчас загружена — CPU, GPU или смешанно. Если видите в выводе 100% CPU там, где ожидали GPU, — это прямое подтверждение проблемы, и дальше нужно смотреть переменные окружения (OLLAMA_NUM_GPU, доступность CUDA в контейнере, если Ollama запущена в Docker) и логи journalctl -u ollama на предмет ошибок инициализации CUDA, которые не привели к падению процесса, но привели к откату на CPU.

Типичная причина №2: несовместимость драйвера с собранным бинарником

Вторая по частоте причина — рассогласование версий: бинарник инференса собран под одну версию CUDA (или ROCm для AMD), а на сервере установлен драйвер другой ветки. В отличие от явной ошибки компиляции, такое рассогласование часто не приводит к падению процесса — вместо этого рантайм либо не находит нужные библиотеки, либо находит их частично, и тихо откатывается на CPU-путь исполнения, потому что CPU-бэкенд у большинства движков всегда собран как гарантированный резервный вариант.

Проверить, видит ли процесс видеокарту вообще, стоит начать с базового:

nvidia-smi
nvcc --version

Первая команда должна показывать карту и её загрузку, вторая — версию CUDA toolkit, если он установлен отдельно от драйвера. Дальше стоит явно проверить, что сам бинарник инференса действительно собран с поддержкой CUDA, а не в CPU-only варианте — это частая ошибка при установке через универсальные скрипты или при сборке из исходников без явного указания флагов сборки (LLAMA_CUDA=1 или аналогичных, в зависимости от версии и системы сборки конкретного проекта). Если движок собирался без поддержки GPU-бэкенда, никакой параметр n-gpu-layers не поможет — библиотека физически не умеет говорить с видеокартой.

Для контейнерных развёртываний (Docker) отдельная частая грабля — отсутствие --gpus all при запуске контейнера или неправильно настроенный NVIDIA Container Toolkit на хосте. Контейнер в этом случае стартует нормально, модель загружается, но GPU внутри контейнера просто не виден процессу — и снова никакой явной ошибки, только тихий откат.

Типичная причина №3: не хватает VRAM на все слои модели

Третий сценарий отличается от первых двух: тут дело не в конфигурации, а в реальной нехватке видеопамяти. Если модель (с учётом её квантования, размера контекста и KV-кэша) не помещается в доступный объём VRAM целиком, движок может автоматически выгрузить только часть слоёв на GPU, оставив остальные на CPU — и это может произойти даже при формально верно выставленном n-gpu-layers, если запрошенное значение больше, чем реально влезает.

В этом случае результат — не полный откат на CPU, а смешанный режим, который часто оказывается медленнее, чем можно было ожидать: каждый прогон через смешанные слои требует передачи промежуточных данных между CPU и GPU по шине, а это дополнительная задержка на каждом токене. Субъективно это ощущается как "GPU вроде работает, но толку от него мало" — утилизация в nvidia-smi при этом может быть ненулевой, но скорость генерации далека от той, что вы видели бы при полной выгрузке.

Стоит явно прикинуть, сколько VRAM требует модель в выбранном квантовании плюс контекст: чем больше --ctx-size (или его аналог в вашем движке), тем больше памяти уходит под KV-кэш, и при большом контексте GPU может не суметь вместить все слои модели именно из-за кэша, а не из-за самих весов. Ориентировочно прикинуть общий объём (это именно ориентир, точная цифра зависит от архитектуры модели, типа квантования и параметров контекста) можно, сложив размер файла модели на диске с оценкой памяти под KV-кэш при вашем размере контекста — если эта сумма заметно больше объёма VRAM карты, часть слоёв неизбежно уйдёт на CPU, и это ожидаемое поведение, а не баг.

Если карты не хватает и это не устранить меньшим контекстом или более агрессивным квантованием, вариант — либо взять модель поменьше или сильнее квантованную, либо перейти на сервер с большим объёмом VRAM или с несколькими GPU.

Как выглядит правильная диагностика по шагам

Порядок действий, который отделяет реальную проблему от иллюзии:

  1. Перечитать полный лог запуска модели, а не только последнюю строку — найти явную запись о распределении слоёв между CPU и GPU.
  2. Проверить nvidia-smi (или rocm-smi) именно во время активной генерации, а не в простое — короткий тестовый запрос с параллельным watch -n 0.5 nvidia-smi в соседнем терминале.
  3. Убедиться, что параметр выгрузки слоёв (n-gpu-layers, num_gpu и аналоги в зависимости от движка) выставлен явно и осознанно, а не оставлен на волю автоопределения.
  4. Сверить версию установленного драйвера CUDA/ROCm с тем, под что собран используемый бинарник инференса, и убедиться, что сборка вообще содержит GPU-бэкенд.
  5. Прикинуть, укладывается ли модель с текущим квантованием и размером контекста в доступный объём VRAM, и при необходимости уменьшить контекст или взять модель с более агрессивным квантованием.
  6. Для Docker-развёртываний — отдельно проверить, что контейнер запущен с доступом к GPU и видит его изнутри (nvidia-smi внутри контейнера должен показывать карту).

Пример короткой проверочной таблицы, куда удобно свести результат диагностики:

ПроверкаОжидаемоКак проверить
Слои на GPUВсе или почти все слои моделиЛог запуска, строка про offload
Утилизация GPU при генерацииЗаметный рост при активном инференсеnvidia-smi во время запроса
VRAM занята процессомОщутимая доля объёма картыnvidia-smi, колонка Memory-Usage
Версия драйвера vs сборкаСовместимы (см. документацию движка)nvidia-smi (драйвер) + сборочные логи бинарника
Модель + контекст помещаются в VRAMДа, с запасомРазмер файла модели + оценка KV-кэша vs объём VRAM

Если после этой проверки станет ясно, что железа объективно не хватает под нужную конфигурацию модели и контекста, дальше вопрос уже не в диагностике, а в подборе сервера с более подходящей видеокартой — это отдельная и довольно частая развилка при переезде с локальной машины на выделенный сервер.

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Развернуть ИИ на сервере

Нужны сами нейросети для контента?

Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.

Частые вопросы

Почему в логе нет ошибки, если GPU не используется?

Потому что большинство движков инференса спроектированы так, чтобы не падать при проблемах с GPU-путём, а тихо продолжать работу на CPU — это осознанный компромисс в пользу отказоустойчивости сервиса, а не баг конкретного движка.

Как быстро понять, работает GPU или нет, без разбора логов?

Запустить nvidia-smi в режиме watch в соседнем терминале и одновременно отправить модели тестовый запрос — если утилизация во время генерации остаётся низкой, GPU практически не участвует в вычислениях.

Что делать, если n-gpu-layers выставлен правильно, а скорость всё равно низкая?

Проверить, не упирается ли конфигурация в объём VRAM при текущем размере контекста — уменьшить --ctx-size и посмотреть, изменится ли распределение слоёв в логе запуска.

Может ли проблема быть в самой модели, а не в конфигурации?

Да, но это можно проверить сравнением: если другая модель того же семейства и похожего размера на этом же сервере грузится на GPU полностью, а конкретный файл модели — нет, вероятная причина — повреждённый или нестандартно собранный файл, а не конфигурация движка.

Нужно ли переустанавливать драйвер при каждом обновлении движка инференса?

Не всегда, но при обновлении движка стоит свериться с его документацией на предмет минимальной поддерживаемой версии CUDA/ROCm — рассинхронизация версий одна из самых частых причин тихого отката на CPU.

Обсудить статью, задать вопрос или начать новую тему

Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.

Перейти в сообщество →