Pular para o conteúdo
CloudIntermediário

Serverless ou containers para cargas de IA: como decidir

Serverless ou containers para IA? Veja quando cada um compensa, os limites do Lambda com modelos, o cold start e como montar uma arquitetura híbrida com fila.

Por Equipe IAUAI Estudos · 15 de julho de 2026 · 9 min de leitura

Nesta página

Ao final deste artigo você vai conseguir decidir se uma carga de IA deve rodar em serverless, em containers ou nos dois, e vai ter um esqueleto de arquitetura híbrida com API leve, fila e workers. A primeira pergunta, porém, vem antes de qualquer tecnologia: o modelo roda na sua infraestrutura ou você chama o modelo de um provedor por API?

Primeiro: hospedar o modelo ou chamar uma API

Se a sua aplicação só chama a API de um modelo hospedado por terceiros, o grosso do trabalho (inferência, GPU, carga de pesos) não está com você. Seu código só monta o prompt, espera a resposta e trata o resultado. Nesse caso serverless costuma ser excelente: a função passa a maior parte do tempo esperando a rede, o tráfego irregular não custa nada parado e não há modelo para carregar.

A discussão interessante começa quando o modelo roda por sua conta, seja um modelo aberto, um modelo ajustado por você ou um modelo pequeno de classificação. Aí entram o peso dos arquivos, a memória, a GPU e o tempo de carga. É esse caso que o restante do artigo cobre.

Cold start: o problema do serverless com modelos

Em serverless, quando não há uma instância pronta, o provedor precisa criar um ambiente, baixar o código ou a imagem e inicializar o runtime. Isso é o cold start. Para uma função pequena, são tipicamente frações de segundo a poucos segundos. Para um modelo, o tempo se soma a outras etapas:

  1. Criar o ambiente de execução.
  2. Trazer os pesos do modelo de um armazenamento de objetos (S3, por exemplo).
  3. Carregar os pesos na memória (ou na GPU).
  4. Inicializar o framework.

Os pesos de um modelo de poucos bilhões de parâmetros ocupam alguns gigabytes mesmo quantizados, e a etapa 2 e a 3 dominam a conta. O tempo total depende do tamanho do modelo, da rede e do disco, e só medindo no seu ambiente você sabe. O padrão do problema é o mesmo em qualquer lugar: a primeira requisição depois de um período ocioso fica muito mais lenta que as seguintes, e o cliente sente.

Há mitigações, como manter instâncias aquecidas (provisioned concurrency no Lambda, instâncias mínimas no Cloud Run), mas elas trazem de volta o custo de ter algo ligado o tempo todo, que era justamente o que o serverless prometia evitar.

Limites técnicos que pesam em IA

No AWS Lambda, os limites documentados que mais afetam IA são:

  1. Timeout máximo de 15 minutos por invocação.
  2. Memória de até 10 GB por função, com CPU proporcional.
  3. Imagens de container de até 10 GB, o que permite empacotar modelos pequenos.
  4. Nenhuma GPU. Para inferência que precisa de GPU, Lambda não é opção.
  5. Disco efêmero em /tmp (configurável até 10 GB), que não se mantém garantidamente entre invocações.

Confira a página de quotas do Lambda antes de projetar, porque esses limites mudam com o tempo. Outro ponto de atenção é o timeout do que fica na frente da função: o API Gateway tem um tempo máximo de integração, e uma resposta lenta de modelo pode estourar com erro 504 antes de a função terminar. Em cargas de IA, o padrão assíncrono (devolver um identificador e entregar o resultado depois) evita esse problema.

Existem opções intermediárias que misturam os dois mundos. Alguns serviços de containers serverless oferecem GPU com escala a zero, como o Cloud Run, e há plataformas especializadas em inferência sob demanda. Elas reduzem o custo de ocioso, mas continuam sujeitas a cold start de carga de modelo, então valem as mesmas perguntas.

Quando serverless ganha

O serverless funciona bem quando a carga é irregular e o trabalho por requisição é leve:

  • Orquestração de chamadas a modelos hospedados por API.
  • Webhooks de terceiros, com picos imprevisíveis.
  • Validação, enfileiramento e pré-processamento.
  • Cargas muito esporádicas, com poucas execuções por dia, onde pagar por invocação sai mais barato que manter uma máquina ligada.

Aqui o argumento é qualitativo: sem tráfego, o custo tende a zero e a escala é automática. Para o número exato, use a calculadora do seu provedor com o seu volume. Preços de nuvem mudam, e qualquer valor fixo neste texto envelheceria rápido.

Quando containers ganham

Containers compensam quando há tráfego contínuo ou previsível, necessidade de GPU, modelo grande ou latência baixa e estável:

  • Chatbot interno ou de produto com uso o dia todo.
  • Processamento em lote em horário fixo.
  • Inferência que se beneficia de batching e de manter o modelo carregado na GPU.

Com o modelo já na memória, a latência de cada requisição deixa de incluir a carga dos pesos. Servidores de inferência como vLLM ou Ollama, rodando em container, fazem batching contínuo, o que aumenta bastante a vazão por GPU em comparação com atender uma requisição por vez.

Para decidir entre os dois com números, faça a conta com o seu cenário: requisições por dia, duração média de cada uma, necessidade de GPU, proporção de tempo ocioso e o preço por hora da máquina que você usaria. Se a máquina ficaria ociosa na maior parte do tempo, serverless ou escala a zero tende a vencer. Se ficaria ocupada na maior parte do tempo, container fixo tende a vencer.

Arquitetura híbrida: API leve, fila e workers

Uma forma comum de combinar os dois é deixar a borda em serverless e o trabalho pesado em containers, ligados por uma fila:

Cliente
   |
API Gateway + Lambda   (valida e enfileira, responde 202)
   |
Fila (SQS)             (desacopla e absorve picos)
   |
Workers em container   (GPU, modelo carregado, escala pelo tamanho da fila)
   |
Banco / S3             (resultado persistido)

O Lambda que recebe o pedido:

import json
import os
import uuid

import boto3

sqs = boto3.client("sqs")
QUEUE_URL = os.environ["QUEUE_URL"]


def lambda_handler(event, context):
    body = json.loads(event["body"])
    prompt = body.get("prompt", "")

    if not prompt or len(prompt) > 10_000:
        return {"statusCode": 400, "body": json.dumps({"erro": "prompt inválido"})}

    job_id = str(uuid.uuid4())
    sqs.send_message(
        QueueUrl=QUEUE_URL,
        MessageBody=json.dumps({"job_id": job_id, "prompt": prompt}),
    )
    return {"statusCode": 202, "body": json.dumps({"job_id": job_id})}

O worker, em container, carrega o modelo uma vez e consome a fila:

import json
import os

import boto3
from transformers import pipeline

sqs = boto3.client("sqs")
table = boto3.resource("dynamodb").Table(os.environ["RESULTS_TABLE"])
QUEUE_URL = os.environ["QUEUE_URL"]

# Carrega o modelo uma vez, na subida do processo
generator = pipeline(
    "text-generation",
    model=os.environ["MODEL_ID"],
    device_map="auto",
)


def process(job):
    try:
        out = generator(job["prompt"], max_new_tokens=200, return_full_text=False)
        table.put_item(Item={
            "job_id": job["job_id"],
            "status": "done",
            "result": out[0]["generated_text"],
        })
    except Exception as exc:
        table.put_item(Item={
            "job_id": job["job_id"],
            "status": "failed",
            "error": str(exc),
        })


while True:
    resp = sqs.receive_message(
        QueueUrl=QUEUE_URL,
        MaxNumberOfMessages=5,
        WaitTimeSeconds=20,
        VisibilityTimeout=300,
    )
    for msg in resp.get("Messages", []):
        process(json.loads(msg["Body"]))
        sqs.delete_message(QueueUrl=QUEUE_URL, ReceiptHandle=msg["ReceiptHandle"])

O MODEL_ID vem de variável de ambiente, então você troca o modelo sem mudar o código. O device_map="auto" exige a biblioteca accelerate instalada. Este worker processa um job por vez dentro de cada lote, o que é simples e suficiente para começar. Para vazão maior, o caminho é trocar o laço por um servidor de inferência com batching.

O cliente consulta o resultado por job_id (ou recebe um webhook) quando o status chega a done. Escalar os workers pelo tamanho da fila é uma boa regra: o Kubernetes tem o KEDA para isso, e o ECS aceita escala por métrica da fila.

Armadilhas comuns

Fila sem DLQ. Se um job falha várias vezes e simplesmente some, ninguém descobre. Configure uma dead-letter queue (política de redrive no SQS) e alerta quando ela receber mensagens.

Visibility timeout curto demais. Se o processamento demora mais que o tempo de visibilidade, outra instância pega a mesma mensagem e o trabalho é duplicado. Ajuste o valor ao pior caso de duração e faça o worker ser idempotente por job_id.

Worker com GPU ligada o dia todo sem carga. Escala mínima de zero ou de um, conforme a sensibilidade a latência da primeira requisição, e um desligamento por ociosidade.

Serverless "puro" com modelo grande. Se o modelo não cabe nos limites de memória, de imagem ou de GPU, a conclusão é de arquitetura, não de ajuste fino de timeout.

Resumo da decisão

Use serverless para a borda (validar, orquestrar, chamar APIs) e para cargas esporádicas e leves. Use containers quando há GPU, modelo grande, tráfego contínuo ou latência estável como requisito. Use a combinação com fila quando a carga tem picos e o trabalho pesado não precisa responder na hora.

Próximos passos

Para provisionar fila, workers e escala como código, veja Terraform: primeiros passos. Para ver latência fim a fim e custo por requisição em produção, leia Observabilidade de LLM.

Quer aplicar isso na sua empresa? Marque uma conversa de 45 minutos em https://iauaicloud.com.br/consultoria

Aprenda jogando

Defensor da Nuvem

Posicione WAF, cache e auto-scaler para segurar ondas de ataque na sua infra.

Jogar Defensor da Nuvem

Teste seu conhecimento

Teste o que você aprendeu sobre serverless vs containers

Pergunta 1 de 5

Sua aplicação apenas monta um prompt e chama a API de um modelo hospedado por um provedor, com tráfego irregular. Qual opção tende a funcionar melhor?

Um conteúdo prático por quinzena

Deixe o seu e-mail para receber um conteúdo prático de IA e cloud a cada quinzena. Sem spam, e você pede a exclusão quando quiser.

Continue lendo