Как раздать одну GPU нескольким сервисам
Если на сервере одна видеокарта, а сервисов, которым она нужна, несколько — эмбеддинги для одного проекта, инференс LLM для другого, генерация изображений для третьего, — покупать под каждый отдельную карту обычно не нужно. Вопрос в том, как эти сервисы поделят одну и ту же GPU так, чтобы никто из них не остался без памяти и не отжал все вычисления у соседей в самый неподходящий момент. Разберём три уровня изоляции — от «пусть само разберётся» до жёсткого лимита — и как понять, кто на карте на самом деле ест ресурсы.
Содержание
Зачем вообще делить одну GPU, а не покупать несколько
Сценарий встречается чаще, чем кажется: небольшая команда держит на одном сервере API для эмбеддингов, отдельный контейнер с локальной LLM для внутреннего чат-бота и время от времени гоняет Stable Diffusion для генерации превью. Каждый из этих сервисов по отдельности использует карту не полностью — LLM на 7-8 млрд параметров в квантованном виде занимает несколько гигабайт видеопамяти, эмбеддинг-модель — и того меньше, а простаивающая карта — это просто выброшенные деньги.
Важно сразу разделить два разных вопроса, которые часто путают. Первый — можно ли отдать всю карту одной виртуальной машине целиком через VFIO/IOMMU (об этом отдельно есть статья про проброс GPU в виртуальную машину) — так вы получаете одну VM с прямым доступом к железу, но карта достаётся только ей одной. Второй вопрос — как раз наш: несколько независимых сервисов или процессов работают с одной и той же картой параллельно, без пробрасывания её в отдельные VM. Это принципиально другая задача, и решается она на уровне драйвера и фреймворков, а не гипервизора.
Вариант 1: несколько процессов без изоляции
Самый простой вариант — просто запустить несколько процессов инференса на одной карте и довериться тому, что драйвер NVIDIA сам разведёт их по памяти. Технически так и происходит: каждый процесс, который открывает CUDA-контекст, получает свой изолированный адресный диапазон видеопамяти, и один процесс физически не может прочитать данные другого. В этом смысле базовая изоляция есть всегда, и это не костыль, а штатное поведение драйвера.
Проблема в другом — в распределении вычислительных ресурсов. По умолчанию все процессы конкурируют за потоковые мультипроцессоры GPU через планировщик драйвера, и никаких гарантий по долям вычислений между ними нет. Если один сервис в моменте отправляет на карту большую пачку запросов, он может на несколько секунд практически монополизировать вычисления, и остальные процессы будут просто ждать своей очереди — даже если формально у них есть выделенная память и они технически "работают".
Для каких случаев это рабочий вариант:
- сервисы с умеренной и предсказуемой нагрузкой, суммарно укладывающиеся в объём видеопамяти карты;
- задачи, где кратковременная задержка ответа на несколько секунд не критична (внутренние инструменты, батчевая обработка);
- этап разработки и тестирования, когда важно быстро проверить, что несколько моделей вообще влезают на карту.
Проверить, что происходит, просто:
nvidia-smi
Команда покажет список процессов на карте, сколько памяти занял каждый, и суммарную загрузку вычислений. Если видите, что сервисы вместе не превышают объём памяти карты, а по загрузке компute почти всегда есть запас — вариант без специальной изоляции вполне рабочий, и городить сложную инфраструктуру ради него не нужно.
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Развернуть ИИ на сервереВариант 2: аппаратная нарезка карты на уровне драйвера
Для случаев, когда нужна не разделяемая, а по-настоящему гарантированная изоляция — не только по памяти, но и по доле вычислительных блоков карты, — у некоторых производителей профессиональных и датацентровых видеокарт есть технология аппаратного разбиения одной физической карты на несколько независимых "нарезок". Каждая такая нарезка получает собственный выделенный объём памяти и собственную долю вычислительных блоков с аппаратной гарантией — то есть один инстанс физически не может отобрать ресурсы у другого, даже если попытается.
Концептуально это похоже на разбиение диска на разделы, только применительно к вычислительным блокам и памяти GPU. Каждая нарезка с точки зрения операционной системы и драйвера выглядит как отдельное GPU-устройство — можно запустить в ней отдельный процесс или даже отдельный контейнер, и они не будут видеть друг друга и не будут конкурировать за ресурсы карты.
Важные оговорки, чтобы не разочароваться на практике:
- такая технология доступна не на всех картах — как правило, это старшие профессиональные и датацентровые модели, а не игровые и не большинство потребительских карт;
- количество и конфигурация нарезок обычно ограничены — нельзя нарезать карту на произвольное число частей произвольного размера, есть фиксированный набор профилей;
- настройка выполняется на уровне драйвера и требует прав администратора хоста, а после изменения конфигурации обычно нужна перезагрузка драйвера или самой карты;
- суммарная производительность нарезанной карты в задачах, которые упираются в вычисления, обычно ниже, чем у той же карты, работающей как единое целое — вы платите частью пиковой мощности за гарантию изоляции.
Если у вас в проекте несколько по-настоящему независимых нагрузок с жёсткими требованиями к предсказуемости (например, платный продакшен-сервис одного клиента и внутренний эксперимент другой команды на одной карте), и карта такую нарезку поддерживает — это самый надёжный вариант. Для большинства небольших проектов с несколькими своими же сервисами это избыточно, и хватает варианта 3.
Вариант 3: лимит памяти на уровне фреймворка
Практический средний вариант, который закрывает большинство реальных ситуаций — явно ограничить долю видеопамяти, которую может занять каждый процесс, средствами самого inference-фреймворка. По умолчанию многие фреймворки ведут себя жадно: PyTorch и TensorFlow при первом обращении к CUDA резервируют существенную часть свободной памяти карты "про запас", даже если реально модели нужно в разы меньше. Если так делают два процесса подряд, второй просто не сможет выделить память и упадёт с ошибкой нехватки памяти — хотя суммарно на карте места хватило бы всем.
Решение — задать явный лимит вместо того, чтобы разрешать фреймворку захватывать всё доступное.
В PyTorch лимит задаётся долей от общего объёма памяти карты:
import torch
# Разрешить процессу использовать не более 40% памяти карты
torch.cuda.set_per_process_memory_fraction(0.4, device=0)
В TensorFlow можно либо запретить предварительный полный захват памяти, либо явно ограничить объём:
import tensorflow as tf
gpus = tf.config.list_physical_devices('GPU')
tf.config.experimental.set_memory_growth(gpus[0], True) # расти по мере необходимости
# либо жёсткий лимит в мегабайтах:
tf.config.set_logical_device_configuration(
gpus[0],
[tf.config.LogicalDeviceConfiguration(memory_limit=4096)]
)
Для vLLM, который часто используют под инференс LLM, лимит задаётся параметром запуска сервера и означает долю памяти карты, которую движку разрешено занять под веса модели и KV-кэш:
vllm serve mistralai/Mistral-7B-Instruct-v0.3 --gpu-memory-utilization 0.5
Аналогичный параметр есть у llama.cpp и большинства серверов инференса, построенных поверх него — там обычно ограничивают не долю, а число слоёв модели, выгружаемых на GPU (--n-gpu-layers), что тоже косвенно управляет занимаемой памятью.
Логика во всех случаях одна: если на карте будут жить три сервиса, заранее прикиньте, сколько памяти реально нужно каждому под его модель плюс запас под пиковую нагрузку, и раздайте лимиты так, чтобы сумма не превышала объём карты с запасом в 10-15% на служебные нужды драйвера. Это не решает проблему с распределением вычислений между процессами (см. вариант 1), но полностью снимает риск, что один сервис случайно захватит всю память и обрушит остальные с ошибкой нехватки памяти при следующем перезапуске.
Как решить, что подходит именно вам
| Ситуация | Подходящий вариант |
|---|---|
| 2-3 своих сервиса с умеренной и предсказуемой нагрузкой | Вариант 1, без изоляции |
| Один сервис иногда "проседает" по задержке, когда работает сосед | Вариант 3, лимиты памяти на уровне фреймворка |
| Разные клиенты или команды на одной карте с требованием изоляции | Вариант 2, аппаратная нарезка (если карта поддерживает) |
| Нужна максимальная гибкость и минимум конфигурации | Вариант 1 → апгрейд до варианта 3 при первых проблемах |
На практике разумный путь — начать с варианта 1, посмотреть на реальную загрузку через мониторинг (следующий раздел) и добавить явные лимиты памяти, как только увидите первые признаки конкуренции за ресурсы. Аппаратную нарезку имеет смысл рассматривать отдельно, когда речь идёт о by design разделении между по-настоящему независимыми арендаторами, а не о трёх ваших же сервисах.
Мониторинг: кто на самом деле ест карту
Без регулярного контроля любое из решений выше — это гадание. Базовый и совершенно достаточный для старта инструмент — nvidia-smi:
# Разовый снимок: кто на карте, сколько памяти занял, какая загрузка compute
nvidia-smi
# Обновление каждую секунду — удобно смотреть в реальном времени
watch -n 1 nvidia-smi
# Только процессы и их память, без лишнего
nvidia-smi --query-compute-apps=pid,process_name,used_memory --format=csv
Для постоянного наблюдения удобнее nvidia-smi dmon — построчный вывод загрузки и температуры без перерисовки экрана, который легко пишется в лог:
nvidia-smi dmon -s um -d 5 >> /var/log/gpu-usage.log &
Если хочется более наглядной картины, чем текстовые таблицы, есть nvtop (по аналогии с htop, но для GPU) — показывает загрузку по каждому процессу и карте живым графиком в терминале:
sudo apt install nvtop
nvtop
Что именно смотреть регулярно:
- used_memory на процесс — растёт ли она со временем у конкретного сервиса (утечка памяти) или стабильна;
- утилизация compute по времени — если один процесс стабильно держит загрузку 90%+ и не спадает, а соседи параллельно жалуются на задержки, это явный признак монополизации вычислений из варианта 1;
- сумма used_memory всех процессов относительно общего объёма карты — если она регулярно подходит к границе, лимиты из варианта 3 нужно затягивать, а не расширять.
Если сервисы работают в контейнерах, полезно завести отдельный дашборд с экспортом метрик nvidia-smi в Prometheus (есть готовый DCGM-exporter от NVIDIA) — это избавляет от ручного захода по SSH каждый раз, когда кто-то жалуется на медленный ответ модели.
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Развернуть ИИ на сервереНужны сами нейросети для контента?
Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.
Частые вопросы
Можно ли делить одну GPU между Docker-контейнерами?
Да, через NVIDIA Container Toolkit каждому контейнеру пробрасывается доступ к карте, а дальше действуют те же принципы: без изоляции драйвер сам развезёт процессы по памяти, а лимиты задаются либо на уровне фреймворка внутри контейнера, либо через переменную NVIDIA_VISIBLE_DEVICES, если карт несколько.
Что будет, если превысить лимит памяти, заданный во фреймворке?
Процесс получит ошибку нехватки видеопамяти (CUDA out of memory) при попытке выделить больше отведённого — это управляемый сбой конкретного запроса, а не крах всей карты или соседних сервисов.
Обязательно ли использовать аппаратную нарезку карты для нескольких сервисов?
Нет, для большинства сценариев с собственными сервисами на одной карте достаточно лимитов памяти на уровне фреймворка — аппаратная нарезка оправдана в основном при разделении между разными клиентами с жёсткими требованиями к изоляции и на картах, которые её поддерживают.
Как понять, что пора переходить с варианта без изоляции на явные лимиты?
Как только мониторинг регулярно показывает, что один сервис держит загрузку compute близко к 100% продолжительное время, а другие процессы при этом заметно "тормозят" — это сигнал либо ставить лимиты, либо разносить нагрузку по расписанию.
Что делать, если сервисы всё равно не помещаются на одну карту?
Тогда речь уже не о разделении одной карты, а о выборе сервера с несколькими GPU или переносе части нагрузки на CPU для менее требовательных задач вроде расчёта эмбеддингов.
Обсудить статью, задать вопрос или начать новую тему
Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.
Перейти в сообщество →