SSD Nodes Learn Hosting plans →
Guias Matt ConnorPor Matt Connor · Atualizado 2026-10-26

Por que seu LLM trava com 5 usuários simultâneos

Entenda por que 1 usuário funciona e 5 ficam lentos: batching, cache KV, prefill e fila definem quantas pessoas seu servidor LLM atende ao mesmo tempo.

Por que um LLM auto-hospedado fica mais lento quando chegam mais utilizadores?

Quando um LLM auto-hospedado fica bloqueado com 5 utilizadores em simultâneo, a causa habitual é o servidor estar configurado para gerar uma resposta de cada vez, enquanto os outros quatro ficam numa fila. A documentação do Ollama é clara sobre a predefinição: OLLAMA_NUM_PARALLEL é "o número máximo de pedidos paralelos que cada modelo processa ao mesmo tempo, por predefinição 1". Não há nenhuma falha, e o software de serving pode processar vários pedidos em simultâneo. Quatro das cinco pessoas estão à espera da sua vez porque uma definição assim o determinou.

A solução raramente é usar um servidor maior. É usar um motor de serving que processe muitos pedidos no modelo durante a mesma passagem forward, além de memória livre suficiente para manter as conversas de todos enquanto isso acontece. As duas partes são importantes, mas é a segunda que define efetivamente o seu limite.

As duas fases pelas quais cada pedido passa

O prefill lê todo o prompt de uma só vez e constrói a cache de atenção correspondente. Todos os tokens do prompt passam pelo modelo em conjunto. Por isso, o prefill é uma grande multiplicação de matrizes e fica limitado pela capacidade de processamento aritmético. O decode escreve a resposta um token de cada vez. Para cada token, é necessário ler novamente todos os pesos do modelo a partir da memória, enquanto o processamento aritmético desse único token é reduzido. O decode fica limitado pela largura de banda da memória.

Essa assimetria é precisamente o motivo pelo qual o batching funciona. O decode de um utilizador lê, por exemplo, 5 GB de pesos por token e deixa a maior parte das unidades aritméticas sem utilização. Ao adicionar um segundo pedido, o motor lê os mesmos 5 GB uma vez e calcula depois dois tokens a partir deles. O segundo utilizador quase não acrescenta tempo. Processar os pedidos estritamente um após outro elimina essa vantagem.

Dois números descrevem a experiência do utilizador. TTFT (tempo até ao primeiro token) é o tempo de espera na fila mais o prefill. ITL (latência entre tokens) é o intervalo entre os tokens transmitidos e é determinado pelo decode. Um servidor lento costuma ser lento num destes aspetos, e as correções não são as mesmas. Vale a pena determinar qual deles está a causar o problema antes de alterar qualquer definição. medir o prefill e o decode separadamente é a forma de o descobrir.

O batching estático faz todos esperarem pela resposta mais lenta

O batching estático é a versão básica. É o que obtém quando agrupa pedidos manualmente no código da aplicação. O motor recolhe N pedidos, executa-os em conjunto e mantém todas as posições ocupadas até terminar a geração mais longa do grupo.

Um utilizador que pede um resumo de 1.200 tokens mantém quatro respostas de uma linha bloqueadas no batch, porque o batch não liberta nenhuma posição até terminar o seu membro mais lento.

Daqui resultam dois custos. As sequências concluídas continuam a ocupar posições sem executar trabalho útil, pelo que o throughput efetivo diminui quando os comprimentos das respostas variam. Os comprimentos das respostas variam bastante nos chats. Um pedido que chega um passo depois da formação do batch espera que todo o batch termine antes de sequer iniciar o prefill. Isto faz com que o seu TTFT seja determinado pelo texto longo de outro utilizador.

O batching contínuo admite e retira pedidos a cada token

O batching contínuo agenda as operações ao nível de cada passo individual da descodificação. Depois de cada passo, o agendador remove as sequências que acabaram de emitir o respetivo token de paragem e admite os pedidos em espera nos slots livres. Uma resposta que termina no passo 40 liberta o seu slot no passo 40, e não no fim de um batch.

Isto não é uma funcionalidade exótica. llama-server documenta -cb, --cont-batching como «se deve ativar o batching contínuo (também designado batching dinâmico) (predefinição: ativado)», inclui «Descodificação paralela com suporte para vários utilizadores» entre as funcionalidades e, desde uma alteração integrada em novembro de 2025, inicia por predefinição com 4 slots (-np tem o valor predefinido -1, automático). O vLLM foi desenvolvido com base nesta ideia. O Ollama também serve pedidos em paralelo. A predefinição limita o número a um, e é por isso que tantas pessoas concluem que o seu hardware não consegue executar pedidos em simultâneo, quando foi a configuração que o impediu.

Os resultados publicados sobre batching contínuo são normalmente medidos em placas de datacenter que têm capacidade de processamento disponível e dezenas de gigabytes para a cache. O padrão desses resultados aplica-se ao seu sistema. A escala não, e a secção sobre memória abaixo explica porquê.

O prefill compete com o decode pelo mesmo poder de processamento

Quando chega um novo pedido enquanto quatro respostas estão a ser transmitidas, o seu prompt tem de passar primeiro pelo prefill, que exige muito poder de processamento. Se o agendador atribuir a esse prefill um passo próprio, os quatro utilizadores que estão a receber respostas não recebem nenhum token durante esse período. Num prompt longo, isto provoca uma pausa visível em todas as janelas abertas. É a interrupção a que as pessoas se referem quando dizem que o servidor engasga sempre que outra pessoa clica em enviar.

O prefill em blocos divide um prompt longo em partes e mistura cada parte no mesmo passo que os decodes em execução. O guia de ajuste do vLLM declara diretamente o compromisso: orçamentos de blocos menores "achieve better ITL because there are fewer prefills slowing down decodes", enquanto valores maiores "achieve better time to first token (TTFT) as you can process more prefill tokens in a batch". Está a escolher qual experiência proteger: a pessoa que espera pelo início da resposta ou as pessoas que veem o texto a ser transmitido.

O comprimento do prompt determina o impacto. Um prompt com 6,000 tokens e uma resposta com 200 tokens representa 6,000 tokens de trabalho de prefill contra 200 passos de decode. O chat com geração aumentada por recuperação e os prompts de sistema longos colocam ambos o sistema nesse regime. Assim, o prefill deixa de ser um custo residual e passa a ser aquilo por que os utilizadores esperam. O cache de prefixo ajuda quando a parte longa se repete: o vLLM expõe --enable-prefix-caching, que reutiliza o cache de um prefixo de prompt partilhado em vez de o recalcular para cada pedido.

A memória que se esgota primeiro é a cache KV

Cada token de cada conversa ativa deixa um vetor de chave e um vetor de valor em cada camada do modelo. Essa é a cache KV (cache de chave/valor). É ela que permite ao decode evitar recalcular todo o prompt para cada token novo. O número de valores colocados em cache por token é definido pela arquitetura do modelo: 2 (uma chave e um valor) vezes o número de camadas, vezes o número de cabeças de chave/valor, vezes a dimensão da cabeça. Consulte esses valores no config.json do modelo. Multiplique-os pelo número de bytes por valor para obter o tamanho. Este último fator é uma definição do runtime, não uma característica do modelo: o llama-server armazena a cache em f16 (2 bytes) por predefinição e aceita tipos menores, como q8_0, através de -ctk e -ctv.

Faça este cálculo uma vez e o limite deixa de ser misterioso. Um modelo típico de 8B, com 36 camadas, 8 cabeças de chave/valor e dimensão de cabeça de 128, usando a cache em 16 bits, consome 2 36 8 128 2 bytes por token. Isso corresponde a 147,456 bytes, ou cerca de 144 KiB. Uma conversa com 8,192 tokens precisa, portanto, de aproximadamente 1.2 GB de cache. Cinco conversas precisam de aproximadamente 6 GB, além dos pesos. Essa é a resposta real para quantos utilizadores cabem.

A simultaneidade multiplica o contexto, e as ferramentas indicam isso explicitamente. A FAQ do Ollama diz: "O processamento paralelo de pedidos para um determinado modelo aumenta o tamanho do contexto pelo número de pedidos paralelos. Por exemplo, um contexto de 2K com 4 pedidos paralelos resulta num contexto de 8K e numa alocação adicional de memória." A RAM necessária escala com OLLAMA_NUM_PARALLEL multiplicado por OLLAMA_CONTEXT_LENGTH. No llama-server, o número predefinido de slots em modo automático executa todos os slots numa única cache KV unificada. Assim, todo o contexto -c é um único pool partilhado pelos slots. Defina -np manualmente e a cache unificada fica desativada, exceto se também passar --kv-unified. Nesse caso, o contexto solicitado com -c é distribuído pelos -np slots, e aumentar apenas o número de slots reduz o que cada pedido pode armazenar. Consulte o contexto por slot no log de arranque, em vez de o assumir.

O vLLM faz a pré-alocação. --gpu-memory-utilization (predefinido como 0.92) é "a fração da memória da GPU a utilizar pelo executor do modelo". Exceto se definir --kv-cache-memory-bytes, o vLLM infere o tamanho da cache KV a partir dessa fração. O orçamento restante depois de contabilizar os pesos e a memória de trabalho medida pelo engine torna-se o pool KV paginado. Quando esse pool fica sem espaço, o scheduler remove uma requisição em vez de a falhar:

WARNING 05-09 00:49:33 scheduler.py:1057] Sequence group 0 is preempted by PreemptionMode.RECOMPUTE mode because there is not enough KV cache space.

No engine V1 do vLLM, o modo de preempção predefinido é RECOMPUTE. Por isso, uma requisição removida descarta a sua cache e volta a fazer o prefill quando é aceite novamente. Esse trabalho é executado duas vezes. A documentação avisa que "a preempção e o recálculo podem afetar negativamente a latência de ponta a ponta". Esta linha do log é a melhor explicação para um utilizador ter esperado muito mais do que os restantes enquanto a média parecia normal. Defina disable_log_stats=False para registar o contador acumulado ou consulte o contador de preempções nas métricas Prometheus expostas pelo vLLM.

O que muda com 2, 5 e 20 utilizadores concorrentes

Dois utilizadores. Num GPU com cache disponível, o impacto é quase invisível, porque o segundo fluxo de decodificação acompanha o primeiro com muito pouco tempo adicional. Num VPS apenas com CPU e 4 a 8 GB de RAM, não é gratuito: os dois fluxos partilham os mesmos poucos vCPUs e a mesma largura de banda da RAM. Assim, cada utilizador obtém aproximadamente metade dos tokens por segundo, e a procura de cache duplica face a um orçamento muito menor.

Cinco utilizadores. É aqui que as predefinições deixam de ser suficientes, e o problema começa por ser uma questão de fila. Com OLLAMA_NUM_PARALLEL em 1, quatro pessoas esperam por quem pediu a resposta longa, e cada uma vê a velocidade normal quando chega a sua vez. Se aumentar a contagem de processos paralelos, o problema muda de natureza: cinco slots com um contexto de 8K cada exigem uma cache de 40K tokens. Se não couber na VRAM, o motor transfere camadas para a RAM do sistema. Se também não couber na RAM, o sistema começa a usar swap e os tokens por segundo entram em colapso.

Vinte utilizadores. Vinte pessoas numa interface de chat normalmente não correspondem a vinte pedidos concorrentes, e esta é a ideia mais importante a compreender antes de comprar hardware. Uma pessoa lê uma resposta e demora 20 a 60 segundos entre interações, por isso a maior parte da sessão está inativa. Vinte agentes ou vinte trabalhos de sumarização de documentos correspondem a vinte fluxos reais sem qualquer tempo de inatividade. Isso exige uma máquina diferente. Um programador que tenha apontado um agente de programação para o seu próprio servidor Ollama está mais próximo do segundo caso do que do primeiro, porque o agente continua a enviar pedidos enquanto a tarefa decorre e não inclui as pausas de leitura que uma pessoa faz.

Os seus utilizadores fazem pedidos simultâneos ou estão apenas autenticados?

Calcule os pedidos em curso antes de dimensionar qualquer componente. A aritmética é simples: pedidos em curso é igual ao número de utilizadores, multiplicado pelos segundos necessários para gerar cada resposta, dividido pelos segundos entre turnos.

  1. Meça primeiro a velocidade do seu próprio fluxo único, tanto no prefill como no decode. Não use um valor obtido com a placa de outra pessoa: meça tokens por segundo no seu próprio sistema e use o resultado obtido.
  2. Estime o ciclo de utilização. Vinte utilizadores de chat, 12 segundos de geração por turno e um turno a cada 90 segundos resultam em 20 * 12 / 90, ou seja, aproximadamente 2.7 pedidos em curso.
  3. Defina o número de slots ligeiramente acima desse valor e confirme-o em relação à memória: o número de slots multiplicado pelo contexto por pedido tem de caber nos tokens de cache que estão realmente disponíveis.
  4. Mantenha a fila curta para que o excesso falhe rapidamente e de forma visível.

Os tokens de cache disponíveis correspondem à memória livre depois de carregar os pesos, dividida pelo custo por token indicado na secção anterior. Uma placa de 24 GB que execute um modelo 8B em 16-bit utiliza cerca de 16 GB para os pesos e tem, no máximo, aproximadamente 6 GB de cache utilizável com a utilização predefinida; o valor é um pouco menor depois de reservar a memória de trabalho, o que equivale a cerca de cinco conversas de 8K. Para suportar mais conversas, reduza o contexto por pedido ou armazene a cache em 8-bit (llama-server utiliza --cache-type-k q8_0). Ambas as opções aumentam a simultaneidade à custa de outra capacidade. Vale a pena compreender esta troca antes de investir em hardware: quando um GPU VPS atinge o ponto de equilíbrio face aos tokens de API.

Quando os valores predefinidos do Ollama deixam de ser suficientes

Aumente a contagem de execuções paralelas através da unidade do serviço, porque uma exportação feita na shell não chega a um daemon gerido pelo systemd.

sudo systemctl edit ollama.service
[Service]
Environment="OLLAMA_NUM_PARALLEL=4"
Environment="OLLAMA_CONTEXT_LENGTH=8192"
Environment="OLLAMA_MAX_QUEUE=64"
sudo systemctl daemon-reload
sudo systemctl restart ollama
systemctl show ollama --property=Environment
ollama ps

systemctl show deve apresentar as três variáveis que acabou de definir. Se isso não acontecer, o drop-in não foi guardado, e nada do que fizer depois terá efeito. ollama ps lista então o modelo carregado com um tamanho superior ao dos pesos isolados, porque quatro slots com 8,192 tokens reservam 32,768 tokens de cache adicional. Uma coluna PROCESSOR que mostre parte do modelo na CPU quando esperava que estivesse todo na GPU significa que pediu mais cache do que a placa tinha disponível. Reduza um dos dois valores. Reduzir o contexto é normalmente a opção mais segura, mas uma janela demasiado pequena trunca silenciosamente os prompts longos em vez de gerar um erro. Por isso, vale a pena dimensionar num_ctx de forma deliberada em vez de o reduzir até o modelo caber.

O valor predefinido da fila merece uma segunda análise. O Ollama coloca em fila até OLLAMA_MAX_QUEUE pedidos, e "o valor predefinido é 512". Depois desse limite, responde "com um erro 503 que indica que o servidor está sobrecarregado". Uma fila com profundidade 512 num servidor que atende quatro pedidos de cada vez é uma promessa que não pode cumprir, porque o cliente na posição 300 excede o tempo limite muito antes de chegar a sua vez. Uma fila curta devolve um erro que a aplicação pode repetir ou comunicar, o que é preferível a um indicador de espera que nunca termina.

Teste na prática. Envie dois pedidos exatamente ao mesmo tempo a partir de dois terminais e observe ambos. Se o segundo não produzir nada até o primeiro terminar, a configuração de execução paralela não foi aplicada.

Quando um motor de serving dedicado começa a compensar

O vLLM compensa a configuração adicional quando tem uma GPU com capacidade disponível e mais de aproximadamente quatro pedidos efetivamente em processamento. O seu scheduler trabalha por token, a cache é paginada para reutilizar fragmentos livres e a VRAM disponível é convertida em concorrência, em vez de ficar inutilizada. Em outubro de 2026, a instalação e o arranque documentados para GPU requerem três comandos. vllm download-kernels obtém kernels pré-compilados que, de outro modo, o vLLM descarregaria e compilaria durante o arranque:

uv pip install vllm --torch-backend=auto
vllm download-kernels
vllm serve Qwen/Qwen2.5-1.5B-Instruct
curl http://localhost:8000/v1/chat/completions \
    -H "Content-Type: application/json" \
    -d '{
        "model": "Qwen/Qwen2.5-1.5B-Instruct",
        "messages": [{"role": "user", "content": "Say hello."}]
    }'

Uma resposta que contenha um array choices significa que o servidor está ativo e que o modelo foi carregado. Sob carga, os dois parâmetros relevantes são --max-num-seqs, o "número máximo de sequências a processar numa única iteração", e --max-num-batched-tokens, o "número máximo de tokens que podem ser processados numa única iteração". O primeiro limita a concorrência. O segundo define o orçamento de prefill em blocos descrito anteriormente.

Com menos de aproximadamente quatro pedidos em processamento, o vLLM acrescenta complexidade e oferece poucos benefícios. Já não é necessária uma GPU: em outubro de 2026, o guia de instalação para CPU documenta "inferência e serving básicos de modelos" em CPUs x86 e Arm, com wheels x86 pré-compiladas desde a versão 0.17.0 e uma imagem Docker para CPU. No entanto, o comportamento relativo à memória mantém-se. Em CPU, aplica a mesma fração de utilização de 0.92 à RAM do sistema e atribui à cache KV o que restar depois de reservar espaço para os pesos. Por isso, por predefinição, ocupa a maior parte da memória da máquina durante o arranque, a menos que limite a cache com --kv-cache-memory-bytes. Esta não é uma boa opção numa VPS com 4 a 8 GB. Nesse caso, a solução é um modelo menor, um contexto mais curto e uma fila que possa controlar. como o Ollama e o vLLM diferem enquanto motores de serving explica a escolha em detalhe, e executar o Qwen 3 8B numa VPS mostra os requisitos de um modelo de tamanho intermédio antes de adicionar um único utilizador extra.

O compromisso que o folclore esconde

O batching contínuo aumenta o throughput total e normalmente também melhora a latência mediana, porque um pedido em fila começa mais cedo. A latência de cauda evolui no sentido oposto, e essa parte raramente é mencionada.

Cada sequência adicional num passo acrescenta algum trabalho, por isso o ITL aumenta para todos à medida que o batch fica cheio. O prefill de uma nova chegada ocupa uma parte de um passo que, de outro modo, estaria disponível para os utilizadores em streaming. Sob pressão de cache, o scheduler faz preempção, o que envia um pedido parcialmente gerado de volta ao início do seu prefill.

Uma interface de chat mostra as caudas, não as médias. Um stream que pausa durante dois segundos a meio de uma frase parece estar avariado, mesmo quando o tempo total até à conclusão é bom. Meça o p95 TTFT e o p95 ITL sob a carga esperada e trate a média de tokens por segundo como um valor de capacidade, não como uma descrição da experiência.

A configuração prática resulta daí. Limite a concorrência ligeiramente abaixo do que a memória permite, para que o motor nunca precise de fazer preempção. Uma fila curta e previsível é melhor do que um batch profundo que entra em thrashing, porque um utilizador que espera quatro segundos e depois recebe um stream contínuo fica mais satisfeito do que um utilizador que começa imediatamente e sofre duas paragens.

O que verificar quando está lento

Cada utilizador tem um desempenho normal, mas a espera é longa. Isso é uma fila, não um problema de velocidade. Verifique primeiro a definição de paralelismo. O modelo está a responder corretamente, uma solicitação de cada vez.

HTTP 503 do Ollama. A fila está cheia. O servidor está no limite da capacidade ou OLLAMA_MAX_QUEUE foi definido com um valor baixo de propósito para rejeitar carga, que é precisamente o comportamento pretendido.

Os tokens por segundo diminuem drasticamente sob carga num servidor com CPU. Execute vmstat 1 enquanto isso acontece. Valores diferentes de zero nas colunas si e so significam que a máquina está a usar swap. Nesse caso, os pesos são lidos do disco a cada token. Nenhuma alteração de configuração resolve isso. Reduza o tamanho do modelo ou o número de slots.

Um em cada dez utilizadores espera muito mais do que os restantes. Procure preempted no log do vLLM. A preempção e o respetivo recálculo são normalmente a causa. Isso significa que a cache é insuficiente para o comprimento de contexto permitido.

O TTFT é elevado mesmo quando o servidor está inativo. Isso é prefill, não concorrência. Prompts longos consomem tempo real antes de surgir o primeiro token. Por isso, verifique o tamanho do prompt e o caching de prefixos antes de analisar o hardware. Se a espera longa ocorrer apenas com a primeira pessoa que regressa depois de um período sem atividade e todos os pedidos seguintes forem normais, então não é prefill. Nesse caso, o Ollama descarregou o modelo e voltou a ler os pesos do disco. Vale a pena excluir essa hipótese mantendo o modelo residente entre pedidos.

FAQ

Porque é que o meu LLM self-hosted fica mais lento quando uma segunda pessoa o utiliza?

Na maioria dos casos, não fica mais lento. Fica em fila. O Ollama é distribuído com OLLAMA_NUM_PARALLEL definido como 1, por isso o segundo pedido espera que o primeiro emita o token final. Distinga os dois casos medindo o stream de um utilizador enquanto outro espera: se a taxa de tokens por segundo for normal depois de começar, existe uma fila, e aumentar o número de pedidos paralelos resolve o problema. Se os dois streams funcionarem a metade da velocidade, está efetivamente a partilhar a largura de banda da memória, que é uma limitação do hardware.

Quantos utilizadores concorrentes pode suportar uma GPU pequena?

Conte a memória, não os utilizadores. Primeiro os pesos e depois a cache KV, que custa 2 vezes o número de camadas vezes o número de cabeças key/value vezes a dimensão da cabeça vezes o número de bytes, por token e por conversa ativa. Um modelo típico de 8B com 36 camadas, 8 cabeças key/value e dimensão de cabeça 128 custa cerca de 144 KiB por token em 16-bit, pelo que uma conversa com 8,192 tokens precisa de aproximadamente 1.2 GB. Uma placa de 24 GB que mantenha esse modelo em 16-bit dispõe de, no máximo, cerca de 6 GB para a cache. Isso corresponde a aproximadamente cinco conversas com o contexto completo, ou a mais conversas se reduzir o contexto.

O batching contínuo torna mais lenta a resposta de cada utilizador?

A latência mediana melhora normalmente, porque os pedidos deixam de esperar que um lote inteiro termine. A latência de cauda piora. Cada sequência adicional acrescenta trabalho a cada passo de decoding, a fase de prefill de uma nova chegada ocupa parte de um passo dos utilizadores que estão a receber o stream e um pedido interrompido tem de executar o prefill duas vezes. Meça a latência p95 entre tokens, não a média, porque uma janela de chat torna as pausas evidentes de uma forma que as médias ocultam.

Devo aumentar OLLAMA_NUM_PARALLEL ou mudar para vLLM?

Aumente primeiro o número de pedidos paralelos. Não tem custo e requer apenas um ficheiro drop-in. Isto resolve o caso comum em que quatro pessoas ficam em fila atrás de uma resposta longa. A memória é o limite: os pedidos paralelos multiplicam o contexto que tem de manter, por isso verifique se algumas camadas estão a passar para a CPU. Mude para vLLM quando tiver uma GPU com VRAM disponível e mais de cerca de quatro pedidos efetivamente em execução, porque é nesse ponto que a cache paginada e o agendamento por token oferecem mais vantagens do que custos.

Mais núcleos de CPU resolvem um servidor LLM lento?

Não na parte que os utilizadores mais notam. O decoding lê o modelo completo da memória para cada token, por isso fica limitado pela largura de banda da RAM. Os núcleos adicionais deixam de ajudar quando a largura de banda fica saturada. O prefill escala com o número de núcleos, pelo que mais núcleos reduzem o tempo até ao primeiro token em prompts longos. Numa VPS com 4 a 8 GB, a limitação principal costuma ser a capacidade da memória. A correção eficaz é normalmente usar um modelo mais pequeno ou um contexto mais curto, e não adicionar mais vCPUs.

Este artigo foi útil?