- BrainTools - https://www.braintools.ru -

Всем привет! В этой статье я расскажу, как мы запускаем большие языковые модели на Kubernetes-платформе Nova AI. Разобьем материал на две части: сначала посмотрим, с помощью чего это реализовано (архитектура и компоненты), а затем — что это позволяет делать (сценарии использования и практические кейсы).
Платформа логически разделена на два уровня: инфраструктурный и софтверный.
Инфраструктурный уровень отвечает за компоненты, которые обеспечивают работу с устройствами. В данном случае речь идет о GPU — видеокартах. Компоненты этого уровня позволяют использовать видеокарты внутри подов для выполнения задач. В текущем релизе это NVIDIA GPU Operator, и уровень будет пополняться в дальнейшем.
Софтварный уровень — это весь софт, все продукты и компоненты, которые покрывают ML-цикл. В текущем релизе это:
JupyterHub — среда разработки
Apache Airflow — автоматизация задач
MLflow — трекинг экспериментов
KServe — inference моделей
Kuberay — распределенные вычисления
Для начала работы нужен Kubernetes-кластер Nova версии 7.3.0 и выше, а также два манифеста — по одному для каждого уровня.
Этот манифест описывает компоненты для взаимодействия с физическим оборудованием:
apiVersion: apps.nova-platform.io/v1alpha1
kind: MLInfrastructure
metadata:
name: nova-ml-infrastructure
spec:
nvidiaGpuOperator:
mig:
enabled: true
strategy: single
configRef: mig-config
timeSlicing:
enabled: true
configRef: time-slicing-config
После применения манифеста через kubectl apply Nova AI разворачивает все необходимые компоненты для взаимодействия с GPU.
Софтварный манифест сложнее, потому что содержит больше компонентов. Ключевой принцип — модульность: платформа не навязывает лишнего. Если нужен только Airflow для сценариев автоматизации, можно поставить только этот компонент:
apiVersion: apps.nova-platform.io/v1alpha1
kind: MLCluster
metadata:
name: nova-ml-cluster
spec:
mlBaseDomain: "apps.test.platform"
storageClass: longhorn-storage-single-replica
jupyterHub:
enabled: true
airFlow:
enabled: true
logVolumeSize: 5G
mlFlow:
enabled: true
kubeRay:
enabled: true
kserve:
enabled: true
postgreSQL:
enabled: true
minIO:
enabled: true
driveCount: 1
driveSize: 25Gi
Остальные компоненты не будут развернуты и не будут загружать лишние ресурсы.
Второй важный принцип — использование внешних сервисов. Некоторые компоненты требуют базу данных для хранения метаданных и S3 для хранения артефактов. Платформа не навязывает развертывание всего этого — если у заказчика есть PaaS в облаке, можно использовать внешние сервисы:
externalDatabase:
enabled: true
host: "psql-postgresql.nova-postgresql.svc"
port: 5432
database: mlflow
userDatabase: mlflow_auth
starVaultUserSecretPath: "nova-secrets/data/credentials/external-integrations#pg_password"
starVaultPassSecretPath: "nova-secrets/data/credentials/external-integrations#pg_password"
externalS3:
enabled: true
host: "minio.nova-minio.svc"
port: 443
bucketRegion: "ru-nova-1"
bucket: "ml"
starVaultUserSecretPath: "nova-secrets/data/credentials/external-integrations#root_user"
starVaultPassSecretPath: "nova-secrets/data/credentials/external-integrations#root_password"
Также можно переопределять storage-классы, чтобы не завязываться на одном типе хранилища.
При полной конфигурации развертывание всей платформы на развернутом кластере занимает от 7 до 15 минут.
Кстати, в этом же манифесте представлена интеграция одного из ML-компонентов с StarVault [2] – российским хранилищем секретов, которое также разрабатываем мы :)
Это компонент инфраструктурного уровня, который обеспечивает работу с видеокартами из подов. Пользователь может написать свой сервис, использующий GPU для обучения [3], запустить его, указать в описании deployment, что хочет использовать GPU — и GPU автоматически туда подтянется.
GPU Operator состоит из нескольких подкомпонентов: Device Plugins, Runtimes, модуль мониторинга, автоматическое обновление драйверов и гибкая конфигурация механизмов взаимодействия с GPU.
Ключевой функционал оператора — возможность делить GPU на более атомарные юниты. Это решает важную проблему: когда мы просто работаем с GPU без деления, у нас есть одна GPU в worker-ноде, и мы можем привязать к ней только один под. Еще хуже, когда легкая модель откусывает 10% ресурсов видеокарты, а 90% простаивает.
Time Slicing — более старая технология, которая делит GPU на виртуальные слайсы. Kubernetes видит их не как одно устройство, а как несколько. Минусы: псевдопараллелизм (задачи не выполняются параллельно) и отсутствие изоляции по видеопамяти — если один под перегрузит память [4], упадут обе нагрузки.
MIG (Multi-Instance GPU) работает начиная с архитектуры Ampere (примерно с видеокарт A30). Это полноценные независимые инстансы — виртуальные микро-GPU с полной изоляцией по памяти и настоящим параллелизмом. Задачи выполняются каждая в своем адресном пространстве и не затрагивают друг друга.
Что касается объединения нескольких GPU в единое пространство — это делается на другом уровне: на уровне железа через NVLink или на уровне софта, когда в под прокидываются две видеокарты и софт внутри распределяет задачи. Для распределенных вычислений между серверами есть Kuberay.
Многопользовательская среда разработки. Дает единую точку входа, где пользователи могут заказывать ноутбуки нужного размера. Один пользователь может создать несколько ноутбуков, решать в них задачи и удалять, когда работа закончена.
Интеграция с StarVault обеспечивает единый механизм аутентификации. Сам ноутбук — это под внутри Kubernetes. В планах вынести конфигурацию ресурсов на сторону пользователя: выбор CPU, RAM и количества GPU через интерфейс.
Универсальный компонент для автоматизации задач — применим не только в ML/AI, но и в BI-системах и любых сферах, требующих автоматизации. Позволяет писать задачи в Python-скриптах, а Airflow контролирует их выполнение по расписанию, по триггеру или вручную.
Процессы описываются в DAG (направленных ациклических графах). DAG — это pipeline, в котором описываются задачи. Когда наступает время выполнения, автоматически запускается под внутри Kubernetes, задача выполняется, результат возвращается.
Все DAG’и — это клон Git-репозитория в Gitea. Пользователь пишет DAG в своем репозитории, делает commit, он прилетает в Gitea, и Airflow автоматически обновляет DAG из этого репозитория.
Сервис для трекинга метрик моделей и экспериментов. Нужен, чтобы пользователи не потерялись в том, что они наделали — при обучении моделей возникает огромное количество вариаций параметров, десятки экспериментов с разными параметрами и эпохами обучения.
MLflow позволяет хранить все в едином месте: трекинг метрик и экспериментов, хранение и версионирование моделей, упаковку модели в оболочку для деплоя. Эксперимент — это один инстанс обучения модели. Три раза запустили обучение — три эксперимента. В каждом можно посмотреть метрики, конфигурацию, прогрессию метрик в разрезе эпох.
Можно сравнивать эксперименты между собой и тегировать их. Например, тег success: true можно прикрутить к автоматизации в Airflow: Airflow наблюдает за тегами, видит, что появились модели с тегом success, и автоматически деплоит их в Production.
MLflow позволяет складывать артефакты в S3-хранилище: файлы модели для деплоя, графики (например, Confusion Matrix), логи и визуализации.
Компонент для inference — вывода моделей в продакшн. Как показывает практика, в России компании только идут к тому, чтобы обучать и дообучать свои модели. Пока больший процент спроса именно на inference — взаимодействие с готовыми моделями, которые запихнули в контейнеры.
KServe — стандарт в отрасли, если речь идет про промышленный инференс. Компонент является универсальным сервисом для деплоя почти любой модели. Работает через унифицированный интерфейс: заполняете манифест, указываете модель, откуда ее взять, и выбираете inference backend.
Из коробки KServe поставляет огромное количество inference-бэкендов: vLLM (для больших языковых моделей), ONNX (универсальный формат), PyTorch, TensorFlow, Triton Inference Server от NVIDIA и другие. За счет этого можно инферить практически любую модель. Например, большие языковые модели инферятся на vLLM, но vLLM не поддерживает ряд embedder-моделей — в этом случае можно выбрать ONNX. Весь прикол в вариативности.
Помимо стандартных бэкендов, KServe позволяет конфигурировать дополнительные. Если понадобится что-то специфичное, можно написать свой адаптер. Доступные бэкенды можно посмотреть через kubectl get clusterservingruntimes.
Что касается безопасности — это пользовательская история. Можно использовать OAuth2 Proxy для авторизации, ролевую модель для ограничения доступа, Ingress с аннотациями. Если нужна кастомизация пода (например, sidecar-контейнер с OAuth2 Proxy), это можно сделать в манифесте InferenceService.
Компонент для распределенных вычислений. Kuberay позволяет создавать кластеры внутри кластеров. Под капотом: в основе лежит Kubernetes, внутри которого создается Ray Cluster с Head-нодами (следят за выполнением задач) и Worker-нодами (выполняют задачи).
Задачи могут шардироваться и разделяться на несколько частей, которые отправляются на разные worker-ноды и выполняются параллельно. Kuberay особенно полезен для распределенного обучения моделей, обработки больших объемов данных, параллельного inference и задач, которые можно разбить на независимые части. Если GPU находятся на разных серверах, Kuberay распределяет обучение модели между ними.
Это модули, которые нужны для функционирования основных компонентов. Можно использовать как внешние сервисы (PaaS в облаке), так и внутренние (развернутые в Kubernetes). Если пользователь не хочет заморачиваться, платформа автоматически поднимает необходимые компоненты.
PostgreSQL хранит метаданные сервисов: какой ноутбук создан для какого пользователя в JupyterHub, информация о DAG’ах в Airflow, информация об экспериментах в MLflow. Это только метаданные, не датасеты и не данные моделей. PostgreSQL выбран как единственная база данных, которая поддерживается всеми компонентами.
Gitea — внутренний Git-сервис. Хранит DAG’и для Airflow, скрипты обучения и т.д. Пользователь делает commit в Gitea, и Airflow автоматически подтягивает изменения. Можно использовать внешний Git.
MinIO — S3-совместимое хранилище для артефактов. Хранит файлы моделей из MLflow, артефакты экспериментов, веса моделей. MLflow использует MinIO как основное хранилище. Можно использовать внешний S3.
Теперь давайте разберем реализацию основных задач, с которыми сталкиваются компании, желающие уверенно начать и масштабировать количество работающих LLM и ML-пайплайнов.
Задача: Быстро задеплоить модель для общения с ней.
Манифест описывается согласно стандартной документации KServe:
apiVersion: serving.kserve.io/v1beta1
kind: InferenceService
metadata:
name: qwen-model
namespace: nova
spec:
predictor:
model:
modelFormat:
name: huggingface # Используем Hugging Face backend
storageUri: "hf://Qwen/Qwen2.5-1.5B" # Модель Qwen 2.5 на 1.5B параметров
resources:
limits:
nvidia.com/gpu: 1 # Запрашиваем 1 GPU
Указываем backend (Hugging Face, под капотом использует vLLM), модель (Qwen 2.5 на 1.5 миллиарда параметров) и ресурсы (1 GPU). Применяем манифест через kubectl apply.
После этого KServe подхватывает манифест, начинает разворачивать под с моделью, скачивает образ контейнера и модель, загружает модель в видеопамять. Время развертывания — примерно 4 минуты. Мир машинного обучения — это про ожидание: образы весят много, модели весят много.
Для оптимизации времени развертывания можно складывать файлы моделей в PVC и тянуть их напрямую из хранилища внутри Kubernetes:
spec:
predictor:
model:
storageUri: "pvc://model-storage/qwen-2.5" # Из PVC
После развертывания можно отправить запрос к модели:
curl -X POST http://qwen-model.nova.svc.cluster.local/v1/completions
-H "Content-Type: application/json"
-d '{
"prompt": "Как варить гречку?",
"max_tokens": 100
}'
Если модель не поднимается на GPU T4 с ошибкой CUDA out of memory — T4 слабенькая для этой модели. Решение: взять более мощную карту или урезать модель, изменив параметр dtype:
spec:
predictor:
model:
args:
- --dtype=float16 # Вместо float32
Преимущества KServe: универсальность (поддержка множества бэкендов), скорость (быстро описали манифест — быстро применили — модель быстро поднялась), гибкость (выбор бэкенда под задачу). Минус: нестандартные endpoint’ы, не всегда совпадают с OpenAI API. Доступ к модели можно вытащить наружу через Ingress или NodePort, это обычный HTTP API, можно настроить OAuth2 Proxy для аутентификации.
После запуска кстати можно кстати поднять в нашей же платформе тот же OpenWebUI и подключить к ней LLM, далее опубликовать через ingress – и вот у вас уже безопасная LLM, доступная для всех сотрудников компании, можно отправлять любые конфиденциальные данные в нее и не переживать за их сохранность.
Задача: Развернуть модель распределенно. Это когда часть LLM выполняется на GPU одного узла, а вторая часть – на другом GPU второго узла. Это стандартный способ доутилизировать оставшиеся ресурсы GPU нашего кластера, чтобы выполнить больше задач. Покупать GPU на десятки миллионов ради 10% загрузки — сомнительное удовольствие.
Схема похожа на KServe, но есть принципиальные отличия. Вместо одного пода мы описываем кластер внутри кластера, в котором все будет работать.
Описание Ray Cluster:
apiVersion: ray.io/v1
kind: RayCluster
metadata:
name: ray-cluster
namespace: nova
spec:
# Конфигурация Head-ноды
headGroupSpec:
rayStartParams:
dashboard-host: '0.0.0.0'
template:
spec:
containers:
- name: ray-head
image: rayproject/ray:latest
resources:
limits:
cpu: "2"
memory: "4Gi"
# Конфигурация Worker-нод
workerGroupSpecs:
- replicas: 3
minReplicas: 1
maxReplicas: 5
groupName: gpu-workers
rayStartParams: {}
template:
spec:
containers:
- name: ray-worker
image: rayproject/ray:latest
resources:
limits:
nvidia.com/gpu: 1
cpu: "4"
memory: "8Gi"
Мы описываем Head-ноду (управляющий узел кластера) и Worker-ноды (рабочие узлы с GPU).
Описание RayService для модели:
apiVersion: ray.io/v1
kind: RayService
metadata:
name: llm-service
namespace: nova
spec:
serviceUnhealthySecondThreshold: 900
deploymentUnhealthySecondThreshold: 300
serveConfigV2: |
applications:
- name: llm
import_path: serve_model:deployment
runtime_env:
working_dir: "https://github.com/example/model-serving.git"
deployments:
- name: LLMDeployment
num_replicas: 3
ray_actor_options:
num_gpus: 1
Создается сервис на базе Ray Cluster, модель разворачивается на нескольких worker-нодах, запросы распределяются между репликами.
Преимущества Kuberay: распределенные вычисления (задачи распределяются между несколькими GPU), масштабируемость (можно добавлять/убирать worker-ноды), отказоустойчивость (если один worker падает, задачи перераспределяются), эффективное использование ресурсов (GPU на разных серверах работают как единое целое).
Когда использовать Kuberay? Большие модели, которые не помещаются на одну GPU. Высоконагруженные сервисы с большим количеством запросов. Распределенное обучение моделей. Параллельная обработка данных.
Задача: Автоматизировать процесс обучения, трекинга и деплоя модели.
Архитектура решения: Gitea (DAG код) → Airflow (Обучение) → MLflow (Трекинг) → KServe (Deploy).
Написание DAG для обучения:
from airflow import DAG
from airflow.operators.python import PythonOperator
from datetime import datetime
import mlflow
def train_model():
"""Функция обучения модели"""
import torch
from transformers import AutoModelForSequenceClassification, AutoTokenizer
# Начинаем MLflow эксперимент
mlflow.start_run()
# Параметры обучения
params = {
'learning_rate': 0.001,
'epochs': 10,
'batch_size': 32
}
mlflow.log_params(params)
# Обучение модели (упрощенно)
model = AutoModelForSequenceClassification.from_pretrained('bert-base-uncased')
# Логируем метрики
for epoch in range(params['epochs']):
# ... процесс обучения ...
train_loss = 0.5 - epoch 0.02 # Пример
accuracy = 0.7 + epoch 0.02 # Пример
mlflow.log_metric('train_loss', train_loss, step=epoch)
mlflow.log_metric('accuracy', accuracy, step=epoch)
# Сохраняем модель
mlflow.pytorch.log_model(model, "model")
# Добавляем тег успешности
mlflow.set_tag("success", "true")
mlflow.end_run()
def check_and_deploy():
"""Проверяем успешные модели и деплоим"""
# Ищем эксперименты с тегом success=true
experiments = mlflow.search_runs(filter_string="tags.success='true'")
if not experiments.empty:
# Берем последний успешный эксперимент
latest_run = experiments.iloc[0]
model_uri = f"runs:/{latest_run.run_id}/model"
# Деплоим через KServe (применяем манифест)
# ... код деплоя ...
print(f"Deployed model from run {latest_run.run_id}")
# Определяем DAG
with DAG(
'ml_training_pipeline',
start_date=datetime(2024, 1, 1),
schedule_interval='@daily', # Каждый день
catchup=False
) as dag:
train_task = PythonOperator(
task_id='train_model',
python_callable=train_model
)
deploy_task = PythonOperator(
task_id='check_and_deploy',
python_callable=check_and_deploy
)
# Определяем порядок выполнения
train_task >> deploy_task
После написания DAG делаем коммит в Gitea. Airflow автоматически синхронизируется с Gitea, обнаруживает новый DAG и добавляет его в список доступных пайплайнов.
В интерфейсе Airflow видим новый DAG ml_training_pipeline, можем запустить вручную или дождаться расписания. В интерфейсе MLflow видим новый эксперимент, можем посмотреть метрики в реальном времени, сравнить с предыдущими, скачать артефакты.
Если модель успешно обучилась (тег success=true), задача check_and_deploy находит ее в MLflow и автоматически создает InferenceService в KServe — модель деплоится в продакшн.
Преимущества: полная автоматизация (от обучения до деплоя), прозрачность (все метрики и артефакты в одном месте), воспроизводимость (можно повторить любой эксперимент), версионирование (все версии моделей сохранены), безопасность (только успешные модели попадают в продакшн).
Задача: Разработать модель в ноутбуке, обучить ее и задеплоить.
Переходим в JupyterHub через роутдэшборд, аутентифицируемся, заказываем ноутбук с нужными ресурсами (CPU: 4 cores, RAM: 16 GB, GPU: 1x NVIDIA T4, Storage: 50 GB).
В ноутбуке пишем код:
# Установка зависимостей
!pip install transformers torch mlflow
# Импорты
import torch
from transformers import AutoModelForSequenceClassification, AutoTokenizer
import mlflow
# Настройка MLflow
mlflow.set_tracking_uri("http://mlflow.nova.svc.cluster.local")
mlflow.set_experiment("sentiment-analysis")
# Загрузка данных
# ... код загрузки данных ...
# Обучение модели
with mlflow.start_run():
model = AutoModelForSequenceClassification.from_pretrained('bert-base-uncased')
tokenizer = AutoTokenizer.from_pretrained('bert-base-uncased')
# Параметры
params = {'learning_rate': 0.001, 'epochs': 5}
mlflow.log_params(params)
# Обучение
for epoch in range(params['epochs']):
# ... код обучения ...
mlflow.log_metric('loss', loss, step=epoch)
mlflow.log_metric('accuracy', accuracy, step=epoch)
# Сохранение модели
mlflow.pytorch.log_model(model, "model")
# Сохранение токенизатора
tokenizer.save_pretrained("./tokenizer")
mlflow.log_artifacts("./tokenizer", artifact_path="tokenizer")
Сохраняем модель в PVC для быстрого доступа:
model.save_pretrained("/mnt/models/sentiment-analysis")
tokenizer.save_pretrained("/mnt/models/sentiment-analysis")
Создаем манифест для деплоя:
apiVersion: serving.kserve.io/v1beta1
kind: InferenceService
metadata:
name: sentiment-analysis
namespace: nova
spec:
predictor:
pytorch:
storageUri: "pvc://model-storage/sentiment-analysis"
resources:
limits:
nvidia.com/gpu: 1
memory: "4Gi"
requests:
cpu: "1"
memory: "2Gi"
Применяем манифест и тестируем:
# Применяем манифест
kubectl apply -f sentiment-analysis.yaml
# Ждем готовности
kubectl wait --for=condition=Ready inferenceservice/sentiment-analysis -n nova
# Тестируем
curl -X POST http://sentiment-analysis.nova.svc.cluster.local/v1/models/sentiment-analysis:predict
-H "Content-Type: application/json"
-d '{
"instances": [
{"text": "This product is amazing!"}
]
}'
Преимущества подхода: быстрая итерация (разработка в привычной среде Jupyter), доступ к GPU (можно обучать модели прямо в ноутбуке), интеграция с MLflow (автоматический трекинг экспериментов), простой деплой (из ноутбука сразу в продакшн).
Nova AI — это платформа, которая покрывает полный цикл работы с ML/LLM-моделями и ресурсами GPU: от разработки в JupyterHub до деплоя через KServe, с автоматизацией через Airflow и трекингом экспериментов в MLflow.
Ключевые преимущества:
модульность (устанавливайте только то, что нужно)
гибкость (используйте внешние или внутренние сервисы)
знакомые инструменты (компоненты, с которыми работают ML-инженеры)
полный цикл ML (от разработки до продакшна)
простота входа (развертывание продуктивного кластера за день и начало работы)
масштабируемость (от одного GPU до распределенных кластеров).
Пишите в комментариях, что вам интересно узнать из нашей практики еще, постараюсь раскрыть это в следующих статьях.
Автор: NVekesser
Источник [5]
Сайт-источник BrainTools: https://www.braintools.ru
Путь до страницы источника: https://www.braintools.ru/article/25283
URLs in this post:
[1] Image: https://sourcecraft.dev/
[2] StarVault: https://www.orionsoft.ru/starvault
[3] обучения: http://www.braintools.ru/article/5125
[4] память: http://www.braintools.ru/article/4140
[5] Источник: https://habr.com/ru/companies/orion_soft/articles/993488/?utm_campaign=993488&utm_source=habrahabr&utm_medium=rss
Нажмите здесь для печати.