Ollama: Ajuste de Desempenho

Ajuste de desempenho é trocar de marcha no motor de IA local — da primeira marcha rastejante à quinta voadora.

💡 Dica: OLLAMA_NUM_PARALLEL controla o número de requisições paralelas por modelo — definir para 4 permite processar 4 requisições simultaneamente, aumentando o throughput. Para 8GB VRAM, comece testando com 2-4; para 16GB, tente 4-8. Combine com OLLAMA_KEEP_ALIVE=30m para evitar descarregamento e recarregamento frequente do modelo.

📋 Pré-requisitos: Você deve dominar o seguinte primeiro

1. O Que Você Vai Aprender


2. Uma História Real de Uma Empreendedora SaaS

💡 Dica: OLLAMA_KEEP_ALIVE define quanto tempo um modelo permanece na memória. Para cenários de alta concorrência, defina para 30m ou mais para evitar atrasos de início a frio causados por descarregamento/recarregamento frequente do modelo (5-30 segundos). Para uso infrequente, defina 5m para liberar VRAM.

ℹ️ Info: num_ctx (tamanho da janela de contexto) afeta diretamente o uso de memória do KV Cache. Um modelo 8B com num_ctx=2048 precisa de cerca de 4GB VRAM, num_ctx=8192 precisa de cerca de 6GB, e num_ctx=32768 precisa de cerca de 12GB. Defina com base nas necessidades reais, não aumente cegamente.

(1) O Problema: Timeouts de Resposta Sob Alta Concorrência

O SupportBot da Alice entrou no ar, e durante horários de pico, 10 requisições concorrentes fizeram o tempo de resposta disparar de 2 segundos para 30 segundos. Clientes reclamaram de "esperar demais," mas o Ollama por padrão processa apenas 1 requisição concorrente.

(2) A Solução: Otimização de Concorrência e Contexto

Ajustando OLLAMA_NUM_PARALLEL e OLLAMA_MAX_LOADED_MODELS, o tempo de resposta recuperou para menos de 5 segundos:

BASH
# Enable 4 parallel requests
export OLLAMA_NUM_PARALLEL=4
export OLLAMA_MAX_LOADED_MODELS=2

3. Métricas de Desempenho-Chave

⚠️ Aviso: Aumentar OLLAMA_NUM_PARALLEL não aumenta linearmente o throughput — cada requisição concorrente compartilha VRAM da GPU, e muitas requisições concorrentes podem causar OOM ou uma queda acentuada na velocidade por requisição. Comece com 2 e teste incrementalmente, observando o uso de VRAM e mudanças de latência.

(1) Três Métricas Centrais

Métrica Nome Completo Significado Valor Alvo
Tokens/s Tokens por segundo Velocidade de geração GPU: 30+, CPU: 5+
TTFT Tempo até o Primeiro Token Latência do primeiro token < 500ms (GPU)
Memória Uso VRAM/RAM Consumo de recursos < 90% da capacidade

(2) Estatísticas de Desempenho do Ollama

Dados de desempenho incluídos no modo --verbose ou respostas não-streaming:

Campo Significado
total_duration Tempo total (nanossegundos)
load_duration Tempo de carregamento do modelo
prompt_eval_count Contagem de tokens de entrada
prompt_eval_duration Tempo de processamento da entrada
eval_count Contagem de tokens de saída
eval_duration Tempo de geração da saída

▶ Exemplo 1: Coleta de Métricas de Desempenho

BASH
# Collect performance metrics with verbose output
ollama run --verbose qwen2.5 "Explain AI in 50 words"

# Key output:
# total duration:       2500000000    # 2.5s total
# load duration:        500000000     # 0.5s model load
# prompt eval count:    15 token(s)
# prompt eval duration: 200000000     # 0.2s input processing
# prompt eval count:    15 token(s)   # = prompt_eval_speed: 75 tok/s
# eval count:           52 token(s)
# eval duration:        1800000000    # = eval_speed: 28.9 tok/s

Saída:

TEXT
I'm a helpful AI assistant running locally on your machine...
PYTHON
import ollama
import time

def measure_performance(model: str, prompt: str) -> dict:
    start = time.time()
    response = ollama.chat(
        model=model,
        messages=[{"role": "user", "content": prompt}],
        stream=False
    )
    total_time = time.time() - start
    content = response["message"]["content"]
    token_count = len(content) // 4  # Rough estimate

    return {
        "model": model,
        "total_time_s": round(total_time, 2),
        "estimated_tokens": token_count,
        "estimated_tok_per_s": round(token_count / total_time, 1) if total_time > 0 else 0
    }

result = measure_performance("qwen2.5", "Explain machine learning in 100 words")
print(result)

4. Ajuste de Requisições Concorrentes

⚠️ Nota: Concorrência excessiva pode causar OOM — OLLAMA_NUM_PARALLEL não aumenta linearmente o throughput; cada requisição concorrente compartilha VRAM da GPU, e muitas requisições concorrentes causam uma queda acentuada na velocidade por requisição ou até estouro de memória. Comece com 2 e teste incrementalmente, observando o uso de VRAM e mudanças de latência.

(1) Variáveis de Ambiente Relacionadas à Concorrência

Variável Padrão Descrição Impacto
OLLAMA_NUM_PARALLEL 1 Requisições paralelas por modelo ↑Throughput ↓Velocidade por requisição
OLLAMA_MAX_LOADED_MODELS 1 Máximo de modelos carregados simultaneamente ↑Concorrência multi-modelo ↑VRAM
OLLAMA_KEEP_ALIVE 5m Tempo de residência do modelo na memória ↑Evita recarregamento ↓Recuperação de memória

(2) Decisão de Configuração de Concorrência

100%
flowchart LR
    A[Requisições Concorrentes?] --> B{QPS de Pico?}
    B -->|1-2| C[Padrão: NUM_PARALLEL=1]
    B -->|3-5| D[NUM_PARALLEL=4<br/>8GB VRAM mínimo]
    B -->|6-10| E[NUM_PARALLEL=8<br/>16GB+ VRAM]
    B -->|10+| F[Multi-nó<br/>Load Balancer]
Configuração Requisito VRAM Throughput Latência por Requisição
NUM_PARALLEL=1 Mais baixo 1 req/s Mais curta
NUM_PARALLEL=4 Médio 3-4 req/s Leve aumento
NUM_PARALLEL=8 Alto 6-8 req/s Aumento notável

▶ Exemplo 2: Configuração e Teste de Concorrência

BASH
# Configure parallel processing
sudo systemctl edit ollama
# Add:
[Service]
Environment="OLLAMA_NUM_PARALLEL=4"
Environment="OLLAMA_MAX_LOADED_MODELS=2"
Environment="OLLAMA_KEEP_ALIVE=30m"

sudo systemctl daemon-reload
sudo systemctl restart ollama

Saída:

TEXT
# Ollama command executed successfully
PYTHON
import ollama
import asyncio
import time

async def concurrent_test(num_requests: int):
    """Test concurrent request handling."""
    client = ollama.AsyncClient()
    start = time.time()

    async def single_request(i: int):
        req_start = time.time()
        response = await client.chat(
            model="qwen2.5",
            messages=[{"role": "user", "content": f"Say hello {i}"}],
            stream=False,
            options={"temperature": 0.1}
        )
        return time.time() - req_start

    tasks = [single_request(i) for i in range(num_requests)]
    latencies = await asyncio.gather(*tasks)

    total = time.time() - start
    print(f"Concurrent: {num_requests} requests")
    print(f"Total time: {total:.2f}s")
    print(f"Avg latency: {sum(latencies)/len(latencies):.2f}s")
    print(f"Throughput: {num_requests/total:.1f} req/s")

asyncio.run(concurrent_test(4))

5. Gerenciamento de Janela de Contexto

(1) Impacto do num_ctx na Memória

num_ctx KV Cache (8B Q4_M) VRAM Total Necessária Caso de Uso
2048 ~1 GB ~6 GB Conversas curtas (padrão)
4096 ~2 GB ~7 GB Conversas médias
8192 ~4 GB ~9 GB Recuperação RAG
32768 ~16 GB ~21 GB Documentos longos
⚠️ Nota: KV Cache cresce linearmente com num_ctx. Um modelo 8B com num_ctx=32768 não roda em 8GB VRAM; precisa de 21GB+ VRAM.

(2) Estratégias de Otimização do num_ctx

Estratégia Método Efeito
Definir sob demanda Chat usa 2048, RAG usa 8192 Reduz desperdício de memória
Janela deslizante Manter apenas as últimas N rodadas de conversa Controla comprimento da entrada
Compressão por resumo Periodicamente comprimir conversas iniciais Economiza espaço de contexto
Curadoria RAG Reduzir top-k de 5 para 3 Reduz tokens de entrada

▶ Exemplo 3: Teste de Comparação num_ctx

PYTHON
import ollama
import time

def test_context_sizes(model: str, prompt: str, context_sizes: list[int]):
    """Test performance with different context window sizes."""
    for ctx in context_sizes:
        start = time.time()
        response = ollama.chat(
            model=model,
            messages=[{"role": "user", "content": prompt}],
            stream=False,
            options={"num_ctx": ctx, "temperature": 0.3}
        )
        elapsed = time.time() - start
        tokens = len(response["message"]["content"]) // 4
        speed = tokens / elapsed if elapsed > 0 else 0
        print(f"num_ctx={ctx}: {elapsed:.2f}s, ~{speed:.1f} tok/s")

test_context_sizes("qwen2.5", "Explain AI briefly", [2048, 4096, 8192])

Saída:

TEXT
# Function defined successfully

6. Processamento em Lote e Benchmarking

(1) Estratégias de Processamento em Lote

Estratégia Descrição Caso de Uso
Lote serial Processar requisições uma a uma Simples e confiável
Lote paralelo Concorrência asyncio Alto throughput
Agendamento de fila FastAPI + fila Ambiente de produção

▶ Exemplo 4: Script de Benchmark em Lote

PYTHON
import ollama
import time
import asyncio
import json
from datetime import datetime

class BenchmarkRunner:
    def __init__(self, model: str = "qwen2.5"):
        self.model = model
        self.results = []

    def warmup(self, runs: int = 2):
        for _ in range(runs):
            ollama.chat(model=self.model,
                        messages=[{"role": "user", "content": "warmup"}],
                        stream=False)

    def single_benchmark(self, prompt: str) -> dict:
        start = time.time()
        response = ollama.chat(
            model=self.model,
            messages=[{"role": "user", "content": prompt}],
            stream=False,
            options={"temperature": 0.3}
        )
        elapsed = time.time() - start
        content = response["message"]["content"]
        return {
            "prompt_length": len(prompt),
            "response_length": len(content),
            "estimated_tokens": len(content) // 4,
            "total_time_s": round(elapsed, 2),
            "tok_per_s": round(len(content) / 4 / elapsed, 1) if elapsed > 0 else 0
        }

    async def concurrent_benchmark(self, prompt: str, concurrency: int) -> dict:
        client = ollama.AsyncClient()
        start = time.time()
        tasks = [
            client.chat(model=self.model,
                       messages=[{"role": "user", "content": prompt}],
                       stream=False, options={"temperature": 0.3})
            for _ in range(concurrency)
        ]
        await asyncio.gather(*tasks)
        total = time.time() - start
        return {
            "concurrency": concurrency,
            "total_time_s": round(total, 2),
            "throughput_rps": round(concurrency / total, 1)
        }

    def run_full_benchmark(self):
        self.warmup()
        prompts = [
            "Hello",
            "Explain AI in 3 sentences",
            "Write a product description for wireless headphones"
        ]
        print("=== Single Request Benchmark ===")
        for p in prompts:
            result = self.single_benchmark(p)
            print(f"  {result['estimated_tokens']}tok, {result['tok_per_s']}tok/s, {result['total_time_s']}s")

        print("\n=== Concurrent Benchmark ===")
        for c in [1, 2, 4]:
            result = asyncio.run(self.concurrent_benchmark("Hello", c))
            print(f"  Concurrency {c}: {result['throughput_rps']} req/s")

# Run
runner = BenchmarkRunner("qwen2.5")
runner.run_full_benchmark()

Saída:

TEXT
=== Single Request Benchmark ===

=== Concurrent Benchmark ===

▶ Exemplo 5: Estabelecimento de Baseline de Desempenho

BASH
#!/bin/bash
# Establish performance baseline

MODEL="qwen2.5"
DATE=$(date +%Y%m%d)
REPORT="baseline_${DATE}.txt"

echo "=== Performance Baseline ===" > "$REPORT"
echo "Date: $(date)" >> "$REPORT"
echo "Model: $MODEL" >> "$REPORT"
echo "GPU: $(nvidia-smi --query-gpu=name --format=csv,noheader 2>/dev/null || echo 'CPU')" >> "$REPORT"
echo "" >> "$REPORT"

# Single request benchmark
echo "## Single Request ##" >> "$REPORT"
for i in {1..5}; do
    start=$(date +%s%N)
    curl -s http://localhost:11434/api/chat -d "{
        \"model\": \"$MODEL\",
        \"messages\": [{\"role\": \"user\", \"content\": \"Hello\"}],
        \"stream\": false
    }" > /dev/null
    end=$(date +%s%N)
    elapsed=$(( (end - start) / 1000000 ))
    echo "  Run $i: ${elapsed}ms" >> "$REPORT"
done

echo "" >> "$REPORT"
echo "## GPU Utilization ##" >> "$REPORT"
nvidia-smi --query-gpu=utilization.gpu,memory.used,memory.total --format=csv >> "$REPORT" 2>/dev/null || echo "CPU mode" >> "$REPORT"

echo "Baseline saved: $REPORT"

Saída:

TEXT
{"status":"ok","data":{}}

7. Exemplo Abrangente: Relatório de Ajuste de Desempenho do SupportBot

PYTHON
# ============================================
# Comprehensive: Performance tuning report
# Full benchmark + optimization recommendations
# ============================================

import ollama
import time
import asyncio
import json

class PerformanceTuner:
    def __init__(self, model: str = "qwen2.5"):
        self.model = model

    def run_diagnostics(self) -> dict:
        """Run full performance diagnostics."""
        # Warm up
        ollama.chat(model=self.model,
                    messages=[{"role": "user", "content": "warmup"}],
                    stream=False)

        # Single request test
        start = time.time()
        resp = ollama.chat(
            model=self.model,
            messages=[{"role": "user", "content": "Hello"}],
            stream=False, options={"temperature": 0.3}
        )
        single_latency = time.time() - start

        # Context size test
        ctx_results = {}
        for ctx in [2048, 4096, 8192]:
            start = time.time()
            ollama.chat(
                model=self.model,
                messages=[{"role": "user", "content": "Hello"}],
                stream=False, options={"num_ctx": ctx, "temperature": 0.3}
            )
            ctx_results[ctx] = round(time.time() - start, 2)

        return {
            "model": self.model,
            "single_latency_s": round(single_latency, 2),
            "context_sizes": ctx_results,
            "recommendations": self._generate_recommendations(single_latency)
        }

    def _generate_recommendations(self, latency: float) -> list[str]:
        recs = []
        if latency > 5:
            recs.append("High latency detected. Enable GPU acceleration or use smaller model.")
        if latency > 2:
            recs.append("Set OLLAMA_NUM_PARALLEL=4 for concurrent requests.")
        recs.append("Use num_ctx=2048 for chat, num_ctx=8192 for RAG.")
        recs.append("Set OLLAMA_KEEP_ALIVE=30m to avoid model reloading.")
        return recs

    def generate_report(self) -> str:
        diag = self.run_diagnostics()
        report = [
            f"# Performance Tuning Report",
            f"Model: {diag['model']}",
            f"Single request latency: {diag['single_latency_s']}s",
            f"\n## Context Window Impact:",
        ]
        for ctx, latency in diag["context_sizes"].items():
            report.append(f"  num_ctx={ctx}: {latency}s")
        report.append("\n## Recommendations:")
        for r in diag["recommendations"]:
            report.append(f"  - {r}")
        return "\n".join(report)

tuner = PerformanceTuner("qwen2.5")
print(tuner.generate_report())

❓ Perguntas Frequentes

P: Qual deve ser o valor de OLLAMA_NUM_PARALLEL? R: 2-4 para 8GB VRAM, 4-8 para 16GB, 8 para 24GB. Muitas requisições paralelas aumentam a latência por requisição e risco de OOM. Comece testando com 4.

P: E se o TTFT estiver muito alto? R: 1) Garanta que OLLAMA_KEEP_ALIVE é longo o suficiente (evite início a frio); 2) Reduza num_ctx; 3) Use um modelo menor; 4) Garanta que a aceleração GPU está funcionando.

P: Como reduzir o tempo de carregamento do modelo? R: Defina OLLAMA_KEEP_ALIVE=30m ou mais para manter o modelo residente na memória. O primeiro carregamento é inevitável; requisições subsequentes respondem em segundos.

P: E se OOM ocorrer durante requisições concorrentes? R: Reduza OLLAMA_NUM_PARALLEL; use num_ctx menor; reduza OLLAMA_MAX_LOADED_MODELS; ou adicione mais VRAM.

P: E se os resultados do benchmark forem instáveis? R: 1) Aqueça 2-3 vezes para eliminar início a frio; 2) Feche outros programas de GPU; 3) Tire 5+ execuções e faça média; 4) Fixe a semente aleatória.

P: Como monitorar o desempenho em ambiente de produção? R: A Aula 21 detalhará a stack de monitoramento Prometheus + Grafana. Por enquanto, use --verbose e scripts personalizados para coletar métricas.


📖 Resumo


📝 Exercícios

  1. Básico (⭐): Execute o modo --verbose, registre Tokens/s, TTFT e outras métricas para 3 execuções de Inferência, e estabeleça seu baseline de desempenho.
  2. Intermediário (⭐⭐): Configure OLLAMA_NUM_PARALLEL=4, execute testes concorrentes, e compare mudanças de throughput e latência antes e depois da concorrência.
  3. Avançado (⭐⭐⭐): Escreva um relatório completo de ajuste de desempenho — incluindo benchmarks de requisição única, testes concorrentes, comparação num_ctx e recomendações de otimização — em formato Markdown.
Web-Tutorial.com

Equipe Técnica Web-Tutorial

Uma plataforma de tutoriais mantida por diversos desenvolvedores. Cada tutorial é escrito e revisado por profissionais da área correspondente. Trabalhamos para manter nosso conteúdo preciso e confiável — se encontrar algum problema, avise-nos.

100%