Lokalny model czy chmura? Porównanie jakości i czasu na analizatorze dokumentów

Piotr Dul

20-10-2026

(aktualizacja: 20-10-2026)

Jak uczciwie porównać model lokalny z chmurowym na analizatorze dokumentów w Pythonie: ten sam kod, ta sama lista kontrolna i pomiary na Twoim komputerze.

Lokalny model czy chmura? Porównanie jakości i czasu na analizatorze dokumentów
Aby uczciwie porównać model lokalny z chmurowym, uruchom ten sam analizator na tych samych dokumentach przez jedną bramkę i zmieniaj tylko alias modelu, mierząc czas każdego wywołania oraz oceniając streszczenia według tej samej listy kontrolnej. Wyniki zależą od Twojego sprzętu, więc porównanie wykonaj na własnym komputerze. Model lokalny wygrywa prywatnością, brakiem kosztu za token i pracą offline, a chmurowy zwykle jakością i szybkością. O wyborze decyduje to, które z tych kryteriów jest dla Twojego zadania najważniejsze.

To ostatnia część cyklu. Mamy model lokalny w Ollamie i bramkę LiteLLM z konfiguracją w YAML. Teraz sprawdzamy, co z tego wynika w praktyce, na projekcie, który już znasz: analizatorze dokumentów.

Jak przepiąć analizator na bramkę LiteLLM?

Zmiany w stosunku do wersji z OpenAI są niewielkie. Klient wskazuje na bramkę, nazwa modelu pochodzi z konfiguracji, a raport zbiera dodatkowo czas wywołań:

import time
from dataclasses import dataclass

from openai import APIConnectionError, OpenAI

from settings import Settings


@dataclass
class Report:
    model: str
    calls: int = 0
    prompt_tokens: int = 0
    completion_tokens: int = 0
    seconds: float = 0.0

    def add(self, usage, seconds: float) -> None:
        self.calls += 1
        self.prompt_tokens += usage.prompt_tokens
        self.completion_tokens += usage.completion_tokens
        self.seconds += seconds

    def format(self) -> str:
        return (
            f"Model (alias): {self.model}\n"
            f"Wywołania: {self.calls}\n"
            f"Tokeny wejściowe: {self.prompt_tokens}\n"
            f"Tokeny wyjściowe: {self.completion_tokens}\n"
            f"Czas: {self.seconds:.1f} s"
        )


def make_client(settings: Settings) -> OpenAI:
    return OpenAI(
        base_url=settings.gateway_url,
        api_key=settings.api_key,
        timeout=settings.timeout_seconds,
        max_retries=0,
    )


def ask(client: OpenAI, settings: Settings, prompt: str, report: Report) -> str:
    started = time.perf_counter()
    try:
        response = client.chat.completions.create(
            model=settings.model,
            messages=[
                {"role": "system", "content": "Streszczasz dokumenty po polsku, zwięźle i rzeczowo."},
                {"role": "user", "content": prompt},
            ],
        )
    except APIConnectionError as error:
        raise SystemExit(f"Brak połączenia z bramką {settings.gateway_url}: {error}")
    report.add(response.usage, time.perf_counter() - started)
    return response.choices[0].message.content

Zwróć uwagę na dwie decyzje. max_retries=0 wyłącza ponawianie wewnątrz klienta, żeby czas jednego wywołania nie zawierał ukrytych powtórzeń. Zwiększony timeout_seconds jest potrzebny, bo model lokalny na słabszym sprzęcie odpowiada dużo dłużej niż API w chmurze. Reszta (dzielenie na fragmenty, streszczanie dwuetapowe) zostaje taka sama jak w oryginalnym analizatorze.

Do dzielenia tekstu nadal używamy tiktoken, ale pamiętaj, że to tylko przybliżenie dla modelu lokalnego, który ma własny tokenizer. Dlatego w raporcie bierzemy liczby z response.usage, a nie z własnego liczenia.

Jak uczciwie porównać dwa modele?

Najczęstszy błąd to porównanie jednego zapytania „na oko”. Żeby wynik coś znaczył:

ZasadaDlaczego
Te same dokumenty dla obu modeliInaczej porównujesz dokumenty, nie modele
Kilka dokumentów o różnej długości (krótki, średni, długi)Czas rośnie z długością, ale nie tak samo u obu
Pierwsze wywołanie lokalnego modelu odrzuć lub oznaczZawiera ładowanie modelu do pamięci
Kilka powtórzeń, mediana zamiast jednego pomiaruPojedynczy czas zależy od chwilowego obciążenia komputera
Jedna lista kontrolna jakości dla obu streszczeńOcena „podoba mi się” nie jest powtarzalna
Nic innego nie obciąża komputera podczas pomiaruPrzeglądarka z wieloma kartami zafałszuje czas

Prosty skrypt benchmark.py, który przechodzi po dokumentach i aliasach:

import statistics
import sys
from pathlib import Path

from analyzer import make_client, summarize
from settings import Settings

REPEATS = 3


def run(alias: str, path: Path) -> tuple[float, str]:
    settings = Settings(model=alias)
    client = make_client(settings)
    times = []
    summary = ""
    for _ in range(REPEATS):
        summary, report = summarize(client, settings, path.read_text(encoding="utf-8"))
        times.append(report.seconds)
    return statistics.median(times), summary


if __name__ == "__main__":
    for path in map(Path, sys.argv[1:]):
        for alias in ("local", "cloud"):
            seconds, summary = run(alias, path)
            print(f"{path.name} | {alias} | mediana {seconds:.1f} s")
            Path(f"wynik-{alias}-{path.stem}.txt").write_text(summary, encoding="utf-8")

Uruchom: python benchmark.py krotki.txt sredni.txt dlugi.txt. Skrypt zapisuje streszczenia do plików, żebyś mógł je ocenić według tej samej listy.

Jak ocenić jakość streszczenia?

Zamiast ogólnego wrażenia sprawdź każde streszczenie według tych samych pytań:

  • Czy zawiera najważniejszy wniosek dokumentu?
  • Czy każda podana informacja da się znaleźć w oryginale (brak zmyśleń)?
  • Czy jest po polsku i nie wtrąca obcych słów?
  • Czy mieści się w poleconych pięciu zdaniach?
  • Czy nie pomija całej części dokumentu (objaw ucięcia kontekstu, opisany w artykule o ucinaniu kontekstu w Ollamie)?

Oceniaj każdy punkt jako tak/nie. Dwie osoby ze wspólną listą dojdą do podobnego wyniku, a z „wrażeniem” już niekoniecznie.

Dlaczego Twoje wyniki będą inne niż moje?

W tym artykule nie podaję tabeli czasów, bo liczby z jednego komputera niewiele mówią o Twoim. Ten sam model może na różnych komputerach działać wielokrotnie szybciej lub wolniej. Na wynik wpływają:

CzynnikDlaczego zmienia wynik
Sprzęt (procesor, karta graficzna, pamięć)Czy model mieści się w pamięci w całości, decyduje o szybkości odpowiedzi
Wielkość modelu i kwantyzacjaTen sam model w innej kwantyzacji ma inną jakość, rozmiar i szybkość
Okno kontekstuWiększe okno zużywa więcej pamięci i spowalnia odpowiedzi
Obciążenie komputeraInne programy zakłócają pomiar
Zimny startPierwsze wywołanie ładuje model do pamięci
Sieć i dostawca (chmura)Czas zależy od połączenia, pory dnia i obciążenia po stronie dostawcy

Dlatego porównanie wykonaj na swoim komputerze i z modelami, których naprawdę użyjesz. Interesuje Cię przecież to, co zadziała u Ciebie, a nie to, co wyszło komuś innemu.

Jak odczytać wyniki na swoim komputerze?

Uruchom benchmark.py na kilku dokumentach i wpisz wyniki w tabelę:

DokumentDługość (tokeny)AliasMediana czasuPunkty z listy jakości
krótkilocal
krótkicloud
średnilocal
średnicloud
długilocal
długicloud

Potem odpowiedz na cztery pytania:

  1. O ile lokalny model jest wolniejszy lub szybszy? Porównuj mediany dla tego samego dokumentu, nie średnią ze wszystkich.
  2. Jak czas rośnie z długością dokumentu? Jeśli u modelu lokalnego rośnie wyraźnie szybciej, długie dokumenty są jego słabą stroną.
  3. Czy różnica w punktach jakości jest istotna? Różnica jednego punktu na pięć przy jednym dokumencie to jeszcze nie wniosek. Powtórz na kolejnym dokumencie.
  4. Czy lokalny model jest wystarczająco dobry dla tego zadania? To jest właściwe pytanie, a nie „który wygrał”.

Kiedy model lokalny się opłaca, a kiedy nie?

Niezależnie od wyników konkretnego pomiaru, kryteria wyboru są stałe:

SytuacjaLepszy wybór
Dokumenty zawierają dane osobowe lub poufneLokalny
Dużo eksperymentów w trakcie naukiLokalny (brak kosztu za token)
Praca bez internetuLokalny
Wymagana wysoka jakość lub trudny język specjalistycznyZwykle chmura
Ważny krótki czas odpowiedzi, a sprzęt jest słabyZwykle chmura
Duży ruch od wielu użytkowników narazZwykle chmura (jeden komputer się nie skaluje)

Dla analizatora dokumentów firmowych, który nie może opuszczać komputera, odpowiedź jest prosta: lokalny, nawet kosztem czasu. Dla publicznych tekstów, gdy liczy się jakość, wybierzesz chmurę. Dzięki bramce nie musisz decydować raz na zawsze: zmieniasz jedną wartość w config.yaml.

Dlaczego ten projekt działa bez Docker Compose?

Całość to trzy zwykłe procesy: Ollama, bramka LiteLLM i Twój skrypt Pythona, każdy w osobnym terminalu. Dla projektu uczącego to wystarczy i ma przewagę, że widzisz logi każdego elementu. Compose ma sens, gdy aplikacja trafia na serwer albo gdy współpracujesz z zespołem i potrzebujesz powtarzalnego środowiska. Warto go dodać na końcu, jako osobny krok. Pamiętaj wtedy o pułapce z localhost w kontenerze Dockera: w kontenerze localhost nie oznacza Twojego komputera.

Jak rozbudować projekt dalej?

  • Dodaj trzeci alias z innym modelem lokalnym i porównaj rozmiary (mały vs duży).
  • Zapisuj wyniki pomiarów do pliku CSV i rysuj, jak czas rośnie z długością dokumentu.
  • Dodaj testy z atrapami, tak jak w artykule o testowaniu kodu z API LLM. Bramka to po prostu kolejny adres do podmienienia.
  • Jeśli dokumentów jest dużo, nie streszczaj wszystkiego, tylko wyszukuj trafne fragmenty (bazy wektorowe i RAG w Pythonie).
  • Poproś narzędzie AI o pomoc przy refaktoryzacji, ale zweryfikuj wynik własnymi testami (vibe coding).

Taki projekt dobrze wygląda w portfolio: pokazuje pracę z bramką, konfiguracją, pomiarami i świadomy dobór narzędzia. Jeśli chcesz, żeby ktoś doświadczony ocenił go przed rozmową rekrutacyjną, zobacz mentoring w ścieżce Python.

Konsultacja i warsztaty devmentor.pl

Masz pytania?