Simplifique inferência de modelos com o Ray Serve Deep Learning Container da AWS

O fim do suporte ao TorchServe e o que isso significa na prática

O TorchServe não está mais sendo mantido ativamente. O aviso oficial do projeto deixa claro: não há previsão de atualizações, correções de bugs, novos recursos ou patches de segurança, e vulnerabilidades podem ficar sem resposta.

Para equipes que hoje rodam inferência de modelos com TorchServe, isso significa duas consequências diretas: os patches de segurança param de chegar e as atualizações de compatibilidade com versões mais recentes do PyTorch e do CUDA também param. O time passa a ser responsável por toda a cadeia de dependências — escolher versões compatíveis na pilha de GPU, corrigir vulnerabilidades em cada camada e depurar falhas sutis quando qualquer componente sai de alinhamento. É um trabalho sem diferencial competitivo que atrasa a entrega de modelos sem agregar valor ao produto final.

O que são os AWS Deep Learning Containers e como o Ray Serve DLC se encaixa

Os AWS Deep Learning Containers (DLCs) já resolvem esse tipo de problema há algum tempo para cargas de trabalho de treinamento. São imagens Docker pré-construídas e otimizadas para desempenho que empacotam um framework, suas dependências e a pilha de GPU em uma combinação testada e com patches aplicados — pronta para uso imediato.

Com o lançamento do Ray Serve DLC, essa mesma abordagem chega agora à inferência. O resultado é um container construído especificamente para servir modelos atrás de um endpoint HTTP, mantido e testado pela AWS, com toda a pilha de inferência já montada.

Como o Ray Serve DLC é construído

A variante de CPU do Ray Serve DLC é baseada na imagem Amazon Linux 2023. Já a variante de GPU é construída sobre a imagem oficial da NVIDIA para Amazon Linux 2023, que inclui tanto a camada do sistema operacional quanto as bibliotecas de runtime do CUDA. Sobre essa base, o DLC adiciona:

  • O framework de deep learning PyTorch
  • A camada de serving do Ray Serve com FastAPI e Uvicorn
  • Utilitários comuns para cargas de trabalho de visão, áudio e multimodal
  • FFmpeg compilado com aceleração de hardware NVIDIA para pré-processamento de vídeo

Todos os componentes são validados e testados juntos antes de cada release, o que elimina o problema de versões incompatíveis entre o runtime do CUDA, o framework e a camada de serving. Patches de segurança são aplicados no momento do build.

O Ray Serve DLC é publicado como imagens separadas para o Amazon Elastic Kubernetes Service (EKS), para o Amazon Elastic Compute Cloud (EC2) e para o Amazon SageMaker — cada um com um entrypoint dedicado adequado ao contrato de serving daquele ambiente. Todos compartilham a mesma pilha e dependências subjacentes. Para a lista atual de tags de imagem disponíveis, consulte a página de imagens disponíveis do Ray DLC.

Compondo a aplicação Ray Serve

O DLC já entrega a pilha de inferência comum, então muitos modelos — incluindo o modelo Qwen3-VL-2B — rodam nele sem necessidade de uma imagem customizada. Para modelos que precisam de bibliotecas extras, é possível adicioná-las em camadas sobre a mesma base testada.

No exemplo apresentado pela AWS, a versão GPU do Ray Serve DLC é usada para servir o modelo vision-language Qwen3-VL-2B. A aplicação é injetada no container por meio de um ConfigMap, o que mantém o deploy flexível: é possível alterar o código de serving sem reconstruir a imagem. O DLC já fornece a pilha de GPU, PyTorch, Ray Serve e Transformers.

Escrevendo a aplicação de serving

Com o Ray Serve, um endpoint de modelo é uma classe Python decorada com @serve.deployment. O método __call__ trata as requisições HTTP e .bind() registra o deployment. Não há archiver de modelo, hierarquia de classes handler nem arquivo de configuração de propriedades.

Para quem vem do TorchServe, isso substitui o handler customizado, a etapa do torch-model-archiver e o arquivo config.properties.

O exemplo a seguir carrega o modelo vision-language Qwen3-VL-2B na GPU e o expõe como um endpoint HTTP. Quando uma requisição chega com uma URL de imagem e um prompt de texto, o modelo gera uma resposta em linguagem natural descrevendo ou respondendo perguntas sobre a imagem:

from ray import serve
from transformers import AutoModelForImageTextToText, AutoProcessor
import torch

@serve.deployment(ray_actor_options={"num_gpus": 1})
class QwenVLService:
    def __init__(self):
        model_name = "Qwen/Qwen3-VL-2B-Instruct"
        self.processor = AutoProcessor.from_pretrained(model_name)
        self.model = AutoModelForImageTextToText.from_pretrained(
            model_name, torch_dtype=torch.float16
        ).to("cuda")

    async def __call__(self, request):
        try:
            body = await request.json()
            image_url = body.get("image_url")
            prompt = body.get("prompt")
            messages = [{"role": "user", "content": [
                {"type": "image", "image": image_url},
                {"type": "text", "text": prompt},
            ]}]
            inputs = self.processor.apply_chat_template(
                messages,
                tokenize=True,
                add_generation_prompt=True,
                return_dict=True,
                return_tensors="pt"
            ).to("cuda")
            generated_ids = self.model.generate(**inputs, max_new_tokens=200)
            trimmed = [o[len(i):] for i, o in zip(inputs.input_ids, generated_ids)]
            output = self.processor.batch_decode(trimmed, skip_special_tokens=True)[0]
            return {"response": output}
        Except Exception:
            logger.exception("Qwen inference request failed")
            return {"error": "Unable to generate a response. Please try again later."}

app = QwenVLService.bind()

Toda a lógica de serving em qwen_serve.py é adicionada a um ConfigMap chamado qwen-serve-code. O decorator @serve.deployment com ray_actor_options={"num_gpus": 1} instrui o Ray a agendar esse deployment em um worker com uma GPU disponível. O modelo é carregado em float16 para caber nos 24 GB de VRAM disponíveis na GPU A10G usada no exemplo.

Deploy no Amazon EKS

O deploy é feito com o Ray Serve DLC junto ao ConfigMap qwen-serve-code. A arquitetura resultante é um cluster Amazon EKS com um único nó de GPU rodando um pod que serve o modelo via HTTP na porta 8000.

Figura 1: Arquitetura de inferência em nó único no Amazon EKS, com um pod de GPU servindo o modelo via HTTP na porta 8000. Imagem original — fonte: AWS

O deploy usa uma única instância g5.xlarge (uma GPU NVIDIA A10G, 24 GB de VRAM). É uma configuração de inferência em nó único: um pod, uma GPU, uma máquina. Para serving distribuído em múltiplos nós — paralelismo de modelo entre máquinas ou autoescala horizontal com múltiplas réplicas — seria necessário construir sobre essa base usando KubeRay para orquestrar workers do Ray entre nós.

O repositório disponibilizado pela AWS inclui scripts que automatizam a configuração da infraestrutura:

  • deploy_cluster.sh: provisiona um cluster EKS usando eksctl, com rede de Nuvem Privada Virtual (VPC), um provedor OIDC para autenticação de pods baseada em Gerenciamento de Identidade e Acesso (IAM) e add-ons essenciais do cluster.
  • deploy_node_group.sh: adiciona um grupo de nós de GPU gerenciado com uma única instância g5.xlarge rotulada como role=gpu-worker.
  • deploy_ray_cluster.sh: aplica o ConfigMap com o qwen_serve.py, faz o deploy de um manifesto Kubernetes que agenda o pod no nó de GPU e inicia o Ray Serve na porta 8000.

Após executar os três scripts, é possível verificar que o pod está rodando e a GPU está alocada:

kubectl get pods -n inference
kubectl describe pod -n inference -l app=ray-serve | grep "nvidia.com/gpu"

Com o pod reportando como pronto, basta fazer o port-forward para testá-lo:

kubectl port-forward -n inference deploy/ray-cluster 8000:8000

Em outro terminal, é possível enviar uma requisição pedindo ao modelo Qwen3-VL-2B que descreva uma imagem:

curl -X POST http://127.0.0.1:8000/ \
  -H "Content-Type: application/json" \
  -d '{
    "image_url": "https://s3.amazonaws.com/model-server/inputs/kitten.jpg",
    "prompt": "Describe this image briefly."
  }'

A resposta será um JSON com a descrição gerada pelo modelo. Para confirmar que a inferência está rodando na GPU:

kubectl exec -n inference deploy/ray-cluster -- nvidia-smi

Observação: o pod reporta como pronto um ou dois minutos antes do Ray Serve começar a responder, porque o modelo ainda está sendo carregado. Se a primeira requisição for recusada, basta aguardar um minuto e tentar novamente.

Limpeza dos recursos

Para evitar cobranças contínuas, os recursos devem ser removidos na ordem inversa:

./delete_ray_cluster.sh  # Remove o deploy do Ray Serve
./delete_node_group.sh   # Remove o grupo de nós de GPU
./delete_cluster.sh      # Exclui o cluster EKS

Conclusão e próximos passos

Ao final desse processo, o resultado é um endpoint de inferência funcional — sem precisar manter compatibilidade com CUDA manualmente, sem escrever boilerplate de handler do TorchServe e sem montar um Dockerfile de múltiplos estágios que costura a pilha de GPU do zero. É exatamente para isso que o Ray Serve DLC foi projetado: oferecer um container com suporte e pré-testado para que as equipes possam focar no código do modelo, não na manutenção de infraestrutura.

Para equipes que ainda estão no TorchServe, esse é um caminho de migração sólido. O DLC elimina o problema de versões incompatíveis, simplifica upgrades para uma simples troca de tag e fornece patches de segurança regulares gerenciados pela AWS. Para cargas de trabalho que precisam escalar além de um único nó, o KubeRay estende essa mesma base para serving distribuído em múltiplos nós.

Para começar, acesse o exemplo de código completo com o deploy end-to-end. Para navegar por todas as imagens DLC disponíveis, incluindo variantes de CPU e outros frameworks, consulte a referência de AWS Deep Learning Containers.

Fonte

Simplify and support your TorchServe workloads using Ray Serve Deep Learning Containers (https://aws.amazon.com/blogs/machine-learning/simplify-and-support-your-torchserve-workloads-using-ray-serve-deep-learning-containers/)

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *