# Observabilidade de LLMs em Produção: Do GPU à Qualidade da Resposta

Colocar um LLM em produção no SageMaker é a parte fácil. O difícil é saber, em tempo real, se ele está respondendo bem, consumindo GPU de forma eficiente e custando o que você planejou. Este artigo detalha a stack de observabilidade que eu construiria hoje para inferência LLM financeira-grade.

- URL: https://fernando.moretes.com/blog/llm-observabilidade-qualidade-custo-sagemaker-bedrock

- Markdown: https://fernando.moretes.com/blog/llm-observabilidade-qualidade-custo-sagemaker-bedrock/article.md?lang=pt

- Published: 2026-06-10T13:08:00.000Z

- Category: Dados & Plataformas

- Tags: llmops, observability, sagemaker, bedrock, grafana, opentelemetry, mlops, financial-grade

- Reading time: 9 min

- Source: [Comprehensive observability for SageMaker AI LLM inference](https://aws.amazon.com/blogs/machine-learning/)

---

Quando um LLM entra em produção num ambiente financeiro — seja para geração de relatórios regulatórios, triagem de documentos ou assistência a analistas — a pergunta que o time de plataforma precisa responder não é 'o endpoint está up?', mas sim: 'a resposta gerada é confiável, está dentro do SLA de latência, o custo por token está dentro do orçamento e o modelo não está alucinando sobre dados de clientes?' Essas quatro dimensões — disponibilidade, latência, custo e qualidade semântica — exigem instrumentação em camadas que vai muito além do CloudWatch básico. Este artigo é um conjunto de notas de campo sobre como eu monto essa stack hoje.

## O Problema Real: Três Camadas de Cegueira Operacional

A maioria dos times que implanta LLMs no SageMaker começa com o mesmo conjunto de métricas que usaria para qualquer endpoint de ML: `Invocations`, `ModelLatency`, `OverheadLatency` e `Invocation4XXErrors`. Isso é necessário, mas completamente insuficiente para inferência de linguagem.

A **primeira camada de cegueira** é a infraestrutura de GPU. Um endpoint `ml.g5.12xlarge` com 4xA10G pode estar com `GPUUtilization` em 40% enquanto o `GPUMemoryUtilization` está em 95% — o modelo está thrashing na VRAM, causando latência de P99 de 8s enquanto o P50 parece saudável em 1.2s. Sem separar essas duas métricas por instância e por modelo carregado, você vai escalar horizontalmente quando o problema é vertical (batch size, quantização, tensor parallelism).

A **segunda camada** é a semântica de tokens. `ModelLatency` mede do request ao response, mas não distingue Time to First Token (TTFT) de Inter-Token Latency (ITL). Para um usuário interagindo com um chat financeiro, TTFT de 3s é inaceitável mesmo que o throughput total seja alto. O LMI (Large Model Inference) container do SageMaker expõe `TimeToFirstByte` via CloudWatch, mas só se você configurar `OPTION_OUTPUT_FORMATTER=jsonlines` e instrumentar o cliente para registrar o timestamp do primeiro chunk.

A **terceira camada** — e a mais negligenciada — é a qualidade semântica. O modelo pode estar respondendo rápido, com GPU eficiente, e ainda assim produzindo respostas com hallucinations, recusas excessivas ou drift de tom que viola políticas de compliance. Isso só é detectável com avaliação assíncrona pós-geração.

## Pipeline de Observabilidade LLM: Do GPU à Qualidade Semântica

Fluxo de dados de observabilidade para inferência LLM no SageMaker, cobrindo métricas de infraestrutura, tokens e qualidade semântica

### 🟧 AWS — Inference Layer

- API Gateway WAF + Auth (security)
- SageMaker Endpoint ml.g5.12xlarge LMI (ai)
- GPU Metrics Util / VRAM / TTFT (compute)

### 🟦 AWS — Telemetry Pipeline

- CloudWatch Metrics + Logs Insights (data)
- Kinesis Firehose log streaming (messaging)
- S3 raw inference logs (storage)
- OTEL Collector sidecar / Lambda (compute)

### 🟩 Quality Evaluation Layer

- Lambda Evaluator async quality scorer (compute)
- Bedrock Claude LLM-as-judge (ai)
- DynamoDB quality scores + trace (storage)

### 📊 Visualization & Alerting

- Grafana Unified Dashboard (external)
- SNS + PagerDuty SLO breach alerts (messaging)

### Fluxos

- client -> apigw: request HTTPS
- apigw -> sm_ep: invoke endpoint
- sm_ep -> gpu_metrics: métricas nativas
- gpu_metrics -> cw: CloudWatch agent
- sm_ep -> otel: TTFT / ITL spans
- otel -> cw: métricas customizadas
- sm_ep -> firehose: log de inferência
- firehose -> s3_logs: raw logs
- s3_logs -> eval_lambda: S3 event trigger
- eval_lambda -> bedrock_judge: prompt + resposta
- bedrock_judge -> ddb: score semântico
- ddb -> grafana: quality metrics
- cw -> grafana: infra + token metrics
- grafana -> sns: SLO breach

## Instrumentando o SageMaker LMI para Métricas de Token

O container LMI do SageMaker (baseado em DJL Serving) expõe métricas de token via o endpoint `/metrics` do servidor interno, mas elas não chegam automaticamente ao CloudWatch. A abordagem que funciona em produção é um **sidecar OTEL Collector** rodando no mesmo task definition (para SageMaker Multi-Container) ou como um Lambda invocado assincronamente pelo próprio endpoint via destino de invocação.

As métricas críticas a capturar são:
- `ttft_ms` (Time to First Token): P50/P95/P99 por modelo e por `request_type`
- `itl_ms` (Inter-Token Latency): distribuição — alta variância aqui indica contenção de GPU
- `tokens_per_second`: throughput real de geração, não confundir com invocações por segundo
- `prompt_tokens` e `completion_tokens`: essenciais para custo e para detectar prompt injection que infla o contexto
- `cache_hit_rate`: se você usa KV-cache com prefix caching (disponível no TGI e vLLM backends do LMI)

Para publicar essas métricas no CloudWatch com dimensões corretas, use `put_metric_data` com namespace `LLMInference/SageMaker` e dimensões `ModelName`, `EndpointName` e `InstanceType`. O custo de métricas customizadas no CloudWatch é $0.30/métrica/mês — com 6 métricas × 3 dimensões × 2 endpoints, estamos falando de ~$10/mês, completamente justificável.

Um detalhe crítico: configure `SAGEMAKER_CONTAINER_LOG_LEVEL=20` (INFO) no endpoint e ative `DataCaptureConfig` com `SamplingPercentage=100` para ambientes de staging e `SamplingPercentage=10` para produção com alto volume. Capturar 100% em produção com modelos grandes pode gerar custos de S3 e Firehose significativos.

## Playbook: Montando a Stack de Observabilidade LLM em 7 Passos

1. **1. Baseline de infraestrutura GPU** — Ative o CloudWatch Container Insights para SageMaker. Configure alertas em `GPUMemoryUtilization > 85%` (não em GPUUtilization) como sinal primário de pressão de memória. Crie um dashboard separando P50 e P99 de ModelLatency — se a diferença for maior que 5x, há contenção.

2. **2. Instrumentar TTFT no cliente** — Use streaming response (`stream=True` no boto3 `invoke_endpoint_with_response_stream`). Registre `time.time()` antes do invoke e no recebimento do primeiro chunk. Publique como métrica customizada `TTFT_ms` com dimensão `model_id`. SLA recomendado para chat financeiro: P95 < 1500ms.

3. **3. Pipeline de log de inferência para S3** — Configure `DataCaptureConfig` no endpoint com `S3OutputPath` apontando para bucket com SSE-KMS (chave gerenciada pelo cliente). Ative lifecycle policy: 90 dias em S3 Standard, depois Glacier Instant Retrieval. Isso satisfaz requisitos de auditoria de 7 anos sem custo linear.

4. **4. Avaliador assíncrono de qualidade** — Crie um Lambda com trigger em S3 (prefixo do DataCapture). O Lambda lê o par prompt/resposta, chama Bedrock Claude 3 Haiku (mais barato, suficiente para scoring) com um rubric de avaliação (factualidade, recusa indevida, tom). Armazene o score em DynamoDB com TTL de 180 dias. Custo estimado: $0.25 por 1000 avaliações com Haiku.

5. **5. Dashboard unificado no Grafana** — Use Amazon Managed Grafana com data sources CloudWatch (métricas de infra e token) e DynamoDB (via Lambda datasource ou AWS Data API). Organize em três rows: Infrastructure (GPU, latência), Token Economics (tokens/s, custo/request, cache hit rate) e Quality (scores de factualidade, taxa de recusa, drift semanal).

6. **6. SLOs e alertas de SLO burn rate** — Defina SLOs no CloudWatch com `ServiceLevelObjective`: TTFT P95 < 1500ms, availability > 99.5%, quality score > 0.75 (escala 0-1). Configure alertas de burn rate: 14x em 1h (critical) e 6x em 6h (warning). Roteie via SNS → PagerDuty com runbook link no alerta.

7. **7. Rastreabilidade de custo por caso de uso** — Adicione tag `use_case` no header de cada invocação (via API Gateway context) e propague para os logs de DataCapture. Use Cost Explorer com tag `use_case` para separar custo de instância GPU por produto. Isso é mandatório em ambientes financeiros onde cada linha de negócio precisa de chargeback.

> **KV-Cache e Prefix Caching: O Multiplicador de Eficiência Mais Subestimado:** Se você tem um system prompt fixo ou semi-fixo (ex: instruções de compliance, contexto do produto), ative prefix caching no backend vLLM do LMI. Em testes com prompts de 512 tokens de prefixo fixo, a redução de TTFT chega a 60-70% e o custo de GPU cai proporcionalmente. Monitore `cache_hit_rate` como KPI primário de eficiência — se estiver abaixo de 40% com prompts repetitivos, revise a estratégia de construção de contexto.

## LLM-as-Judge em Escala: Armadilhas e Calibração

O padrão de usar um LLM para avaliar outro LLM (LLM-as-judge) é poderoso, mas tem falhas específicas que eu já vi causar falsos positivos de qualidade em produção financeira.

O primeiro problema é o **viés de posição**: modelos como Claude tendem a avaliar melhor respostas mais longas, independentemente da precisão. Para mitigar, use rubricas estruturadas com scoring por dimensão (factualidade: 1-5, completude: 1-5, tom: 1-5) em vez de um score único. Inclua exemplos few-shot no prompt do juiz com casos de respostas curtas e corretas que devem pontuar alto.

O segundo problema é o **custo de avaliação em escala**. Avaliar 100% das inferências com Claude 3 Sonnet é proibitivo — para um endpoint processando 10.000 requests/dia com resposta média de 300 tokens, o custo seria ~$180/dia só em avaliação. A estratégia correta é **amostragem estratificada**: 100% de amostras com score de confiança baixo (detectado por entropia do logit, se disponível), 10% aleatório do restante, e 100% de qualquer request que acionou um guardrail.

O terceiro problema é **drift do juiz**: o modelo juiz também pode mudar com atualizações de versão do Bedrock. Use model versioning explícito (`anthropic.claude-3-haiku-20240307-v1:0`) e nunca `claude-3-haiku` sem versão em produção. Mantenha um golden dataset de 200 pares prompt/resposta com scores humanos para recalibrar o juiz mensalmente — isso é equivalente ao teste de regressão de um sistema de ML clássico.

Para ambientes com requisitos de soberania de dados (LGPD, regulação bancária brasileira), o pipeline de avaliação deve garantir que dados de clientes não saiam do boundary de VPC. Use Bedrock via VPC endpoint (`com.amazonaws.region.bedrock-runtime`) e nunca roteie logs de inferência por redes públicas.

## Benchmarks de Referência para LLM Inference Observability

- **60-70%** — Redução de TTFT com prefix caching ativo. Para prompts com prefixo fixo ≥ 256 tokens no vLLM backend do LMI
- **~$0.25** — Custo por 1000 avaliações LLM-as-judge com Claude 3 Haiku. Assumindo 300 tokens de prompt + 150 tokens de resposta do juiz
- **5x** — Razão P99/P50 de latência como limiar de alerta de contenção. Diferença maior que 5x indica GPU thrashing ou fila de requests

## Segurança e Governança no Pipeline de Observabilidade

O pipeline de observabilidade de LLM é, paradoxalmente, uma superfície de ataque. Os logs de DataCapture contêm prompts e respostas completas — em contexto financeiro, isso pode incluir dados de clientes, números de conta, análises de portfólio. Tratar esse pipeline com menos rigor do que o endpoint em si é um erro grave.

As camadas de controle que implemento obrigatoriamente:

**Criptografia em trânsito e em repouso**: O bucket S3 de DataCapture deve ter SSE-KMS com chave CMK separada do endpoint. A política da chave deve permitir decrypt apenas para a role do Lambda avaliador e para a role de auditoria — não para a role do endpoint em si (princípio de separação de duties).

**Mascaramento de PII antes da avaliação**: O Lambda avaliador deve passar o texto por Amazon Comprehend `detect_pii_entities` antes de enviar para o Bedrock judge. Substitua entidades PII por placeholders (`[ACCOUNT_NUMBER]`, `[CPF]`) no payload de avaliação. Isso garante que dados sensíveis não entrem no contexto do modelo avaliador.

**IAM com condições de contexto**: A role do Lambda avaliador deve ter `bedrock:InvokeModel` com condição `aws:SourceVpc` restrita ao VPC do pipeline. Adicione `aws:RequestedRegion` para garantir que invocações só ocorram na região primária — relevante para compliance com BACEN e LGPD que exigem processamento em território nacional.

**Auditoria de acesso aos logs**: Configure CloudTrail com S3 data events no bucket de DataCapture. Qualquer acesso ao bucket deve gerar evento auditável. Em ambientes com SOC 2 ou ISO 27001, isso é requisito de controle, não opcional.

Um detalhe que frequentemente é esquecido: o endpoint SageMaker em si deve ter `KmsKeyId` configurado no `ProductionVariant` para criptografar o volume EBS da instância. Sem isso, os pesos do modelo e o KV-cache ficam em disco não criptografado.

## Anti-Padrões Comuns em Observabilidade de LLM

- **Usar apenas ModelLatency como proxy de saúde**: ModelLatency mede o tempo total de inferência, não a experiência do usuário. TTFT pode estar 3x acima do SLA enquanto ModelLatency parece normal em P50.
- **Avaliar qualidade 100% em produção com modelos grandes**: Usar Claude 3 Sonnet para avaliar cada resposta em produção de alto volume custa mais do que o endpoint principal. Use Haiku com amostragem estratificada.
- **Não versionar o modelo juiz**: Atualizar o modelo de avaliação sem um golden dataset de referência invalida séries históricas de qualidade. Scores de semanas diferentes se tornam incomparáveis.
- **DataCapture sem lifecycle policy**: Logs de inferência crescem linearmente. Sem lifecycle para Glacier após 90 dias, o custo de S3 pode superar o custo de GPU em 6 meses.
- **Escalar horizontalmente antes de otimizar o batch**: Adicionar instâncias quando `GPUMemoryUtilization > 85%` sem primeiro testar `MaxConcurrentRequests` e `MaxBatchSize` no LMI. Muitas vezes, dobrar o batch size resolve o problema sem custo adicional.
- **Logs de inferência sem mascaramento de PII**: Enviar prompts brutos para o avaliador externo sem passar por Comprehend. Em contexto financeiro, isso é uma violação de LGPD e possivelmente de regulação bancária.

## Perguntas Frequentes

### Devo usar SageMaker ou Bedrock para inferência LLM em produção financeira?

Depende do controle necessário. Bedrock oferece observabilidade nativa menor (sem acesso a métricas de GPU, sem TTFT granular) mas simplifica compliance com modelos aprovados. SageMaker dá controle total sobre o stack de inferência, quantização, KV-cache e métricas — essencial se você tem SLAs de latência agressivos ou modelos customizados. Para modelos foundation sem fine-tuning, Bedrock com Guardrails é o ponto de partida. Para modelos fine-tuned ou com requisitos de latência P95 < 1s, SageMaker com LMI.

### Como detectar prompt injection via observabilidade?

Monitore `prompt_tokens` por request. Prompt injection que tenta injetar contexto adicional geralmente causa picos anômalos no tamanho do prompt. Configure alarme em `prompt_tokens > P99 + 3σ` como sinal de anomalia. Combine com Bedrock Guardrails (se usando Bedrock) ou com um classificador leve de pré-processamento no Lambda de entrada para SageMaker.

### Qual a diferença entre observabilidade de LLM e MLOps tradicional?

MLOps tradicional monitora drift de features e acurácia de predição — métricas determinísticas. LLM observability adiciona três dimensões não-determinísticas: qualidade semântica (que requer avaliação por outro modelo), latência de token (TTFT/ITL, não apenas latência total) e custo por token (que varia com o tamanho do contexto). O modelo de dados de observabilidade é fundamentalmente diferente: você precisa armazenar pares prompt/resposta, não apenas features numéricas.

### Como calcular o custo real por request em SageMaker?

Custo por request = (custo/hora da instância) / (requests/hora). Para um `ml.g5.12xlarge` a ~$5.67/hora processando 3600 requests/hora (1 req/s), o custo é ~$0.0016/request. Mas isso ignora utilização real. A métrica correta é: custo por token gerado = (custo/hora) / (tokens_per_second × 3600). Monitore `tokens_per_second` como KPI de eficiência e calcule o custo/token em tempo real no dashboard.

## Operacionalizando SLOs de Qualidade: O Que Ninguém Te Conta

Definir um SLO de qualidade para LLM é conceitualmente simples — 'quality score > 0.75 em 95% das respostas' — mas operacionalizar isso em CloudWatch com burn rate alerts exige uma arquitetura de dados específica.

O problema é que quality scores são produzidos assincronamente (pelo Lambda avaliador, segundos ou minutos após a resposta original), enquanto SLOs de latência são síncronos. Para unificar no mesmo framework de SLO, você precisa de uma **janela de avaliação diferida**: o SLO de qualidade opera sobre uma janela de 1 hora com delay de 5 minutos (tempo de avaliação), não em tempo real.

A implementação prática usa DynamoDB Streams: quando o Lambda avaliador escreve o score no DynamoDB, o stream aciona outro Lambda que publica a métrica `QualityScore` no CloudWatch com timestamp retroativo (usando `put_metric_data` com `Timestamp` explícito, não o tempo atual). Isso permite que o CloudWatch SLO calcule o burn rate corretamente sobre a janela histórica.

Um aspecto crítico para ambientes financeiros: o SLO de qualidade deve ter **thresholds diferenciados por tipo de request**. Uma pergunta sobre saldo de conta tem threshold de factualidade diferente de uma pergunta sobre estratégia de investimento. Use a dimensão `request_type` no CloudWatch para criar SLOs separados por categoria — isso é mais trabalhoso para configurar mas evita que respostas de baixo risco diluam o sinal de qualidade em requests de alto risco.

Por fim, documente o SLO de qualidade como um ADR (Architecture Decision Record) com a justificativa do threshold escolhido, o modelo juiz utilizado, o golden dataset de calibração e o processo de revisão trimestral. Em auditorias de compliance, a capacidade de demonstrar que você tem um processo formal de avaliação de qualidade de IA é tão importante quanto os números em si.

## Lentes do Well-Architected Framework

- **security**: SSE-KMS com CMK separado para DataCapture, IAM com condições de VPC e região, mascaramento de PII via Comprehend antes de qualquer avaliação externa, CloudTrail com S3 data events no bucket de logs.
- **reliability**: SLOs com burn rate alerts em três dimensões (disponibilidade, latência, qualidade), DataCapture com retry automático, Lambda avaliador com DLQ para garantir que nenhum par prompt/resposta seja perdido na avaliação.
- **performance**: Prefix caching para redução de TTFT, monitoramento de P99/P50 como sinal de contenção, otimização de batch size antes de escalonamento horizontal, cache_hit_rate como KPI primário de eficiência.

> **Nota do Curador:** Na prática, o maior gap que vejo em times implantando LLMs em produção financeira não é falta de métricas — é falta de propriedade clara sobre o que 'qualidade' significa para aquele caso de uso específico. Antes de montar qualquer pipeline de avaliação, eu forço uma sessão com o product owner e o time de compliance para definir o rubric de avaliação em linguagem natural, com exemplos concretos de respostas boas e ruins. Sem isso, você vai construir um avaliador tecnicamente impecável que mede a coisa errada. A lição mais dura que aprendi: um LLM com quality score de 0.85 que responde perguntas erradas com confiança é mais perigoso do que um com score de 0.65 que recusa quando não sabe.

## Veredicto: Observabilidade de LLM Não É Opcional em Produção Financeira

A stack descrita aqui — métricas de GPU + TTFT/ITL via OTEL, DataCapture para S3 com SSE-KMS, avaliação assíncrona com LLM-as-judge amostrado, SLOs em três dimensões no CloudWatch e dashboard unificado no Grafana — é o mínimo viável para operar um LLM em produção financeira com responsabilidade. O custo incremental dessa instrumentação é marginal comparado ao custo da instância GPU: estimativa de $50-150/mês para um endpoint de médio porte. O custo de não ter isso — uma resposta incorreta sobre dados de cliente que passa despercebida por semanas — é incalculável. Implante o playbook em staging primeiro, calibre o modelo juiz com um golden dataset humano, e só então leve para produção. Não pule a etapa de calibração.

**Rating:** Essential for financial-grade LLM produc

## Referências

- [SageMaker LMI Container — DJL Serving Documentation](https://docs.aws.amazon.com/sagemaker/latest/dg/large-model-inference-container-docs.html)
- [SageMaker Data Capture for Model Monitor](https://docs.aws.amazon.com/sagemaker/latest/dg/model-monitor-data-capture.html)
- [Amazon CloudWatch — Service Level Objectives](https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/CloudWatch-ServiceLevelObjectives.html)
- [Amazon Bedrock Guardrails](https://docs.aws.amazon.com/bedrock/latest/userguide/guardrails.html)
- [AWS Blog: Comprehensive observability for SageMaker AI LLM inference](https://aws.amazon.com/blogs/machine-learning/)
- [OpenTelemetry Collector — AWS CloudWatch Exporter](https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/exporter/awscloudwatchmetricsexporter)
- [LLM-as-Judge: Judging the Quality of LLM Outputs (Zheng et al., 2023)](https://arxiv.org/abs/2306.05685)
- [Amazon Managed Grafana — CloudWatch Data Source](https://docs.aws.amazon.com/grafana/latest/userguide/using-amazon-cloudwatch-in-AMG.html)
