Desafios de concorrência em CPU

O que acontece quando o pipeline aprende a buscar.

A transição de uma interface de inferência puramente conversacional para um pipeline híbrido — com RAG persistente (ChromaDB + Ollama) e busca web em tempo real — impôs desafios operacionais severos a uma infraestrutura focada em CPU. Aqui estão os números e as correções.

Ilustração do Projeto Nyeera: um vulto sopra um sopro que se transforma em símbolos de conhecimento e circuitos digitais

Da conversa simples ao pipeline híbrido

Somar RAG persistente e busca web em tempo real a uma interface antes puramente conversacional expôs gargalos de rede interna no Docker, limites de I/O em lote e retenção de memória de modelos — todos amplificados por rodar inteiramente em CPU.

Sintoma
Erros de DNS interno
Falhas de resolução entre contêineres causadas por redes Docker isoladas entre si.
Sintoma
httpx.ReadTimeout
Timeouts de conexão durante a indexação, com centenas de trechos enviados simultaneamente ao endpoint de embeddings.
Sintoma
Falhas de conexão por saturação de CPU
Concorrência entre modelo de embeddings e LLM de chat disputando o mesmo recurso de processamento.

Quatro eixos de correção no pipeline

Para viabilizar a arquitetura e eliminar os gargalos operacionais, quatro frentes principais foram trabalhadas — da rede interna do Docker ao ciclo de vida do banco vetorial.

Rede

Unificação de redes no Docker Compose

Remoção da diretiva external: true isolada do Streamlit, consolidando ollama, kafka, airflow e streamlit-app na mesma rede de bridge. Endereçamento migrado de localhost:11434 para http://ollama:11434.

Ingestão

Processamento de embeddings em lotes

A ingestão de PDFs enviava centenas de trechos simultâneos ao endpoint de embeddings, gerando timeouts. A rotina foi reescrita para fatiar as chamadas em lotes de 10 chunks, com progresso via st.progress.

Ollama

Multi-model concurrency & keep-alive

Por padrão, o Ollama carrega um modelo por vez, descarregando o de embeddings para subir o de chat (e vice-versa). Configuramos OLLAMA_NUM_PARALLEL=2, OLLAMA_MAX_LOADED_MODELS=2 e OLLAMA_KEEP_ALIVE=30m.

ChromaDB

Resiliência & limpeza de cache

Correção do ciclo de vida da coleção vetorial no Streamlit via @st.cache_resource.clear(), evitando referências órfãs que quebravam a interface com exceções NotFoundError.

O mesmo hardware, três cenários de carga

Tempos reais de execução medidos em ambiente local, inteiramente em CPU, comparando inferência pura, RAG vetorial e busca web em tempo real.

Time to First Token (TTFT)
Base LLM pura (DeepSeek · abr/2023)17.52 s
Base vetorial RAG (ChromaDB + Ollama)147.01 s
Internet / DuckDuckGo + LLM152.16 s
Tempo efetivo de streaming (geração de tokens)
Base LLM pura200.25 s
Base vetorial RAG196.52 s
Internet / DuckDuckGo + LLM199.91 s
Cenário de execução TTFT Tempo total de geração Streaming efetivo Sobrecarga de pré-processamento
Base LLM pura17.52 s217.77 s (~3.6 min)200.25 sBaseline (prompt evaluation simples)
Base vetorial RAG147.01 s343.53 s (~5.7 min)196.52 s+129.49 s (vetorização da query + contexto denso)
Internet / DuckDuckGo + LLM152.16 s352.07 s (~5.8 min)199.91 s+134.64 s (I/O web + parsing de snippets + prompt denso)

Consistência na escrita, gargalo na largada

Separar o "tempo até começar a responder" do "tempo de escrita da resposta" revela onde a CPU realmente sofre.

Consistência na velocidade de escrita O tempo dedicado estritamente à escrita e ao streaming das respostas manteve-se uniforme nos três cenários — 200.25 s, 196.52 s e 199.91 s — o que demonstra que a taxa de geração de tokens por segundo em CPU é estável uma vez iniciado o fluxo de resposta.
  • 01 Prompt Evaluation Cost (KV Cache)

    Injetar 2 a 3 chunks de documentos ou múltiplos snippets de busca web adiciona milhares de caracteres ao system prompt. O processador precisa calcular os tensores de atenção de todo o contexto antes de emitir o primeiro token.

  • 02 Ciclo de embeddings síncrono

    No cenário com ChromaDB, a CPU executa primeiro o modelo de embeddings para codificar o vetor da pergunta, e só depois entrega a fila de execução para o modelo generativo.

O gargalo do Time to First Token (TTFT) Na inferência direta sem fontes externas, a resposta começou em 17.52 segundos. Na busca vetorial e na busca web, o TTFT saltou para ~147 s e ~152 s, respectivamente — um acréscimo de mais de 700% na latência inicial, concentrado inteiramente no início da resposta.

Reduzir o TTFT sem perder a fundamentação dos dados

Três frentes para atacar o gargalo identificado, sem abrir mão do enriquecimento por RAG ou busca web.

  • 01 Limitar a janela de contexto (num_ctx)

    Reduzir a alocação padrão para num_ctx: 2048 nas opções do Ollama, aliviando o uso de cache na memória.

  • 02 Truncamento de chunks RAG

    Priorizar a recuperação de top-2 trechos de tamanho moderado (600 a 800 caracteres) em vez de lotes volumosos de texto.

  • 03 Embeddings em serviço isolado ou offload

    Delegar a geração de vetores a bibliotecas leves em C++ (ex: FastEmbed / ONNX Runtime), para não concorrer com o processo principal do Ollama.

As recomendações já mostram resultado

Duas rodadas de medição em produção, com as recomendações aplicadas de forma incremental: primeiro FastEmbed + reserva de um core de CPU para o SO, depois a redução do tamanho dos chunks recuperados por cima disso.

Time to First Token (TTFT)
Antes (base vetorial RAG)147.01 s
+ FastEmbed & CPU threads106.60 s
+ Redução de chunks118.34 s
Tempo total de execução
Antes (base vetorial RAG)343.53 s
+ FastEmbed & CPU threads296.39 s
+ Redução de chunks263.55 s
Tempo efetivo de streaming (pós-TTFT)
Antes (base vetorial RAG)196.52 s
+ FastEmbed & CPU threads189.79 s
+ Redução de chunks145.21 s
Rodada Pergunta feita Início da resposta Concluído em Latência (TTFT) Tempo total
CPU Threads & FastEmbed09/09/2026 07:17:2207:19:0807:22:18106.60 s296.39 s
+ Redução de Chunks09/09/2026 07:32:0607:34:0507:36:30118.34 s263.55 s
O que os números mostram Só com FastEmbed e a reserva de um core de CPU para o SO, o TTFT caiu de 147.01 s para 106.60 s (‑27.5%) — o ganho esperado ao tirar a concorrência entre embeddings e LLM pela mesma CPU. A redução dos chunks, por sua vez, não melhorou o TTFT na mesma medida (subiu para 118.34 s frente à rodada anterior), mas cortou o tempo de streaming de 189.79 s para 145.21 s, puxando o tempo total de 296.39 s para 263.55 s — uma queda de 23.3% frente ao cenário original. São medições pontuais de produção, não médias de múltiplas execuções, mas a direção de cada mudança é consistente com o que a análise previu.

Concorrência é um problema de arquitetura, não só de hardware.

O gargalo não estava na CPU em si, mas no jeito como os serviços disputavam ela ao mesmo tempo. Ajustar a orquestração recuperou a viabilidade do pipeline híbrido.