DEV Community

Cover image for Pare de Brincar com LLMs Locais: Leve a IAG Open Source para a Produção na Magalu Cloud
Gláucio for Magalu Cloud

Posted on

Pare de Brincar com LLMs Locais: Leve a IAG Open Source para a Produção na Magalu Cloud

Nos últimos anos, tivemos um salto no processamento de linguagem natural (NLP). O surgimento da IA Generativa não apenas refinou tarefas antigas, mas abriu bastantes possibilidades em lidar com textos e dados não estruturados, melhorias em experiências de aplicações já existentes, classificações mais simples e demais tarefas. No entanto, para muitas organizações, trouxe um dilema crítico: como escalar o uso dessas tecnologias mantendo o controle absoluto sobre os dados?

Para setores que lidam com informações sensíveis ou que operam sob regulamentações rígidas de privacidade, submeter dados a LLMs públicos via APIs externas não é apenas um risco, mas muitas vezes uma impossibilidade jurídica. É aqui que a soberania de dados se torna o pilar central da estratégia de tecnologia. Rodar modelos abertos em uma infraestrutura nacional e controlada, como a Magalu Cloud, não é mais apenas uma alternativa de custo, é uma necessidade de segurança e conformidade.

Neste tutorial, vamos explorar como você pode unir o estado da arte dos modelos do Hugging Face com o vLLM, garantindo performance de nível de produção sem abrir mão da privacidade.

O que é vLLM?

O vLLM não é apenas uma biblioteca de inferência; é um sistema de gerenciamento de memória virtualizado para GPUs.

O problema central da inferência de LLMs não é computação (FLOPs), é I/O de memória. Engines tradicionais alocam VRAM contígua para o Key-Value (KV) Cache, resultando em fragmentação massiva (60-80% de desperdício) e impedindo o paralelismo.

O vLLM resolve isso com o PagedAttention: um algoritmo de atenção que permite armazenar o KV Cache em blocos de memória não contíguos. Isso desacopla a memória lógica (sequência de tokens) da memória física (VRAM), permitindo:

  • Desperdício Zero: Quase nenhuma fragmentação de memória (<4%).
  • Continuous Batching: A capacidade de injetar novas requisições na GPU token-a-token, sem esperar que o lote anterior termine.
  • Copy-on-Write: Mecanismo eficiente para decodificação paralela (como em Beam Search), onde múltiplos "caminhos" compartilham a mesma memória física até divergirem.

Você pode conhecer mais sobre o vLLM neste blog post

Passo a passo para configuração do ambiente

Para este tutorial, provisionamos uma máquina virtual equipada com a GPU NVIDIA L40S. A escolha é estratégica pois a L40S é baseada na arquitetura Ada Lovelace, possui o Transformer Engine com suporte nativo a precisão FP8. Isso garante que o modelo Qwen3-30B-A3B-FP8 rode muito bem tirando proveito dos 18.176 núcleos CUDA e da alta disponibilidade de memória. Com 48GB de VRAM GDDR6 (ECC) e uma largura de banda de 864 GB/s, eliminamos gargalos de transferência, assegurando estabilidade e alta vazão durante a inferência.

Então dado o contexto vamos iniciar a mão na massa.

Criação da máquina

Etapas para criarmos a máquina

  • 1 - Início e Seleção do Serviço

    • No Console da Magalu Cloud, localize o card com o texto "Virtual Machine".
    • Clique no botão azul "Criar instância".
  • 2 - Configurações de região e sistema operacional

    • Zona de Disponibilidade: Selecione a zona desejada (no meu caso, foi escolhida a br-se1-a mas escolha o que preferir).
    • Escolha de Imagem: Selecione a distribuição Ubuntu.
    • Versão: No menu suspenso que aparece, escolha a versão Ubuntu 24.04 LTS.
  • 3 - Hardware

    • Habilitar GPU (muito importante): Marque a caixa de seleção "Habilitar GPU" para visualizar as instâncias de alta performance.
    • Tipo de Instância:
    • Role até a seção de GPUs (GPU NVIDIA L40S, indicada para IA generativa e LLMs).
    • Selecione a configuração desejada. Neste caso escolha a instância L40S-1x-DP8-64-100 (que oferece 8 vCPUs, 64 GB de RAM e 100 GB de disco).
  • 4 - Conectividade e Acesso

    • Acesso Público: Mantenha marcada a opção "Atribuir IPv4 público para essa instância" (padrão).
    • Chave SSH (muito importante para poder acessar sua máquina):
    • Selecione a opção "Selecionar chave já utilizada anteriormente" caso tenha uma ou cadastre uma nova clicando em " Inserir uma chave própria nova".
    • Caso esteja inserindo uma nova chave, consulte-a com o seguinte comando Linux/macos cat ~/.ssh/id_ed25519.pub(o nome da sua chave deve ser diferente de id_ed25519.pub, mas ela deve finalizar com .pub), copie o valor que aparecer no seu terminal e cole no campo "Insira sua chave SSH" e depois dê um nome para essa chave ssh ser utilizada.
  • 5 - Nomeando a máquina

    • Nome da Instância: Defina um nome identificável para o servidor. No meu caso, usei o nome llm_server-vllm.
    • Clique no botão "Criar instância" no canto inferior direito.
    • Você será redirecionado para a lista de instâncias, onde poderá ver seu novo servidor com o status "Criando".

Acessando nossa máquina virtual para efetuarmos nossas configurações

Depois de criarmos nossa máquina virtual, agora precisaremos acessar ela para fazermos as instalações necessárias para rodarmos nossa LLM em produção com proxy reverso e SSL.

Requisito obrigatório:
Para nosso proxy reverso funcionar corretamente, será preciso configurar seu domínio apontando o IP da sua máquina que poderá pegar essa informação acessando a página de detalhes da nossa VM.

Configuração da VM na Magalu Cloud

No meu caso utilizo a Cloudflare para gerir meu DNS, e você pode seguir a documentação deles para configurar seu DNS, mas caso use outro local para gerenciar seu DNS busque e faça a configuração, para este tutorial eu vou utilizar o seguinte domínio vllm-mgc.glaucio.tec.br , dito isto vamos seguir para acesso.

Primeiro acesso a nossa VM via SSH

Na página da nossa máquina virtual como no print acima, há um ícone azul para copiar o seu IPv4 Público e seu usuário.

e execute o comando ssh ubuntu@201.23.79.32 no seu terminal como no print abaixo

Acessando nossa máquina via SSH.

No primeiro acesso irá aparecer esta mensagem da imagem acima no seu terminal, apenas digite yes e aperte enter e agora com acesso ao servidor podemos iniciar a instalação das dependências.

Instalação do Docker

Para instalar o Docker rapidamente vamos automatizar esta parte, para isso abra algum editor pelo terminal como VIM ou nano, no meu caso para ser mais fácil irei utilizar o nano para criar o script de instalação do nosso Docker com o comando:

nano install_docker.sh
Enter fullscreen mode Exit fullscreen mode

e você irá ver a seguinte tela, com o nosso editor nano aberto com um arquivo de texto em memória sem conteúdo algum

Editor nano aberto com um arquivo de texto em memória sem conteúdo algum.

Para facilitar esta etapa de instalação do Docker, copie e cole o shell script abaixo.

#!/bin/bash
set -e

# 0. Antes de rodar o script lembre de executar `chmod +x install_docker.sh`

# 1. Limpeza de tentativas anteriores (Resolve o erro 'Malformed entry')
echo "🧹 Limpando configurações antigas..."
sudo rm -f /etc/apt/sources.list.d/docker.list
sudo apt-get update

# 2. Instalar dependências essenciais
echo "📦 Instalando dependências..."
sudo apt-get install -y ca-certificates curl gnupg lsb-release

# 3. Baixar Chave GPG
echo "🔑 Configurando chave GPG..."
sudo install -m 0755 -d /etc/apt/keyrings
# Se a chave já existir, removemos para baixar a mais nova
sudo rm -f /etc/apt/keyrings/docker.asc
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc

# 4. Adicionar Repositório (Fixo para 'noble' - Ubuntu 24.04)
echo "📂 Adicionando repositório 'noble'..."
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu noble stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

# 5. Instalar Docker
echo "⬇️  Instalando pacotes do Docker..."
sudo apt-get update
sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

# 6. Configurar permissão de usuário
echo "👤 Adicionando usuário '$USER' ao grupo docker..."
sudo usermod -aG docker $USER

echo "---"
echo "✅ Instalação concluída"
echo "⚠️  Para usar imediatamente, rode: newgrp docker"
echo "Valide instalação com docker run hello-world"
Enter fullscreen mode Exit fullscreen mode

e deve ficar da seguinte forma

Acessando nossa máquina via SSH.

Agora aperte as teclas Ctrl e x para salvar e confirme para salvar o conteúdo que adicionamos ao nosso arquivo.

Feito isso, confirme se o nosso script foi salvo com sucesso e deve ter o mesmo texto do script acima, rode o comando:

cat install_docker.sh 
Enter fullscreen mode Exit fullscreen mode

e confira se está tudo ok. Feito isso precisamos fazer nosso script ser executável, para isso vamos precisar rodar o seguinte comando:

chmod +x install_docker.sh
Enter fullscreen mode Exit fullscreen mode

Feito isso, podemos rodar nosso script de instalação do Docker com o comando:

./install_docker.sh 
Enter fullscreen mode Exit fullscreen mode

e correndo tudo bem irá aparecer no seu terminal as seguintes mensagens:

Acessando nossa máquina via SSH.

e então siga as instruções que foram exibidas para alterar o GID (Group ID) da sua sessão do terminal para o grupo "docker", sem que você precise deslogar e logar e rodar um container de hello-world para dar sorte haha e validar se a instalação correu bem.

Ao rodar os comandos sugeridos, você deve ter o seguinte resultado no seu terminal:

Validando instalação do Docker com docker run hello-world

Se você teve os mesmos resultados dos prints acima, estamos prontos para instalar as dependências para que o Docker consiga se comunicar com a nossa GPU.

Instalação de drivers e nvidia-container-toolkit

Assim como a etapa de instalação do Docker criaremos um script para agilizar nossa instalação e facilitar a reprodutibilidade.

Crie nosso arquivo de setup da nossa máquina com o seguinte comando:

nano setup_nvidia.sh
Enter fullscreen mode Exit fullscreen mode

cole o script abaixo como fizemos na etapa do Docker:

#!/bin/bash
set -e

# Configuração - Altere conforme necessário
NVIDIA_DRIVER_VERSION="${NVIDIA_DRIVER_VERSION:-570}"

# Cores para output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color

echo -e "${GREEN}╔════════════════════════════════════════════════════════╗${NC}"
echo -e "${GREEN}║  Instalação NVIDIA Driver + Container Toolkit          ║${NC}"
echo -e "${GREEN}║  Driver versão: ${NVIDIA_DRIVER_VERSION}${NC}"
echo -e "${GREEN}╚════════════════════════════════════════════════════════╝${NC}"
echo ""

# 0. Verificar se está rodando como root ou com sudo
if [ "$EUID" -eq 0 ]; then
    echo -e "${RED}❌ Não execute este script como root. Use seu usuário normal.${NC}"
    exit 1
fi

# 1. Verificar se driver já está instalado
echo "🔍 Verificando instalação existente..."
if command -v nvidia-smi &> /dev/null; then
    echo -e "${YELLOW}⚠️  Driver NVIDIA já está instalado:${NC}"
    nvidia-smi --query-gpu=driver_version,name --format=csv,noheader
    read -p "Deseja continuar mesmo assim? (s/N): " -n 1 -r
    echo
    if [[ ! $REPLY =~ ^[Ss]$ ]]; then
        echo "Instalação cancelada."
        exit 0
    fi
fi

# 2. Verificar se Docker está instalado
echo "🐳 Verificando Docker..."
if ! command -v docker &> /dev/null; then
    echo -e "${RED}❌ Docker não encontrado. Execute primeiro o script 01-docker_install_script.sh${NC}"
    exit 1
fi
echo -e "${GREEN}✓ Docker encontrado${NC}"

# 3. Atualizar sistema
echo ""
echo "📦 Atualizando lista de pacotes..."
sudo apt-get update

# 4. Instalar Driver NVIDIA
echo ""
echo "🎮 Instalando driver NVIDIA versão ${NVIDIA_DRIVER_VERSION}..."
sudo apt-get install -y nvidia-driver-${NVIDIA_DRIVER_VERSION}

# 5. Instalar NVIDIA Container Toolkit
echo ""
echo "📦 Configurando repositório NVIDIA Container Toolkit..."

# Adicionar chave GPG
curl -fsSL https://nvidia.github.io/libnvidia-container/gpgkey | \
    sudo gpg --dearmor -o /usr/share/keyrings/nvidia-container-toolkit-keyring.gpg --yes

# Adicionar repositório
curl -s -L https://nvidia.github.io/libnvidia-container/stable/deb/nvidia-container-toolkit.list | \
    sed 's#deb https://#deb [signed-by=/usr/share/keyrings/nvidia-container-toolkit-keyring.gpg] https://#g' | \
    sudo tee /etc/apt/sources.list.d/nvidia-container-toolkit.list > /dev/null

# Instalar toolkit
echo "⬇️  Instalando nvidia-container-toolkit..."
sudo apt-get update
sudo apt-get install -y nvidia-container-toolkit

# 6. Configurar Docker para usar runtime NVIDIA
echo ""
echo "🔧 Configurando Docker para usar GPU NVIDIA..."
sudo nvidia-ctk runtime configure --runtime=docker

# 7. Reiniciar Docker
echo "🔄 Reiniciando serviço Docker..."
sudo systemctl restart docker

# 8. Resumo final
echo ""
echo -e "${GREEN}╔════════════════════════════════════════════════════════╗${NC}"
echo -e "${GREEN}║  ✅ Instalação concluída!                              ║${NC}"
echo -e "${GREEN}╚════════════════════════════════════════════════════════╝${NC}"
echo ""
echo -e "${YELLOW}⚠️  IMPORTANTE: Reinicie a máquina para carregar o driver:${NC}"
echo ""
echo "    sudo reboot"
echo ""
echo -e "${GREEN}Após reiniciar, valide a instalação com:${NC}"
echo ""
echo "    # Verificar driver no host"
echo "    nvidia-smi"
echo ""
echo "    # Testar GPU no Docker"
echo "    docker run --rm --gpus all nvidia/cuda:12.3.1-base-ubuntu22.04 nvidia-smi"
echo ""
Enter fullscreen mode Exit fullscreen mode

salve com as teclas Ctrl e x e siga os caminhos para salvar e agora dê permissão para que ele também seja executável com o comando:

chmod +x setup_nvidia.sh
Enter fullscreen mode Exit fullscreen mode

e agora vamos rodar com o comando:

./setup_nvidia.sh
Enter fullscreen mode Exit fullscreen mode

e correndo tudo bem na instalação, você verá o seguinte resultado no seu terminal

Instruções de sucesso para instalação dos drivers e container kit.

e seguindo as instruções, vamos reiniciar a máquina para aplicar as novas configurações de driver.
com o comando:

sudo reboot
Enter fullscreen mode Exit fullscreen mode

aguarde alguns segundos para a máquina reiniciar, aproveite para pegar um café, um chá ou uma água.

Agora vamos precisar acessar nossa máquina via SSH novamente, com o seguinte comando:

ssh ubuntu@ip-da-sua-maquina
Enter fullscreen mode Exit fullscreen mode

Antes de seguirmos para a mão na massa para subir o nosso servidor e LLM, vamos validar se nossos passos anteriores funcionaram e tudo foi instalado corretamente com o comando:

docker run --rm --gpus all nvidia/cuda:12.3.1-base-ubuntu22.04 nvidia-smi
Enter fullscreen mode Exit fullscreen mode

e caso tenha o seguinte resultado, suas instalações estão corretas e podemos ir para a etapa de subir o nosso servidor de LLM. 🎉

nvidia-smi rodando dentro do container Docker

Subindo aplicação

Agora antes de partirmos para configurações e deploy, vale passarmos pela forma que foi pensada e arquitetada a essa nossa aplicação, passando por como ela vai ser servida, protegendo rotas do vLLM que não validam nosso token de autenticação e podem ser alvo de exploração por entidades mal-intencionadas.

Arquitetura da aplicação

Desenho de como funcionará nossa aplicação, com um proxy reverso utilizando caddy

Como podemos observar na imagem acima, a porta de entrada para o modelo é o servidor Caddy, pois quando expomos um servidor de LLM à internet, não basta configurar uma API Key e achar que está tudo protegido. O vLLM, por padrão, expõe endpoints como /health , /metrics e /docs que não validam autenticação, criando uma superfície de ataque significativa para scanners automatizados e potenciais explorações. Seguindo as recomendações oficiais de segurança do vLLM, implementamos uma arquitetura de "defense in depth": o vLLM roda em uma rede interna do Docker, completamente isolado da internet, enquanto o Caddy atua como nosso portão de entrada único. No Caddyfile, configuramos um allowlist restrita apenas com as rotas /v1/chat/completions , /v1/completions , /v1/models (opcional e pode ser removida) e /v1/embeddings (opcional e pode ser removida) que são encaminhadas para o vLLM. Todo o resto, incluindo os endpoints críticos /metrics e o /health interno do vLLM, é bloqueado com abort, retornando 403 Forbidden. Isso significa que mesmo que alguém descubra o IP do servidor, não consegue extrair métricas de performance, status detalhados do modelo ou acessar documentação sensível. Complementamos com TLS 1.2/1.3 forçado via Caddy e a API Key do vLLM protegendo os endpoints OpenAI e com isto temos um servidor que segue as recomendações de segurança da documentação oficial do vLLM, minimizamos os endpoints expostos, colocamos tudo atrás de um proxy reverso confiável, e mantemos o motor de inferência completamente inacessível diretamente e sua LLM roda segura, privada e pronta para produção.

  • Não cheguei a configurar o rate limit, mas deixo como dever de casa caso tenha interesse.

Agora contextualizados, vamos à mão na massa.

Configurações importantes

Para subirmos a aplicação, vamos precisar criar os seguintes arquivos:

  • .env: Onde incluiremos as nossas variáveis de ambiente usadas pelo Caddy e pelo vLLM.
  • Caddyfile: Configurações do nosso servidor de aplicação para fazer proxy reverso para o vLLM.
  • compose.yml: Docker compose para orquestrarmos nossa aplicação de forma fácil.

e para termos uma organização, vamos criar uma pasta para envolver os arquivos que vamos precisar criar, e para criar a pasta e os arquivos rode o seguinte comando no seu terminal:

mkdir llm-server; cd llm-server; touch .env Caddyfile compose.yml
Enter fullscreen mode Exit fullscreen mode

e depois valide se tudo correu bem, se sim terá um resultado como o seguinte:

Criando pastas e arquivos para servir nossa aplicação e mostrando arquivos da pasta.

Feito isto, vamos configurar nosso .env com as variáveis necessárias para rodar nossos containers, e para isso vamos utilizar o nano, o conteúdo que vamos precisar inserir no nosso arquivo são:

O que é cada variável?

  • vLLM

    • VLLM_API_KEY: Será o token que a API do vLLM vai validar a presença para permitir a inferência, pode gerar um para nosso teste com o comando openssl passwd -6 "uma chave bem segura aqui"
    • HF_TOKEN: Token para baixar modelos privados, caso você tenha algum fine tuning e deseja servir ele, para conseguir este token pode seguir a documentação oficial. Neste caso como vamos utilizar um modelo aberto, pode deixar qualquer valor.
    • MODEL_NAME: Nome do modelo do Hugging Face que iremos utilizar
    • Onde copiar o nome do modelo
    • Fica até como desafio, servir outro modelo como o gpt-oss-20b.
  • Caddy

    • DOMAIN: O domínio em que vamos servir nossa aplicação.
    • EMAIL_SSL: O email que o caddy utiliza para que o CertMagic renove seus certificados.
# vLLM Configuration
VLLM_API_KEY=sua-chave-api-segura 
HF_TOKEN=hf_seu_token_huggingface
MODEL_NAME=Qwen/Qwen3-30B-A3B-FP8

# Caddy/SSL Configuration
DOMAIN=vllm-mgc.glaucio.tec.br
EMAIL_SSL=meuemail+vllm-server@lorem.com
Enter fullscreen mode Exit fullscreen mode

Agora contextualizados vamos preencher nosso .env , abra o arquivo com o comando:

nano .env
Enter fullscreen mode Exit fullscreen mode

e cole suas variáveis preenchidas com seus dados, use Ctrl + x e quando aparecer "Save modified buffer?" confirme apertando a tecla y e depois quando aparecer "File Name to Write: .env", aperte Enter .

Vamos para o nosso arquivo Caddyfile , abra ele com o nano como já descrito no passo do .env e cole nele o seguinte conteúdo:

{$DOMAIN} {
    # Habilita compressão Gzip/Zstd para performance
    encode gzip zstd

    # Configuração de Log (Opcional, bom para auditoria)
    log {
        output file /var/log/caddy/access.log
    }

    # REGRAS DE SEGURANÇA E ROTEAMENTO
    # Bloqueia tudo por padrão, exceto o bloco 'handle' abaixo

    # Página de status para verificar se o Caddy está online
    handle /health {
        respond "OK - Caddy is running on {$DOMAIN}" 200
    }

    # Página inicial com informações básicas
    handle / {
        header Content-Type text/html
        respond `<!DOCTYPE html>
<html>
<head>
    <title>vLLM API Server</title>
    <style>
        body { font-family: system-ui, sans-serif; max-width: 600px; margin: 50px auto; padding: 20px; background: #1a1a2e; color: #eee; }
        h1 { color: #00d9ff; }
        .status { background: #16213e; padding: 20px; border-radius: 8px; margin: 20px 0; }
        .ok { color: #00ff88; }
        code { background: #0f3460; padding: 2px 6px; border-radius: 4px; }
    </style>
</head>
<body>
    <h1>vLLM API Server</h1>
    <div class="status">
        <p class="ok">Proxy: Online</p>
        <p>Domain: {$DOMAIN}</p>
    </div>
    <h3>API Endpoints:</h3>
    <ul>
        <li><code>/v1/chat/completions</code></li>
        <li><code>/v1/completions</code></li>
        <li><code>/v1/models</code></li>
        <li><code>/v1/embeddings</code></li>
    </ul>
    <p><small>Health check: <a href="/health">/health</a></small></p>
</body>
</html>` 200
    }

    # Define o matcher para rotas OpenAI (Chat, Completions, Models, Embeddings)
    @openai_routes {
        path /v1/chat/completions
        path /v1/completions
        path /v1/models # pode remover caso não queria expor
        path /v1/embeddings # como vamos servir apenas LLM não é muito útil aqui no nosso caso
    }

    # Processa apenas as rotas permitidas
    handle @openai_routes {
        reverse_proxy vllm:8000 {
            # Configurações de timeout para streaming longo (LLMs)
            flush_interval -1
            transport http {
                response_header_timeout 300s
            }
        }
    }

    # Captura qualquer outra rota (metrics, health, root) e retorna 403 Forbidden
    handle {
        abort
    }

    # TLS Configuration (Automático, mas forçando protocolos seguros)
    tls {$EMAIL_SSL} {
        protocols tls1.2 tls1.3
    }
}
Enter fullscreen mode Exit fullscreen mode

E por último nosso arquivo compose.yml para subirmos a aplicação, abra ele com nano compose.yml e cole o seguinte conteúdo:

services:
  vllm:
    image: vllm/vllm-openai:latest
    container_name: vllm_inference
    restart: unless-stopped
    oom_score_adj: -500
    environment:
      - VLLM_API_KEY=${VLLM_API_KEY}
      - HUGGING_FACE_HUB_TOKEN=${HF_TOKEN}
    volumes:
      - ./vllm_cache:/root/.cache/huggingface
    deploy:
      resources:
        limits:
          cpus: '6.0'
          memory: 56G
        reservations:
          cpus: '4.0'
          memory: 32G
          devices:
            - driver: nvidia
              count: 1
              capabilities: [gpu]
    ipc: host
    shm_size: '16gb'
    command: >
      --model ${MODEL_NAME}
      --served-model-name "qwen3-30b-a3b-fp8"
      --host 0.0.0.0
      --port 8000
      --max-model-len 16384
      --gpu-memory-utilization 0.95
      --kv-cache-dtype fp8
      --enable-prefix-caching
      --max-num-seqs 8
      --disable-log-requests
      --reasoning-parser deepseek_r1
    healthcheck:
      test: ["CMD-SHELL", "curl -f http://localhost:8000/health || exit 1"]
      interval: 15s
      timeout: 10s
      retries: 5
      start_period: 300s  # 5 minutos para carregar o modelo

  proxy:
    image: caddy:alpine
    container_name: caddy_secure_proxy
    restart: unless-stopped
    deploy:
      resources:
        limits:
          cpus: '1.0'
          memory: 512M
        reservations:
          cpus: '0.5'
          memory: 64M
    extra_hosts:
      - "host.docker.internal:host-gateway"
    ports:
      - "80:80"
      - "443:443"
      - "443:443/udp"
    environment:
      - DOMAIN=${DOMAIN}
      - EMAIL=${EMAIL_SSL}
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile
      - ./caddy_data:/data
      - ./caddy_config:/config
    depends_on:
      vllm:
        condition: service_healthy
Enter fullscreen mode Exit fullscreen mode

Com nossos arquivos preenchidos, podemos executar o comando para subir nosso docker compose:

docker compose --env-file .env up
Enter fullscreen mode Exit fullscreen mode

e com isso começará a baixar as imagens que vamos utilizar:

Baixando imagens dos nossos containers

Depois de ter baixado as imagens dos nossos containers, vai aparecer na sua tela a inicialização do vLLM

Baixando imagens dos nossos containers

e após o vLLM baixar os pesos do modelo ele irá iniciar o caddy e aguarde um tempo para configurações de SSL sejam feitas

Servidor de aplicação subindo

Aí agora para deixar os containers rodando em background, aperte a tecla d como sugerido na parte inferior do terminal

Mostrando como liberar terminal

e para não ter de passar por isso na próxima, suba os containers com -d no comando para subir os containers e já liberar o terminal

docker compose --env-file .env up -d
Enter fullscreen mode Exit fullscreen mode

Agora feito tudo isso, nossa aplicação está rodando lindamente, mas ainda não está respondendo pois precisaremos fazer as configurações de rede para liberar as portas 80 e 443 do nosso servidor.

No próximo passo irei explicar como resolver o erro do servidor não responder

Erro que vai aparecer por não termos configurado a porta 443 para nossa VM.

Configuração de rede para liberar acessos externos pelo nosso DNS

Agora vamos para a última etapa de configuração antes de podermos usar nossa LLM, e fazer nossas tão desejadas chamadas à API.

Acesse novamente seu console do Magalu Cloud, estando na tela inicial, clique no menu sanduíche no lado superior direito e com isto irá abrir o menu lateral.

Como Chegar na tela Grupo de Segurança

depois clique na opção com o texto "Network", ele irá abrir um submenu flutuante à esquerda dele e então clique na opção "Grupo de Segurança" e você será redirecionado para a tela de Grupo de Segurança.

Tela Grupo de Segurança

Chegando na tela Grupo de Segurança, clique no botão "Criar grupo de segurança" e irá abrir uma tela para nomear e dar uma descrição para seu novo grupo de segurança, e preencha com um nome que vá lembrar, no meu caso coloquei "tutorial-vllm-l40s" como pode ser visto na imagem abaixo.

Criando um Grupo de Segurança

e confirmando a criação do nosso grupo, voltaremos para a página inicial de Grupo de Segurança, agora vamos precisar clicar no novo grupo que criamos, no meu caso foi "tutorial-vllm-l40s".

Criando um Grupo de Segurança

Clicando nele, iremos para a página do nosso Grupo de Segurança e nela iremos clicar no botão "Adicionar regra", preencha o formulário para que nosso servidor possa responder às requisições que faremos para ele. Devemos liberar a porta 443 para entrada, aceitando requisições de qualquer origem; ou, caso você tenha um IP fixo que sempre chamará a API, pode incluí-lo no preenchimento. O meu ficou da seguinte forma:

Liberando porta 443

Clique em "Adicionar regra", e após fechar a tabela onde lista as regras deve conter agora a regra de entrada para a porta 443:

Regra aplicada para entrada

agora vá até a página da nossa máquina virtual e clique na "Tabzinha" (Contornada na imagem em vermelho) de rede e verá uma tela como a do print abaixo.

Tela de rede da máquina virtual

Estando nesta página, clique no botão azul com o texto "Adicionar grupo de segurança", na modal que irá abrir, busque o Grupo de segurança que criamos e configuramos.

Modal para aplicar regras de grupo de segurança

Após adicionar, podemos validar se o nosso domínio está respondendo às requisições

Tela de rede da máquina virtual

Caso suas configurações estiverem corretas, você verá uma tela como acima, mas com o seu domínio.

Agora podemos fazer chamadas para nosso modelo com o seguinte comando:

curl -X POST 'https://vllm-mgc.glaucio.tec.br/v1/chat/completions' \
  --header 'Content-Type: application/json' \
  --header 'Authorization: Bearer sua-chave-api-segura' \
  --data '{
  "model": "qwen3-30b-a3b-fp8",
  "messages": [
    {
      "role": "system",
      "content": "Você é um assistente muito inteligente, que pensa antes de qualquer resposta, que segue a ciência, e deve ser muito dedicado a acertar."
    },
    {
      "role": "user",
      "content": "você conhece o acre? /no_think"
    }
  ]
}'
Enter fullscreen mode Exit fullscreen mode

Como estamos servindo um modelo com capacidade de "raciocínio", para habilitar enviamos na mensagem o texto /think e para desabilitar envie /no_think e configuramos o parser --reasoning-parser deepseek_r1 , então quando o "raciocínio" for ativado ele virá em choices[0].message.reasoning e caso esteja desativado, essa chave terá o valor nulo.

Exemplo de resposta com /think :

{
  "id": "chatcmpl-871a1a570fd3a0d9",
  "object": "chat.completion",
  "created": 1769888901,
  "model": "qwen3-30b-a3b-fp8",
  "choices": [
    {
      "index": 0,
      "message": {
        "role": "assistant",
        "content": "\n\nSim, conheço o **acre**! Ele pode se referir a **dois conceitos principais**:\n\n---\n\n### 1. **Unidade de área (acre)**  \nO **acre** é uma unidade de medida de área do sistema imperial, amplamente usada em países como os Estados Unidos, Reino Unido e outros.  \n- **Definição**: 1 acre = **4.046,86 metros quadrados** (m²).  \n- **Equivalência**:  \n  - 1 acre ≈ 0,4047 hectares (ha).  \n  - 1 hectare ≈ 2,471 acres.  \n- **Uso comum**: Medição de terrenos, campos de futebol, fazendas, etc.  \n  - Por exemplo, um campo de futebol oficial tem cerca de **0,71 acre**.\n\n---\n\n### 2. **Estado do Acre (Brasil)**  \nO **Acre** também é um **estado da Amazônia brasileira**, localizado no **norte do Brasil**, na região da **Amazônia Ocidental**.  \n- **Capital**: **Rio Branco**.  \n- **Características**:  \n  - Faz parte da **Bacia do Rio Amazonas**.  \n  - Conhecido por sua **floresta tropical**, riqueza em biodiversidade e culturas indígenas.  \n  - Tem uma economia baseada em **extrativismo (como a seringueira)** e **agricultura**.  \n  - O **Parque Nacional do Acre** e a **Reserva da Biosfera do Acre** são áreas protegidas.  \n- **População**: Cerca de **900.000 habitantes** (estatísticas de 2023).  \n\n---\n\nSe você se referia a algo específico (como uma empresa, termo técnico ou outro contexto), me avise que posso aprofundar! 😊",
        "refusal": null,
        "annotations": null,
        "audio": null,
        "function_call": null,
        "tool_calls": [],
        "reasoning": "\nOkay, the user is asking if I know about Acre. First, I need to confirm what exactly they're referring to. Acre can mean a few things. The most common is the unit of area, which is 4,046.86 square meters. But there's also Acre as a place, like the Acre state in Brazil, or maybe even a company or a term in another context.\n\nI should start by acknowledging that Acre can refer to multiple things. Let me break it down. First, the unit of area. Explain that it's a unit used in the imperial system, commonly used in countries like the US, UK, and others. Mention its conversion to square meters and other units. Then, mention the Brazilian state, Acre, located in the northwest of Brazil, part of the Amazon region. Highlight its geography, maybe the capital, Rio Branco, and some key points about the state, like its natural resources or cultural aspects.\n\nWait, the user might be asking about the state, but I should cover both possibilities. Also, check if there's another context, like a company or a term in a different field. But I think the two main ones are the unit and the state. Make sure to present both clearly. Avoid any confusion by structuring the answer with headings or bullet points. Also, verify the accuracy of the information, like the exact area of an acre, the location of Acre state, and its capital. Maybe mention that the state is known for its rainforests and biodiversity. Double-check the conversion factors to ensure they're correct. Alright, that should cover the main points.\n",
        "reasoning_content": "\nOkay, the user is asking if I know about Acre. First, I need to confirm what exactly they're referring to. Acre can mean a few things. The most common is the unit of area, which is 4,046.86 square meters. But there's also Acre as a place, like the Acre state in Brazil, or maybe even a company or a term in another context.\n\nI should start by acknowledging that Acre can refer to multiple things. Let me break it down. First, the unit of area. Explain that it's a unit used in the imperial system, commonly used in countries like the US, UK, and others. Mention its conversion to square meters and other units. Then, mention the Brazilian state, Acre, located in the northwest of Brazil, part of the Amazon region. Highlight its geography, maybe the capital, Rio Branco, and some key points about the state, like its natural resources or cultural aspects.\n\nWait, the user might be asking about the state, but I should cover both possibilities. Also, check if there's another context, like a company or a term in a different field. But I think the two main ones are the unit and the state. Make sure to present both clearly. Avoid any confusion by structuring the answer with headings or bullet points. Also, verify the accuracy of the information, like the exact area of an acre, the location of Acre state, and its capital. Maybe mention that the state is known for its rainforests and biodiversity. Double-check the conversion factors to ensure they're correct. Alright, that should cover the main points.\n"
      },
      "logprobs": null,
      "finish_reason": "stop",
      "stop_reason": null,
      "token_ids": null
    }
  ],
  "service_tier": null,
  "system_fingerprint": null,
  "usage": {
    "prompt_tokens": 58,
    "total_tokens": 836,
    "completion_tokens": 778,
    "prompt_tokens_details": null
  },
  "prompt_logprobs": null,
  "prompt_token_ids": null,
  "kv_transfer_params": null
}
Enter fullscreen mode Exit fullscreen mode

Exemplo de resposta com /no_think :

{
  "id": "chatcmpl-a0710ba10d25e658",
  "object": "chat.completion",
  "created": 1769889464,
  "model": "qwen3-30b-a3b-fp8",
  "choices": [
    {
      "index": 0,
      "message": {
        "role": "assistant",
        "content": "\n\nClaro, eu conheço o Acre! O Acre é um estado da Amazônia brasileira, localizado no extremo oeste do Brasil, fazendo fronteira com a Bolívia e o Peru. É um dos estados mais extensos do Brasil e é conhecido por sua vasta floresta tropical, rica em biodiversidade e por ser um dos últimos grandes territórios preservados da Amazônia.\n\nAlguns pontos interessantes sobre o Acre:\n\n- **Capital**: Rio Branco\n- **População**: Cerca de 900 mil habitantes (dados aproximados)\n- **Cultura**: Mistura de influências indígenas, brasileiras e bolivianas.\n- **Economia**: Baseada principalmente na agricultura (como a seringueira, o cauim, o açaí), na pecuária e na exploração de recursos naturais.\n- **Ecologia**: É uma das regiões mais preservadas da Amazônia, com muitas áreas de proteção ambiental.\n- **História**: Foi cedido ao Brasil pela Bolívia em 1903, através do Tratado de Petrópolis.\n\nO Acre também é famoso por sua paisagem única, com rios, florestas e uma cultura rica e diversificada. Se quiser, posso te contar mais sobre algum aspecto específico do Acre!",
        "refusal": null,
        "annotations": null,
        "audio": null,
        "function_call": null,
        "tool_calls": [],
        "reasoning": "\n\n",
        "reasoning_content": "\n\n"
      },
      "logprobs": null,
      "finish_reason": "stop",
      "stop_reason": null,
      "token_ids": null
    }
  ],
  "service_tier": null,
  "system_fingerprint": null,
  "usage": {
    "prompt_tokens": 60,
    "total_tokens": 380,
    "completion_tokens": 320,
    "prompt_tokens_details": null
  },
  "prompt_logprobs": null,
  "prompt_token_ids": null,
  "kv_transfer_params": null
}
Enter fullscreen mode Exit fullscreen mode

Então a partir de agora é só ser feliz.

Imagem de encerramento do looney tunes

Em breve trarei mais um post fazendo múltiplas requisições para nosso modelo, trazendo números de tempo de resposta e qualidade das respostas do modelo.

Top comments (0)