Da chamada de função ao servidor HTTP — e o que os servidores de produção fazem que o seu não faz, e por que isso importa só a partir de certa escala.
Até aqui, cada script do laboratório carregava o modelo do zero. Para o gpt2-xl isso custa 7 segundos e 5,8 GB de RAM — toda vez.
Um servidor de inferência resolve isso invertendo a relação: o modelo fica carregado num processo que não morre, e qualquer coisa fala com ele por HTTP. Você ganha:
curl, uma planilha — qualquer coisa que fale HTTP..venv\Scripts\python.exe server\app.py -> http://localhost:8080/ painel -> http://localhost:8080/docs API interativa (Swagger)
São ~200 linhas em server/app.py, e ele expõe deliberadamente os mesmos endpoints do TGI e do TEI — de forma que o que você aprende aqui transfere direto para os servidores de produção.
| Endpoint | Origem | O que faz |
|---|---|---|
POST /generate | TGI | completa um texto; aceita os 5 modelos da família GPT |
POST /generate_stream | TGI | o mesmo, token a token, via Server-Sent Events |
POST /embed | TEI | vetor de 768 dimensões por texto (mean pooling normalizado) |
POST /predict | TEI | classificação — o detector RoBERTa devolvendo Fake/Real |
GET /info | TGI | modelos disponíveis e limites |
GET /health | TGI | checagem de vida |
# gerar texto curl -X POST http://localhost:8080/generate \ -H "Content-Type: application/json" \ -d '{"inputs": "The capital of France is", "model": "gpt2-medium", "parameters": {"max_new_tokens": 20, "do_sample": false}}' # resposta: # {"generated_text": " Paris, and the capital of France is Paris.", # "details": {"model": "gpt2-medium", "prompt_tokens": 5, # "generated_tokens": 15, "seconds": 2.649, # "tokens_per_second": 5.7}} # classificar com o detector curl -X POST http://localhost:8080/predict \ -H "Content-Type: application/json" \ -d '{"inputs": "I went to the market and the tomatoes were a disgrace."}' # resposta: # [[{"label": "Fake", "score": 0.309467}, # {"label": "Real", "score": 0.690533}]]
Gere texto com /generate usando o gpt2-large, e passe o resultado para /predict. Foi assim que os números da apostila 12 foram produzidos: o detector devolveu P(Fake) = 0,9997.
Um decoder escreve, um encoder julga, os dois no mesmo processo, conversando por HTTP. É todo o curso rodando em duas requisições.
O text-generation-inference é o servidor de produção do Hugging Face para modelos generativos. Escrito em Rust (o roteador) mais Python (o motor), ele voltou a ser Apache 2.0 em abril de 2024, depois de um período com licença restritiva.
Ele não faz nada de diferente conceitualmente do nosso servidor. Ele faz as mesmas coisas muito melhor, através de quatro técnicas que valem conhecer pelo nome:
| Técnica | O problema que resolve |
|---|---|
| Continuous batching | Nosso servidor atende uma requisição por vez. O TGI junta requisições de vários usuários num único lote — e, crucialmente, troca requisições dentro do lote conforme umas terminam e outras chegam, sem esperar o lote inteiro acabar. É o maior ganho de throughput dos quatro. |
| PagedAttention | O cache KV (apostila 04) cresce a cada token e tem tamanho imprevisível. Alocá-lo em blocos contíguos desperdiça memória enorme. O PagedAttention o gerencia em páginas, como memória virtual de sistema operacional. Veio do projeto vLLM. |
| Kernels fundidos | FlashAttention e afins: implementações CUDA que calculam a atenção sem nunca materializar a matriz |
| Tensor parallelism | Dividir um modelo grande demais para uma GPU entre várias, com as camadas fatiadas. |
O TGI é distribuído como imagem Docker Linux e seu motor depende de kernels CUDA. O laboratório foi feito para uma máquina comum: Windows, sem GPU NVIDIA (uma placa AMD de entrada não tem suporte oficial a ROCm) e sem Docker nem WSL.
Fazer o TGI rodar aqui exigiria instalar WSL2 e Docker Desktop — algumas horas e ~15 GB de disco, dos quais você tem pouco sobrando. E, no fim, você teria o mesmo GPT-2 respondendo, dentro de um contêiner, sem GPU para usar os kernels que justificam o TGI.
As quatro técnicas acima só compensam a partir de dezenas de requisições simultâneas. Para uma pessoa aprendendo, com um cliente por vez, elas não fazem diferença nenhuma. Foi por isso que escrevemos os mesmos endpoints em 200 linhas de Python — e a troca aqui é a favor do aprendizado, porque você consegue ler este servidor inteiro.
Se um dia você precisar servir modelos em produção, com GPU NVIDIA e Linux: use o TGI ou o vLLM. Não escreva o seu.
O text-embeddings-inference é o irmão do TGI para encoders, também Apache 2.0 desde abril de 2024. E ele traz um detalhe relevante para nós:
Ao contrário do que se costuma supor, o TEI não serve apenas embeddings. Ele tem um endpoint /predict para modelos de sequence classification — exatamente a categoria dos nossos roberta-*-openai-detector. Ou seja: o RoBERTa, que não roda no Ollama por falta de suporte a cabeça de classificação (como a apostila 00 explica), rodaria no TEI sem problema.
Isso vale como correção de uma simplificação comum: "encoders não têm servidor de inferência" é falso. O ecossistema tem ferramenta para os dois galhos da árvore — é só que a do galho encoder é menos famosa.
O TEI tem um backend em Rust puro (candle) que pode compilar para CPU, então ele é tecnicamente menos hostil ao Windows que o TGI. Mas o Windows não é plataforma suportada, e a compilação é uma aposta. Nosso /predict faz o mesmo trabalho hoje.
| Situação | Use |
|---|---|
| Aprendendo, um usuário, CPU | este servidor — legível, sem dependências pesadas |
| Protótipo, poucos usuários | este servidor, ou transformers direto |
| Produção, decoder, GPU NVIDIA, Linux | vLLM ou TGI |
| Produção, embeddings ou classificação | TEI |
| Rodar LLM no desktop, sem programar | Ollama ou llama.cpp (mas não servem encoders) |
| Máxima velocidade em CPU | llama.cpp com pesos quantizados em GGUF |
O gpt2-xl gera a 3,6 tokens/s nesta máquina. Uma resposta de 100 tokens leva ~28 segundos. Sem streaming, o usuário encara uma tela parada por 28 segundos. Com streaming, ele vê a primeira palavra em 300 ms e o texto crescendo.
O tempo total é idêntico. A percepção é completamente diferente — e é por isso que todo assistente de IA faz streaming. No nosso servidor, o TextIteratorStreamer roda a geração numa thread separada e vai emitindo eventos SSE.
/embedO encoder devolve um vetor por token. Para ter um vetor de frase, tiram-se a média sobre os tokens reais (ignorando padding) e normaliza-se para norma 1:
v = (h * mascara).sum(1) / mascara.sum(1) # média só dos tokens reais v = F.normalize(v, dim=-1) # norma 1
A normalização é o que permite comparar vetores por produto interno — que, para vetores de norma 1, é a similaridade de cosseno. É assim que bancos de dados vetoriais fazem busca rapidamente.
Uma nota: usamos mean pooling, não o <s>. Para modelos treinados especificamente para embeddings (Sentence-BERT e derivados), o mean pooling costuma ser melhor. Para classificação, usa-se o <s> — como o /predict faz.
# terminal 1 — deixe rodando .venv\Scripts\python.exe server\app.py # terminal 2 .venv\Scripts\python.exe lab\11_api.py
/generate → /predict, medindo quanto o detector acusa./embed — e falha. Leia a caixa abaixo antes de rodar; esse fracasso é a parte mais instrutiva do lab.Montei um acervo de 6 frases e perguntei "Where is the famous iron tower located?". O sistema devolveu a frase sobre a floresta amazônica. E o mais revelador: todas as similaridades ficaram entre +0,99 e +1,00. Segundo esses vetores, a Torre Eiffel e a Amazônia são 99,8% parecidas.
Isso se chama anisotropia. Em vez de se espalharem pela esfera de 768 dimensões, os vetores colapsam num cone estreito — todos apontando quase para a mesma direção. O cosseno perde poder de discriminar.
Centralizar ajuda parcialmente. Subtraindo a média dos vetores antes de comparar, as notas voltam a se espalhar (de +0,99 para uma faixa de +0,54 a +0,79) e o sistema passa a acertar parte das perguntas. Mas ainda erra — porque isso trata o sintoma.
A causa são duas, empilhadas:
roberta-base-openai-detector, fine-tunado para uma tarefa binária. O ajuste reorganizou as representações em torno do eixo "humano vs máquina" e descartou estrutura semântica que não servia para isso.Foi exatamente para resolver isso que criaram o Sentence-BERT (Reimers & Gurevych, 2019): pegar um BERT e treiná-lo com um objetivo de similaridade, usando pares de frases parecidas e diferentes. Modelos de embedding como E5, BGE e gte fazem alguma variante disso.
A lição: "é um encoder" não significa "serve para embeddings". O objetivo de treino determina para que as representações servem. A mecânica que o lab mostra (vetorizar → normalizar → produto interno → ordenar) está correta e é a de qualquer banco vetorial. Troque o modelo por um treinado para embeddings e funciona.
http://localhost:8080/docs e dispare uma geração pelo formulário. Mude temperature e seed e observe o que acontece com a reprodutibilidade.seed fixo e do_sample=true, a saída se repete? E com seed nulo? Por quê?/generate carrega o modelo na primeira chamada e o mantém em cache. Faça duas chamadas ao gpt2-xl e compare os tempos. Onde foram os 7 segundos?As fontes primárias dos conceitos deste capítulo. Os números do texto foram medidos no laboratório; as ideias vêm daqui.
Os termos técnicos têm aqui o sentido de aprendizado de máquina. O glossário do curso mostra esse sentido ao lado dos outros que as mesmas palavras têm na TI.
A apostila 15 é o mapa de referência: cada script, cada comando, cada arquivo — o guia para você voltar depois.