Заменить TorchServe на Ray Serve DLC для запуска Qwen3-VL-2B в Amazon EKS можно через четыре слоя: GPU-образ с PyTorch, CUDA и Ray Serve, Python-обёртку модели, Kubernetes workload с запросом одного GPU и HTTP-проверку через kubectl и curl. TorchServe при этом меняется не одним контейнером: меняются точка входа приложения, жизненный цикл реплики, контракт endpoint и подход к масштабированию.
Базовая конфигурация с одной NVIDIA A10G подходит для проверки всей цепочки: pod получает GPU, контейнер видит CUDA, Ray Serve поднимает deployment, сервис принимает изображение с текстовым prompt и возвращает JSON-ответ. Такая схема не доказывает производительность под нагрузкой, отказоустойчивость или готовность к нескольким репликам. Для этого потребуются отдельные измерения, GPU-пул и, вероятно, KubeRay.
Как заменить TorchServe на Ray Serve DLC: короткий ответ
Практический путь выглядит так: выберите подтверждённый Ray Serve Deep Learning Container от AWS, добавьте тонкий слой с Python-кодом сервиса, передайте несекретные параметры через ConfigMap, запросите nvidia.com/gpu: 1 в Deployment и откройте порт Ray Serve через Kubernetes Service. Затем проверьте pod, логи, видимость GPU и HTTP-запрос с тестовым изображением.
Перед началом зафиксируйте версии текущего TorchServe, PyTorch, CUDA, Python и формата модели. Без этой точки сравнения миграция быстро превращается в поиск случайных несовместимостей между контейнером, драйвером GPU и кодом обработки изображений.
Что меняется после ухода от TorchServe
В TorchServe сервис обычно строится вокруг model archive, handler и конфигурации сервера. В Ray Serve центр тяжести смещается в Python deployment: класс или функция загружают модель, принимают HTTP-запросы и управляют обработкой данных. Kubernetes по-прежнему отвечает за размещение pod, сеть и ресурсы GPU.
| Зона ответственности | При использовании TorchServe | При использовании Ray Serve |
|---|---|---|
| Загрузка модели | Архив модели и handler | Инициализация Python deployment |
| HTTP-обработка | Предопределённые API сервера и handler | Метод deployment и контракт запроса |
| Параметры приложения | Конфигурация TorchServe | ConfigMap, переменные окружения, аргументы запуска |
| Реплики и GPU | Настройки сервера и Kubernetes workload | Ray Serve deployment, Ray-ресурсы и Kubernetes workload |
| Кластерное управление Ray | Не требуется для самого TorchServe | Для сложных сценариев применяется KubeRay |
Полную функциональную совместимость нужно проверять отдельно. Например, старый endpoint TorchServe может принимать multipart-форму, а новый Ray Serve endpoint - JSON с изображением в Base64. Клиентам в таком случае нужен адаптер или управляемый переход на новый контракт.
Как выглядит минимальная схема запуска
Клиент отправляет HTTP POST-запрос на Kubernetes Service. Service направляет трафик в pod. Ray Serve передаёт запрос в Python deployment, где Qwen3-VL-2B загружена в память GPU. Deployment декодирует изображение, собирает multimodal-вход, запускает генерацию и возвращает JSON с текстом.
Одна реплика должна получать один GPU последовательно и предсказуемо. Если разрешить несколько тяжёлых запросов одновременно, GPU-память может закончиться даже при успешной загрузке самой модели. Поэтому в демонстрационном запуске полезно ограничить число одновременных запросов до одного.
Почему TorchServe пересматривают перед новым GPU-инференсом
Решение о миграции стоит принимать по состоянию вашего стека, а не по общим заявлениям о технологиях. Поводом могут быть несовместимые версии PyTorch и CUDA, неудобный формат handler, отсутствие нужной observability, сложная упаковка model archive или необходимость встроить нестандартную обработку изображений и текста.
Какие факты нужно проверить перед миграцией
- Версию TorchServe и дату последнего доступного релиза в вашем окружении.
- Версии Python, PyTorch, CUDA и драйвера NVIDIA на GPU-узлах.
- Как текущий сервис получает веса модели и секреты доступа.
- Контракт существующего API: путь, заголовки, формат изображения, поля JSON и коды ошибок.
- Сколько GPU и памяти фактически потребляет одна реплика.
- Какие показатели нужны в production: p95 latency, число запросов, длина генерации, доступность сервиса.
Фраза о прекращении активной поддержки TorchServe сама по себе не заменяет технический аудит. Сначала нужно определить, мешает ли текущий сервер обновлять ML-стек, обслуживать модель или масштабировать сервис. Затем стоит оценить стоимость перехода, включая изменение API и повторное тестирование клиентов.
Когда Ray Serve DLC действительно подходит
Ray Serve DLC подходит команде, которой нужен Python-native сервис для модели, готовый GPU runtime и запуск в Kubernetes. Такой подход удобен, когда обработка запроса включает декодирование изображения, составление chat template, валидацию payload, нестандартные параметры генерации и постобработку результата.
Контейнер не гарантирует совместимость конкретной Qwen3-VL-2B с конкретным тегом образа. До деплоя проверьте поддерживаемые версии Ray Serve, PyTorch, CUDA и API модели. Не подставляйте URI образа, тег или имя репозитория из случайного примера: эти параметры должны совпасть с документацией выбранного DLC и вашей политикой доступа к реестру.
Ray Serve Deep Learning Containers от AWS: из чего состоит стек
Amazon EKS управляет Kubernetes-ресурсами. GPU-узел предоставляет драйвер, NVIDIA runtime и доступный ресурс nvidia.com/gpu. AWS Deep Learning Container даёт базовое ML-окружение. Ray Serve принимает запросы и направляет их в deployment. Python-код остаётся адаптером между HTTP-контрактом и API Qwen3-VL-2B.
Что даёт готовый GPU-контейнер
Готовый GPU-контейнер сокращает объём ручной сборки окружения: не нужно самостоятельно собирать базовый образ с CUDA, PyTorch и зависимостями Ray Serve. Это снижает число переменных при первом запуске, но не снимает задачу проверки совместимости.
Перед публикацией workload нужно сверить четыре точки: тег контейнера, драйвер GPU на узле, доступность CUDA внутри pod и поддержку нужной модели. Ошибка в любой из них выглядит одинаково для пользователя: endpoint не поднимается или инференс завершается до ответа.
Где заканчивается ответственность DLC
DLC не скачивает и не настраивает Qwen3-VL-2B автоматически. Команда отвечает за Python-код, доступ к весам, обработку ошибок, формат multimodal-запроса, ограничения размера изображения, readiness-поведение и журналирование. Секреты доступа к приватному реестру или хранилищу весов держат в Kubernetes Secret либо в управляемом хранилище секретов AWS, но не в ConfigMap.
Код приложения обычно добавляют поверх базового DLC тонким образом. Это не сборка всего ML-стека с нуля: Dockerfile копирует один или несколько Python-файлов, а зависимости PyTorch, CUDA и Ray Serve остаются в проверенном базовом образе.
FROM VERIFIED_RAY_SERVE_DLC_IMAGE:VERIFIED_TAG
COPY app.py /srv/app.py
WORKDIR /srv
CMD ['python', 'app.py']
Строки VERIFIED_RAY_SERVE_DLC_IMAGE и VERIFIED_TAG здесь намеренно оставлены шаблоном. Их нужно заменить после проверки доступного образа и его матрицы совместимости.
Ray Serve и KubeRay: разные уровни решения
Ray Serve обслуживает deployment и HTTP-маршруты. KubeRay управляет жизненным циклом Ray-кластера в Kubernetes. В одноподовом демонстрационном запуске Ray Serve может работать внутри одного workload. При нескольких GPU-узлах, autoscaling и раздельных head и worker pod нужен отдельный дизайн Ray-кластера, где KubeRay становится практичным инструментом.
Подготовка Amazon EKS и одной NVIDIA A10G
До запуска модели EKS должен уметь назначить pod на GPU-узел. Наличие A10G в описании node group ещё ничего не подтверждает: Kubernetes должен видеть ресурс nvidia.com/gpu, а выбранный контейнер должен видеть CUDA после старта.
Проверка GPU-узла до деплоя
Начните с состояния кластера и ресурсов узла:
kubectl get nodes
kubectl describe node GPU_NODE_NAME
kubectl get pods -A
В выводе describe node проверьте состояние Ready, разделы Capacity и Allocatable, наличие nvidia.com/gpu, labels GPU-пула и taints. Если GPU-ресурс не отображается, проблема находится ниже уровня Python-приложения: драйвер, NVIDIA device plugin или конфигурация узла не готовы.
После старта сервиса полезна проверка из контейнера:
kubectl exec -n ai-inference DEPLOYMENT_POD_NAME -- nvidia-smi
Команда сработает только если утилита присутствует в образе. Её отсутствие не доказывает отсутствие GPU, поэтому основными признаками остаются ресурсы pod, логи приложения и успешное создание CUDA-контекста.
Один GPU как базовая конфигурация
В Deployment задайте лимит nvidia.com/gpu: 1. Kubernetes назначит pod на узел, где есть свободный ускоритель. Сам лимит не выбирает модель GPU, поэтому A10G-пул нужно отделить label, node affinity или nodeSelector, соответствующим реальным labels в кластере.
Оставьте параметрами имя модели, порт, маршрут endpoint, длину генерации, лимиты CPU и RAM. Планирование GPU-памяти требует отдельной оценки: размер весов, dtype, размер изображения, длина prompt, KV-cache и число одновременных запросов меняют потребление памяти. Сравнение требований разных крупных Qwen-моделей есть в разборе Qwen 3.8 27B и запуска на RTX 5090, но переносить его цифры на Qwen3-VL-2B и A10G нельзя.
Доступ к образу и весам Qwen3-VL-2B
У деплоя есть две независимые точки загрузки. Первая: Kubernetes получает контейнер из реестра. Вторая: приложение получает веса и processor модели. Ошибка первой стадии приводит к ImagePullBackOff, ошибка второй обычно видна в логах уже после запуска контейнера.
- Для закрытого реестра настройте image pull credentials.
- Для закрытых весов передайте токен через Secret, а не через ConfigMap.
- Проверьте сетевой маршрут pod к хранилищу весов до запуска нагрузки.
- Определите, где будет храниться cache модели после перезапуска pod.
- Не помещайте большие веса в ConfigMap или образ приложения.
Запуск Qwen3-VL на GPU: Python-сервис для Ray Serve
Python deployment должен загрузить модель один раз, валидировать JSON, преобразовать Base64 в изображение, подготовить входы processor и вернуть структурированный ответ. Точный класс модели и правила формирования chat template зависят от опубликованного API Qwen3-VL-2B. Ниже приведён логический скелет на Transformers API: перед запуском подтвердите, что выбранный тег модели поддерживает AutoModelForVision2Seq.
Загрузка модели один раз на реплику
Загрузка весов внутри метода обработки HTTP-запроса приведёт к повторному чтению модели и резко ухудшит время ответа. Поместите processor и модель в __init__ deployment. Одна Ray Serve реплика в примере получает один GPU через ray_actor_options, а Kubernetes резервирует тот же ускоритель на уровне pod.
import base64
import io
import os
import ray
import torch
from PIL import Image
from ray import serve
from starlette.requests import Request
from starlette.responses import JSONResponse
from transformers import AutoModelForVision2Seq, AutoProcessor
MODEL_ID = os.environ['MODEL_ID']
MAX_NEW_TOKENS = int(os.getenv('MAX_NEW_TOKENS', '128'))
@serve.deployment(
ray_actor_options={'num_gpus': 1},
max_ongoing_requests=1,
)
class QwenVLService:
def __init__(self):
if not torch.cuda.is_available():
raise RuntimeError('CUDA GPU is required for this deployment')
self.device = 'cuda'
self.processor = AutoProcessor.from_pretrained(MODEL_ID)
self.model = AutoModelForVision2Seq.from_pretrained(
MODEL_ID,
torch_dtype='auto',
).to(self.device).eval()
async def __call__(self, request: Request):
try:
payload = await request.json()
prompt = payload.get('prompt', '').strip()
image_base64 = payload.get('image_base64', '')
if not prompt or not image_base64:
raise ValueError('prompt and image_base64 are required')
image_bytes = base64.b64decode(image_base64, validate=True)
image = Image.open(io.BytesIO(image_bytes)).convert('RGB')
inputs = self.processor(
text=prompt,
images=image,
return_tensors='pt',
)
inputs = {
name: value.to(self.device)
for name, value in inputs.items()
}
with torch.inference_mode():
output = self.model.generate(
**inputs,
max_new_tokens=MAX_NEW_TOKENS,
)
text = self.processor.batch_decode(
output,
skip_special_tokens=True,
)[0]
return JSONResponse({'text': text})
except ValueError as error:
return JSONResponse({'error': str(error)}, status_code=400)
Параметр torch_dtype='auto' не гарантирует, что модель поместится в память A10G. Он лишь передаёт выбор dtype загрузчику. Для production зафиксируйте dtype после проверки модели, доступной GPU-памяти и качества ответа.
Формат multimodal-запроса
Контракт endpoint в примере принимает два поля: prompt и image_base64. Base64 упрощает первый тест через curl и не требует публичного хранилища файлов. Минус очевиден: JSON становится крупнее исходного изображения, поэтому для production стоит установить лимит на размер payload и заранее решить, как сервис примет большие файлы.
Некоторые VLM требуют сообщения в специальном chat template, отдельные placeholders для изображения или обрезку входных токенов перед decode. Эти детали нельзя переносить между моделями наугад. Проверьте ожидаемый формат в документации именно Qwen3-VL-2B и вынесите подготовку input в отдельную функцию, если generic processor API не совпадает с моделью.
Публикация deployment через Ray Serve
HTTP-маршрут Ray Serve и Kubernetes Service находятся на разных уровнях. Ray Serve сопоставляет путь с deployment внутри приложения. Kubernetes Service направляет сетевой трафик на порт контейнера. В приложении нужны оба элемента.
import os
import threading
import ray
from ray import serve
from app import QwenVLService
if __name__ == '__main__':
ray.init()
serve.start(
http_options={
'host': '0.0.0.0',
'port': int(os.getenv('SERVE_PORT', '8000')),
}
)
serve.run(QwenVLService.bind(), route_prefix=os.getenv('ROUTE_PREFIX', '/infer'))
threading.Event().wait()
Проверьте параметры запуска по документации выбранной версии Ray Serve. Между версиями Ray менялись детали CLI и конфигурации HTTP proxy, поэтому команду из другого образа нельзя считать универсальной.
Kubernetes ConfigMap и деплоймент в Amazon EKS
Для минимального запуска нужны ConfigMap, Deployment и Service. ConfigMap хранит изменяемые несекретные параметры. Deployment запускает образ с Python-сервисом и запрашивает GPU. Service создаёт стабильную внутреннюю точку доступа для port-forward или других pod.
Что хранить в ConfigMap
В ConfigMap уместны идентификатор модели, HTTP-порт, route prefix, лимит генерации и режим журналирования. Пароли, токены доступа к весам, ключи реестра и cloud credentials держите в Secret.
apiVersion: v1
kind: ConfigMap
metadata:
name: qwen-vl-config
namespace: ai-inference
data:
MODEL_ID: 'REPLACE_WITH_VERIFIED_QWEN3_VL_2B_ID'
SERVE_PORT: '8000'
ROUTE_PREFIX: '/infer'
MAX_NEW_TOKENS: '128'
Идентификатор модели оставлен шаблоном специально. Его нельзя подменять непроверенным именем из чужого Dockerfile: от него зависят способ скачивания весов, архитектура processor и совместимость с Transformers.
Workload с запросом одного GPU
В примере Deployment использует образ, собранный поверх подтверждённого Ray Serve DLC. Label gpu-pool: a10g условный: замените его на label, который фактически установлен на A10G-узлах вашего EKS-кластера.
apiVersion: apps/v1
kind: Deployment
metadata:
name: qwen-vl
namespace: ai-inference
spec:
replicas: 1
selector:
matchLabels:
app: qwen-vl
template:
metadata:
labels:
app: qwen-vl
spec:
nodeSelector:
gpu-pool: a10g
imagePullSecrets:
- name: registry-credentials
containers:
- name: qwen-vl
image: YOUR_REGISTRY/qwen-vl-ray:REVISION
ports:
- containerPort: 8000
envFrom:
- configMapRef:
name: qwen-vl-config
env:
- name: MODEL_ACCESS_TOKEN
valueFrom:
secretKeyRef:
name: model-access
key: token
resources:
requests:
cpu: '2'
memory: '8Gi'
nvidia.com/gpu: 1
limits:
cpu: '4'
memory: '16Gi'
nvidia.com/gpu: 1
CPU и RAM в шаблоне служат стартовыми значениями, а не универсальными требованиями Qwen3-VL-2B. Увеличьте их после наблюдения за загрузкой весов, использованием RAM и обработкой изображений. Запрос и лимит GPU заданы одинаково, чтобы scheduler резервировал один ускоритель для pod.
Service и доступ к HTTP-интерфейсу
Для первой проверки достаточно Service типа ClusterIP. Он не публикует модель в интернет и позволяет использовать kubectl port-forward. Внешний LoadBalancer требует отдельного решения по аутентификации, ограничению запросов, TLS, журналированию и защите от загрузки слишком больших изображений.
apiVersion: v1
kind: Service
metadata:
name: qwen-vl
namespace: ai-inference
spec:
type: ClusterIP
selector:
app: qwen-vl
ports:
- name: http
port: 80
targetPort: 8000
Применяйте манифесты последовательно: сначала ConfigMap и Secret, затем Deployment, затем Service. У Service selector app: qwen-vl должен точно совпадать с label у pod, иначе endpoint останется пустым при полностью запущенном контейнере.
Проверка endpoint через kubectl и curl
Проверка должна идти по уровням: scheduler назначил GPU, образ скачался, процесс Ray Serve стартовал, deployment загрузил модель, Service нашёл pod, HTTP-запрос получил структурированный ответ. Такой порядок быстро отделяет инфраструктурный сбой от ошибки Python-кода или payload.
Состояние pod, события и логи
kubectl get pods -n ai-inference -w
kubectl describe pod POD_NAME -n ai-inference
kubectl logs deployment/qwen-vl -n ai-inference
kubectl get endpoints qwen-vl -n ai-inference
Статус Pending часто означает, что cluster не нашёл свободный GPU с подходящими labels или tolerations. ImagePullBackOff указывает на образ, тег или credentials. Ошибки импорта, загрузки весов и создания CUDA-контекста ищите в логах контейнера. Если Service не содержит endpoints, проверьте labels pod и selector Service.
Тестовый запрос curl
Откройте локальный туннель к Service:
kubectl port-forward svc/qwen-vl 8000:80 -n ai-inference
Подставьте Base64-код небольшой тестовой картинки в поле image_base64. Ответ вида {'text': '...'} подтверждает HTTP-маршрут и прохождение запроса через сервис. Содержательное качество ответа нужно оценивать отдельно на наборе целевых изображений и prompt.
curl -sS -X POST localhost:8000/infer \
-H 'Content-Type: application/json' \
-d '{"prompt":"Кратко опиши изображение","image_base64":"BASE64_IMAGE"}'
При пустом prompt или отсутствующем image_base64 пример сервиса возвращает HTTP 400. Ошибку декодирования изображения, лимит размера тела запроса и модельные ошибки стоит обработать отдельными ветками до выхода сервиса в production.
Если pod запущен, но инференс не работает
- Проверьте, что URL path в curl совпадает с
ROUTE_PREFIX. - Проверьте, что Service направляет на
targetPort: 8000. - Сверьте
MODEL_ID, токен доступа и логи загрузки весов. - Убедитесь, что внутри deployment доступна CUDA и Ray actor получил GPU.
- Проверьте размер изображения, корректность Base64 и обязательные поля JSON.
- Посмотрите потребление GPU-памяти во время первого запроса.
- Сверьте класс модели, processor и правила chat template с API Qwen3-VL-2B.
Ошибка после успешного старта pod часто связана с тем, что generic Transformers-код не совпадает с точным API vision-language модели. В таком случае замените только модельный адаптер, сохранив контракт HTTP, ConfigMap и Kubernetes Service. Это ограничивает объём изменений и упрощает повторную проверку.
Ограничения примера и переход к масштабированию через KubeRay
Один pod с одной NVIDIA A10G проверяет базовую архитектуру. Он не показывает пиковую пропускную способность, p95 latency, устойчивость к перезапускам, очередь запросов или поведение при одновременной генерации. Эти характеристики появляются только после нагрузочного теста с репрезентативными изображениями и prompt.
Что подтверждает демонстрационная конфигурация
Успешный сценарий подтверждает ограниченный набор фактов: EKS назначил GPU pod, контейнер стартовал, Ray Serve зарегистрировал deployment, приложение получило изображение и prompt, модель вернула HTTP-ответ. На этой базе можно переходить к тестированию качества, потребления памяти и ошибок запросов.
Не переносите этот результат на production без метрик. Длина prompt, разрешение изображения, число новых токенов и параллельные запросы меняют нагрузку сильнее, чем кажется на первом тестовом curl.
Почему несколько реплик требуют дополнительных GPU-ресурсов
Каждая GPU-реплика загружает собственную копию весов и занимает GPU-память. Увеличение replicas на одном A10G-узле не создаёт новые ускорители. Scheduler либо оставит лишние pod в состоянии Pending, либо разместит их только при наличии свободных GPU и допустимой конфигурации ресурсов.
Перед масштабированием определите профиль нагрузки: средний размер изображения, длину контекста, лимит генерации, допустимую очередь и требуемое число одновременных запросов. Полезно отдельно изучить, как память влияет на параллельный инференс в более крупных конфигурациях, например в разборе стека Qwen 3.8 27B на двух RTX 3090. Конкретные параметры той сборки не подходят для EKS автоматически, но хорошо показывают связь VRAM, RAM, хранилища и очереди запросов.
Когда нужен KubeRay и распределённый инференс
KubeRay нужен, когда Ray Serve выходит за пределы одного pod: появляются несколько worker-узлов, GPU-пулы, autoscaling, размещение реплик на разных машинах или распределённые вычисления. Он управляет RayCluster в Kubernetes, но не освобождает команду от проектирования сети, прав доступа, мониторинга, обновлений образов и бюджета GPU.
Для моделей, которые не помещаются на одном ускорителе, распределённый инференс добавляет новые ограничения: межузловую сеть, согласованность версий CUDA и NCCL, topology GPU, отказ одного worker и стоимость простаивающих ресурсов. Вопрос размещения весов и квантования тоже остаётся частью архитектуры. Более широкий взгляд на связь формата весов и памяти даёт разбор GLM-5.2 на 8× GB10.
Начните с одной реплики, зафиксируйте потребление памяти и поведение endpoint, затем добавляйте нагрузочные тесты. После появления измеримых требований можно выбрать размер GPU-пула, число реплик, правила autoscaling и схему Ray-кластера без догадок.