tetes
Możliwości
Historia
Home App
Warstwy
Mapa homelaba
Blog
Studia przypadków
Współpraca
Kontakt
tetes

Docker homelab. 40+ serwisów. 0 subskrypcji chmurowych.

mariuszbinczyk.com

Nawigacja

  • Możliwości
  • Historia
  • Home App
  • Warstwy
  • Współpraca
  • Kontakt

Kontakt

  • GitHub
  • Email
  • PL / EN / DE

© 2026 tetes.pl — Wszelkie prawa zastrzeżone

0d98bb6
O projekciePrywatność
>|
Język:ENPLDE
AI·4 lipca 2026·12 min czytania·Aktualizacja: 6 lipca 2026

Lokalny LLM bez GPU: czego nauczył mnie rok z Ollama na starym i5

Sześć rdzeni, 32 GB RAM, zero GPU — i modele językowe, które naprawdę pracują w domu: streszczają alerty, obsługują domowego agenta i szukają po znaczeniu. O doborze modeli, temperaturach i pułapkach, których nie ma w poradnikach.

#ollama#llm#ai#self-hosting#homelab
Lokalny LLM bez GPU: czego nauczył mnie rok z Ollama na starym i5

Po co komu wolny model, skoro jest szybka chmura

Krótka odpowiedź: bo prompty nie wychodzą z domu. Mój domowy agent czyta alerty z serwera, notatki rodziny i stan urządzeń — dokładnie ten rodzaj danych, którego nie chcę wysyłać do zewnętrznego API, choćby było najtańsze. Do tego dochodzi przewidywalność: lokalny model nie zmieni cennika, nie zniknie z oferty i nie zacznie nagle odpowiadać inaczej, bo ktoś po drugiej stronie podmienił wersję.

Długa odpowiedź zajmie resztę tego wpisu, bo lokalny LLM bez to seria kompromisów — i o nich rzadko piszą poradniki, które kończą się na ollama run.

Sprzęt: rozczarowanie wkalkulowane w plan

Mój serwer to Intel i5-8500 — sześć rdzeni z 2018 roku — i 32 GB RAM. Żadnego GPU. Na tym sprzęcie obsługuje dziś kilka modeli, każdy od innej roboty:

ModelRozmiarTempo na CPURola
phi4-mini2,5 GB~8 tok/sklasyfikacja alertów
gemma4:e4b9,6 GB~5 tok/sdomowy agent z narzędziami
qwen3-embedding 0.6b639 MBponiżej sekundyembeddingi do wyszukiwania
Bielik 4.5B5,1 GB~4 tok/spolskie komunikaty alertów

Te liczby ustawiają oczekiwania lepiej niż jakikolwiek benchmark z internetu. Osiem tokenów na sekundę oznacza, że streszczenie alertu powstaje w kilkanaście albo kilkadziesiąt sekund. Ciekawostka z tej tabeli: gemma4:e4b zajmuje na dysku prawie 10 GB, a generuje szybciej niż wyraźnie mniejszy, gęsty model 4B, którego używałem wcześniej — bo to architektura MoE i realnie aktywna jest tylko część parametrów. „Większy plik” przestał znaczyć „wolniejszy”.

Agent z narzędziami odpowiadał mi kiedyś po ~250 sekundach na zapytanie z jednym narzędziem. Dziś ta sama rozmowa zajmuje ~30 sekund — ale nie dzięki nowemu modelowi, tylko dzięki przebudowie toru i cache'owi promptu, o czym za chwilę. Zimny start, gdy model dopiero ładuje się do pamięci, dolicza swoje.

Najboleśniejsza lekcja nie dotyczyła jednak generowania, tylko czytania. Mój osobisty agent dostał kiedyś rozbudowany system prompt — około dwóch tysięcy tokenów instrukcji. Na CPU samo przetworzenie promptu zajmowało ponad cztery minuty, zanim model napisał pierwsze słowo. Na GPU nikt tego nie zauważa; na CPU długość promptu to realny koszt, płatny przy każdej rozmowie.

Płatny — chyba że nauczysz się korzystać z cache'u promptu. Ollama potrafi nie przeliczać od nowa początku rozmowy, jeśli jest identyczny bajt w bajt z poprzednim zapytaniem. U mnie długo nie działało, bo do każdej wiadomości użytkownika doklejałem świeży blok kontekstu — prompt rozjeżdżał się tuż za instrukcją systemową i cache nie miał czego trafić. Po przeniesieniu kontekstu do stabilnej części promptu druga i kolejne tury rozmowy przetwarzają się w dziewięć sekund zamiast stu czterech. Ta sama maszyna, ten sam model, dwunastokrotna różnica.

✓Wniosek
Bez GPU płacisz za każdy token — także te, które model czyta, nie tylko te, które pisze. Krótki system prompt i układ rozmowy pod cache promptu to na CPU nie stylistyka, tylko najtańsza optymalizacja, jaka istnieje.

Trzy małe modele zamiast jednego dużego

Pierwszy odruch jest zawsze ten sam: wziąć największy model, jaki zmieści się w RAM. Mój największy błąd z początków — bo „mieści się” i „nadaje się do użytku” to dwie różne rzeczy. Model 11B na CPU technicznie działa. Praktycznie: zdążysz zrobić herbatę (z rozpaleniem ogniska najpierw), zanim skończy zdanie.

Zamiast tego działa u mnie podział pracy. Szybki phi4-mini klasyfikuje alerty z monitoringu, a Bielik zamienia je na krótkie komunikaty po polsku — oba zadania są wąskie i powtarzalne, małe modele w zupełności wystarczają. Gemma 4 E4B prowadzi domowego agenta, bo w moich testach najpewniej wywołuje narzędzia: nie gubi wymaganych argumentów i nie wymyśla funkcji, których nie ma. A qwen3-embedding zamienia notatki i pamięć agenta na wektory — dzięki czemu wyszukiwanie działa po znaczeniu, nie po słowach kluczowych, i to w ułamku sekundy.

Przy embeddingach czekała na mnie pułapka, o której nie mówi żadna karta modelu wprost: qwen3-embedding to model asymetryczny. Dokumenty koduje się bez niczego, ale zapytania wymagają specjalnego prefiksu z instrukcją — bez niego ranking wyników po polsku potrafi być absurdalny: na pytanie „co lubi pić” wyżej od faktu o kawie plasowała się notatka o słoiku Nutelli. Jeden prefiks w kodzie i kolejność wraca do rozsądku. Jeśli po zmianie modelu embeddingów wyszukiwanie nagle „zgłupieje” — zacznij od tego.

Bielika — polski model od SpeakLeash — testowałem w wersji 4.5B z myślą o podmianie phi4-mini przy alertach. Wynik był pouczający: po polsku streszcza wyraźnie ładniej, ale w klasyfikacji alertów ze sztywnym formatem JSON poległ — spłaszczał wszystkie priorytety do średniego, podczas gdy phi4-mini rozdzielał je bezbłędnie i trzy razy szybciej. Najlepsza polszczyzna nie wygrywa, jeśli zadanie wymaga twardej klasyfikacji, a nie ładnego stylu. Bielik został na dysku i doczekał się: dziś w torze alertów phi4-mini klasyfikuje, a Bielik pisze finalny komunikat po polsku — każdy model robi to, w czym jest dobry. Jedna pułapka z wdrożenia: przez surowe /api/generate Bielik potrafił odbić prompt echem zamiast odpowiedzieć; pomogły dopiero rozmowa przez /api/chat i przykład odpowiedzi w prompcie systemowym.

→Decyzja
Zamiast pytać „jaki najlepszy model się zmieści”, pytam „jakie zadanie chcę wykonać”. Klasyfikacja, tool-calling i embeddingi to trzy różne prace — i trzy różne, małe modele robią je lepiej niż jeden duży, który we wszystkim jest przeciętny i wolny. A wybór weryfikuję testem na własnym zadaniu, nie rankingiem z internetu.

Temperatury, czyli fizyka upomina się o swoje

O tym poradniki milczą zupełnie: LLM na CPU to najcięższa praca, jaką ten procesor kiedykolwiek wykonywał. Podczas inference wszystkie rdzenie chodzą na pełnych obrotach nieprzerwanie przez kilkadziesiąt sekund. Mój i5 osiągał wtedy 88-95°C — przy progu ostrzegawczym 82°C. Przez pewien czas uznawałem to za „akceptowalne”, co jest eufemizmem na „ignorowałem problem”.

Zbliżenie na procesor i radiator w pomarańczowo-fioletowej poświacie — inference na CPU to najcięższa praca, jaką ten układ kiedykolwiek wykonywał

Rozwiązanie okazało się banalne i nie miało nic wspólnego z AI:

# docker-compose.yml — fragment konfiguracji Ollama
environment:
  - OLLAMA_NUM_THREAD=4        # 4 z 6 rdzeni → ~65% CPU, temp poniżej 80°C
  - OLLAMA_KEEP_ALIVE=2m       # domyślnie: szybki unload modelu po rozmowie
  - OLLAMA_MAX_LOADED_MODELS=3 # agent + alerty + embeddingi jednocześnie
  - OLLAMA_NUM_PARALLEL=2      # max 2 równoległe zapytania
deploy:
  resources:
    limits:
      memory: 24g              # twardy limit RAM kontenera — o tym za chwilę

Oddanie dwóch rdzeni kosztuje trochę szybkości, ale procesor przestał flirtować z progiem krytycznym — a serwer, który robi też wszystko inne, ma z czego oddychać. Drugi parametr to kompromis w drugą stronę: krótki globalny KEEP_ALIVE zwalnia RAM i obniża temperatury w spoczynku, ale rozmowa po przerwie może zacząć się od zimnego startu.

Można zresztą mieć jedno i drugie, bo keepAlive da się nadpisać per zapytanie. Globalne 2m to u mnie domyślna higiena dla zadań ad-hoc, ale zapytania agenta ustawiają własne keepAlive: 24h i numCtx: 8192 — model agenta siedzi w RAM na stałe, żeby rozmowa nie zaczynała się od zimnego startu i ponownego przetwarzania długiego promptu. Reszta modeli przychodzi i odchodzi.

Tu wyszła rzecz, której nie doceniłem przez pół dnia: keepAlive to prośba, nie gwarancja. O tym, czy model faktycznie zostanie w pamięci, decyduje MAX_LOADED_MODELS — przy limicie 2 każdy alert (ładujący model od streszczeń) i każde wyszukiwanie (ładujące model embeddingów) po cichu wypychały mi „wiecznego” agenta z RAM. Efekt: czterdzieści sekund ładowania doliczane do losowo wybranych rozmów. Limit 3 zamknął temat — wszystkie trzy etatowe modele mieszczą się w 24 GB z zapasem.

Pułapka, która wygląda jak brak RAM-u

Najdziwniejszy błąd tego roku: Ollama odmawia załadowania modelu, twierdząc, że nie mieści się w pamięci — na maszynie z 32 GB, z której realnie zajęte jest osiem. Powód: przy sprawdzaniu wolnej pamięci Ollama patrzy na MemFree, a nie MemAvailable. A MemFree na serwerze, który dużo czyta z dysku, potrafi spaść poniżej gigabajta, bo Linux słusznie trzyma resztę w cache — który odda natychmiast, gdy ktoś go potrzebuje.

W liczbach wygląda to absurdalnie: 32 GB RAM, z czego realnie zajęte ~8, jakieś 20 GB w cache — a MemFree pokazuje mniej niż gigabajt, więc Ollama odmawia załadowania modelu, który potrzebuje ośmiu. Model „nie mieści się” w pamięci, której jest pod dostatkiem, tylko system jej jeszcze nie posprzątał. U mnie pomogło jawne ustawienie limitu pamięci dla Ollamy i przeniesienie cięższych zadań AI na noc, poza godziny intensywnego I/O. Ale diagnoza zajęła zdecydowanie dłużej niż naprawa — bo komunikat błędu kłamie o przyczynie.

Timeouty: wszystkie zegary muszą tykać zgodnie

Wolny model odsłania problem, o którym przy szybkim API nie myślisz: timeouty. Przez długi czas moje zapytanie przechodziło przez aplikację, potem przez , na końcu do Ollamy — i każda warstwa miała własny zegar. Jeśli aplikacja czeka krócej niż n8n, użytkownik dostaje błąd, choć odpowiedź wciąż powstaje. Jeśli n8n utnie wcześniej niż Ollama, model pisze w próżnię.

Zasada, do której doszedłem po kilku „Error in workflow” bez wyraźnego winnego: zegary ustawia się od końca. Najpierw realistyczny czas najwolniejszej odpowiedzi modelu — z zimnym startem i długim promptem, nie ze średnią — a potem każda warstwa wyżej dostaje co najmniej tyle samo. I dokumentację, czemu tyle, bo za pół roku te wartości wyglądają na przypadkowe.

Te zegary potrafią sięgać kilkudziesięciu minut i to brzmi jak wieczność — zwykle nią jest. Ale zapas liczy się na najgorszy przypadek: zimny start plus długi prompt plus dwa narzędzia po drodze. Zegar, który utnie o dziesięć sekund za wcześnie, marnuje pięć minut pracy procesora. Dziś warstw mam mniej, więc i zegarów mniej — dlaczego, o tym zaraz.

Bug, którego nie naprawisz: dlaczego agent wyprowadził się z n8n

Ta historia zaczęła się od komunikatu, który znają wszyscy użytkownicy węzła AI Agent w n8n: Received tool input did not match expected schema. Agent, który miesiącami odpowiadał na pytania o stan serwera, nagle padał na każdym wywołaniu narzędzia. Pierwszy podejrzany: za słaby model. Wymieniłem go na lepszy — bez zmian. Drugi podejrzany: model gubi się przy kilkunastu narzędziach. Test rozstrzygający: narzędzie bez żadnych parametrów, do którego model wysłał poprawne, puste argumenty — też padło.

Prawdziwy winowajca siedział w środku n8n — w wersji 2.25, której wtedy używałem: po jednej z aktualizacji argumenty wywołań narzędzi ginęły między biblioteką LLM a wykonaniem, a walidator dostawał pustkę niezależnie od tego, co odpowiedział model. Na GitHubie n8n zgłoszeń z tym komunikatem jest kilka (#14399, #17241, #23588) — w chwili, gdy to piszę, są zamykane jako nieaktualne albo „problem wsparcia”, bez poprawki. Powrót do starszej wersji? Niemożliwy — n8n migruje bazę danych w jedną stronę. To jest moment, w którym samodzielne hostowanie pokazuje zęby: bug w cudzej warstwie pośredniej, na który nie masz wpływu, a którego nie możesz ominąć.

Wyjście okazało się prostsze, niż brzmiało: wyrzucić warstwę pośrednią. Pętla agenta to w gruncie rzeczy niewielki program — wyślij rozmowę z listą narzędzi do Ollamy, jeśli model chce użyć narzędzia, wykonaj je i dołóż wynik, powtarzaj aż powstanie odpowiedź. Napisałem ją bezpośrednio w backendzie aplikacji: bez frameworka do LLM, bez wizualnego edytora, za to z pełną kontrolą nad każdym timeoutem, promptem i logiem. n8n dalej robi u mnie świetną robotę w automatyzacjach — pipeline alertów, powiadomienia, harmonogramy — ale rozmowa agenta z modelem przestała przez niego przechodzić.

Efekt uboczny okazał się ważniejszy niż sama naprawa: pełna kontrola nad torem pozwoliła ułożyć prompt pod cache (te dziewięć sekund zamiast stu czterech z początku wpisu), trzymać właściwy model w pamięci i strumieniować odpowiedź na żywo. Rozmowa z narzędziem, która w starym torze zajmowała ~250 sekund, dziś zamyka się w ~30. Ten sam procesor, ten sam model klasy 4B.

!Problem
Każda warstwa między aplikacją a modelem to cudzy kod, cudze bugi i cudze tempo naprawiania. Na CPU, gdzie każda sekunda jest droga, warstwa pośrednia musi na siebie zarabiać — a pętla agenta to za mało kodu, żeby oddawać ją frameworkowi, którego nie kontrolujesz.

Gdzie lokalny LLM ma sens — a gdzie go nie ma

Po roku rozkład jest klarowny. Lokalna AI sprawdza się w zadaniach, które biegną w tle i nie każą nikomu czekać: streszczenia alertów z monitoringu, embeddingi i wyszukiwanie semantyczne, tagowanie treści, domowy agent, który odpowiada w kilkadziesiąt sekund i steruje odkurzaczem. Wspólny mianownik: nikt nie siedzi i nie patrzy na kursor — a od kiedy odpowiedź agenta strumieniuje się na żywo, nawet to czekanie wygląda jak praca, nie jak zawieszenie.

Nie sprawdza się tam, gdzie oczekiwanie ma być rozmową: interaktywny czat z odpowiedzią w dwie sekundy, długie konteksty przetwarzane na żywo, cokolwiek, co konkuruje z płynnością chmurowego czatu. Można się na to obrazić albo to zaakceptować i projektować z myślą o mocnych stronach — moja lista alertów nie potrzebuje odpowiedzi w dwie sekundy, potrzebuje odpowiedzi, która nie wysłała treści alertu poza dom.

⇄Kompromis
Lokalny LLM na CPU to nie jest tańszy ChatGPT. To inne narzędzie: wolniejsze, całkowicie prywatne i przewidywalne kosztowo. Kto próbuje nim zastąpić chmurowy czat, będzie zawiedziony; kto da mu zadania w tle, przestanie rozumieć, po co miałby je wysyłać na zewnątrz.

Od czego zacząć

Nie od zakupu GPU. Od jednego małego modelu — 3-4B parametrów — i jednego prawdziwego zadania: streszczaj mi to, otaguj tamto, znajdź podobne notatki. Jeśli po tygodniu zadanie faktycznie się dzieje samo, warto inwestować dalej. Jeśli nie — oszczędziłeś na karcie graficznej, a docker compose down kosztuje mniej niż żal po wydanych pieniądzach.

U mnie się przyjęło. Alerty przychodzą streszczone po polsku, agent gasi światła i pilnuje pamięci rodzinnych ustaleń, a wyszukiwanie w notatkach rozumie, że „urodziny taty” i „prezent dla ojca” to ten sam temat. Wszystko na sześciu rdzeniach z 2018 roku — powoli, lokalnie i dokładnie tak, jak chciałem. A GPU? Zawsze można dołożyć — ale to już zupełnie inna historia.

Najczęstsze pytania

Czy potrzebuję GPU, żeby uruchomić lokalny LLM?

Nie — ale musisz dopasować oczekiwania. Na samym CPU modele klasy kilku miliardów parametrów dobrze nadają się do zadań w tle, a dopracowany tor (cache promptu, model trzymany w RAM) skraca odpowiedź agenta z narzędziami do kilkudziesięciu sekund. Interaktywnego czatu jak z ChatGPT z tego nie będzie.

Czy lokalny model zastąpi mi ChatGPT?

Do innych rzeczy służy. Lokalny LLM wygrywa tam, gdzie liczy się prywatność i praca w tle: streszczenia, tagowanie, wyszukiwanie semantyczne, automatyzacje. Do szybkiej rozmowy i trudnych zadań chmurowe modele pozostają wygodniejsze.

Od jakiego modelu zacząć bez GPU?

Od małego: 3-4B parametrów (np. phi4-mini albo qwen w wersji 4B). Zmieści się w RAM, odpowie w rozsądnym czasie i pokaże, czy lokalny LLM w ogóle rozwiązuje twój problem. Większe modele docenisz dopiero, gdy wiesz, po co ci one.

Co zmienia dodanie GPU?

Skalę. Przetwarzanie promptu i generowanie spadają z minut do sekund, a modele 11B+ przestają być ćwiczeniem z cierpliwości. Architektura zostaje ta sama — Ollama, te same narzędzia, ten sam tor — ogranicza tylko budżet i pobór prądu.

Tagi

#ollama#llm#ai#self-hosting#homelab
← Powrót do bloga

← Poprzedni

n8n w homelabie: wzorce automatyzacji, które przetrwały rok produkcji

Następny →

Notatnik z aparatem, głosem i OCR — jak zbudowałem to lokalnie