Lokalny Ollama i LLM: uruchamianie modeli na własnym sprzęcie
W 2023 r. lokalne prowadzenie modelu wielkojęzykowego było sprawą tylko dla kogo
miał głęboką wiedzę techniczną: kompilowanie pliku llama.cpp, konwertowanie wag, konfigurowanie
Parametry GGML, zarządzaj złożonymi zależnościami. Potem dotarło Ollama,
i wszystko się zmieniło. Jednym poleceniem — ollama run llama3 — ktokolwiek
może w ciągu kilku minut uruchomić konkurencyjny program LLM na swoim laptopie.
Trend jest wybuchowy. Ollama osiągnęła ponad 1 milion pobrań miesięcznie w 2024 r., z 300% wzrostem rok do roku. Rynek wyraźnie wybiera prywatność (dane nie opuszczają urządzenia), zerowy koszt API, personalizacja (modele niestandardowe, stałe podpowiedzi systemowe) e dostępność nieaktywny. Te zalety napędzają migrację wielu procesów biznesowych, od interfejsu API w chmurze po wdrożenie lokalne.
W tym przewodniku od instalacji do produkcji: jak skonfigurować Ollamę, wybierz odpowiedni model, utwórz niestandardowe pliki modeli, udostępnij interfejsy API REST, zintegruj LangChain dla rurociągów RAG offline i dostrajania modeli GGUF do konkretnych przypadków użycia, na laptopach, serwerach i Raspberry Pi.
Czego się nauczysz
- Instalacja Ollama na systemach Windows, macOS i Linux
- Przewodnik po wyborze modelu: Llama, Qwen, Phi, Gemma, Mistral, DeepSeek
- Modelfile: twórz niestandardowych asystentów z niestandardowymi parametrami
- Ollama REST API: Integracja z Pythonem, JavaScriptem i cURL
- Ollama z Pythonem za pośrednictwem oficjalnej biblioteki i zgodności z OpenAI
- Potok RAG offline z LangChain i FAISS
- Wdrożenie na Raspberry Pi i serwerze bezgłowym z systememd
- OpenWebUI: Całkowicie offline interfejs podobny do ChatGPT
- Szczegółowe testy porównawcze i wybór poziomu kwantyzacji
- Zarządzanie wieloma modelami i optymalizacja produkcji
Jak Ollama działa wewnętrznie
Przed użyciem Ollama warto zrozumieć, co robi pod maską. Ollama i opakowanie wokół lama.cpp, silnik wnioskowania C++, który to umożliwił uruchamiaj skwantowane modele na standardowym sprzęcie. Ollama dodaje:
- Rejestr modeli: System pull/push podobny do Docker Hub dla modeli GGUF
- Serwer API REST: udostępnia lokalny serwer HTTP na porcie 11434
- Buforowanie modelu: utrzymuje modele ładowane w pamięci RAM pomiędzy żądaniami
- Wykrywanie procesora graficznego: automatycznie wykrywa NVIDIA CUDA, AMD ROCm i Apple Metal
- Zarządzanie kontekstem: zarządza oknem kontekstowym i pamięcią podręczną KV
# Architettura Ollama - diagramma semplificato
#
# Client (Python/cURL/Browser)
# |
# v
# [Ollama REST API - port 11434]
# |
# v
# [Model Manager] --- ~/.ollama/models/ (storage GGUF)
# |
# v
# [llama.cpp backend]
# |
# _____|______
# | |
# [CPU] [GPU/Metal]
# ARM/x86 CUDA/ROCm/Metal
#
# Formato modelli: GGUF (GPT-Generated Unified Format)
# Quantization levels: Q4_K_M, Q5_K_M, Q6_K, Q8_0, F16
#
# Dove sono i modelli sul disco:
# macOS/Linux: ~/.ollama/models/
# Windows: C:\Users\USERNAME\.ollama\models\
#
# Struttura directory:
# ~/.ollama/models/
# ├── blobs/ (file GGUF binari, identificati da SHA256)
# └── manifests/ (metadata: quale blob = quale modello:tag)
import subprocess, json
def ollama_status():
"""Controlla status Ollama e modelli caricati."""
result = subprocess.run(
["ollama", "list"], capture_output=True, text=True
)
print("Modelli installati:")
print(result.stdout)
# Controlla processo
ps = subprocess.run(
["pgrep", "-x", "ollama"], capture_output=True, text=True
)
running = ps.returncode == 0
print(f"Ollama in esecuzione: {running}")
ollama_status()
Instalacja i pierwsze kroki
Ollama instaluje się za pomocą jednego polecenia i nie wymaga żadnej konfiguracji. Wsparcie macOS (Apple Silicon i Intel), Windows (z procesorami graficznymi NVIDIA lub AMD) i Linux (deb/rpm/generic).
# ================================================================
# INSTALLAZIONE OLLAMA
# ================================================================
# macOS / Linux (un comando):
# curl -fsSL https://ollama.com/install.sh | sh
# Windows:
# Download installer da https://ollama.com/download
# (include supporto CUDA automatico se GPU NVIDIA presente)
# Verifica installazione:
# ollama --version
# ollama serve (avvia il server manualmente se non e attivo)
# ================================================================
# COMANDI BASE
# ================================================================
# Esegui un modello (download automatico se non presente)
# ollama run llama3.2
# Lista modelli disponibili localmente
# ollama list
# Pull senza eseguire (per pre-scaricare)
# ollama pull llama3.2:3b
# Informazioni dettagliate su un modello
# ollama show llama3.2
# Rimuovi un modello (libera spazio disco)
# ollama rm llama3.2:old-version
# Copia un modello con nome diverso
# ollama cp llama3.2 my-custom-model
# ================================================================
# VARIABILI D'AMBIENTE UTILI
# ================================================================
# Ascolta su tutte le interfacce (per accesso dalla rete)
# export OLLAMA_HOST=0.0.0.0:11434
# Directory custom per i modelli
# export OLLAMA_MODELS=/mnt/ssd/ollama-models
# Numero massimo richieste parallele (default: 1)
# export OLLAMA_NUM_PARALLEL=4
# Massimo modelli in memoria (default: 1)
# export OLLAMA_MAX_LOADED_MODELS=2
# Tempo prima di scaricare un modello dalla RAM (default: 5m)
# export OLLAMA_KEEP_ALIVE=30m
# ================================================================
# MODELLI POPOLARI e REQUISITI HARDWARE (2025)
# ================================================================
MODELS_GUIDE = {
# Modelli PICCOLI (per Raspberry Pi / laptop 8 GB)
"qwen2.5:1.5b": {"size": "0.9 GB", "ram": "2 GB", "quality": 7, "rpi5_tps": 4.5},
"llama3.2:1b": {"size": "1.3 GB", "ram": "2 GB", "quality": 7, "rpi5_tps": 5.1},
"phi3.5:mini": {"size": "2.2 GB", "ram": "4 GB", "quality": 8, "rpi5_tps": 2.8},
"qwen2.5:3b": {"size": "1.9 GB", "ram": "4 GB", "quality": 8, "rpi5_tps": 2.1},
"gemma2:2b": {"size": "1.6 GB", "ram": "3 GB", "quality": 8, "rpi5_tps": 3.2},
# Modelli MEDI (laptop 16+ GB / desktop)
"llama3.2:3b": {"size": "2.0 GB", "ram": "4 GB", "quality": 8, "rpi5_tps": 1.8},
"mistral:7b": {"size": "4.1 GB", "ram": "8 GB", "quality": 9, "rpi5_tps": 0.8},
"llama3.1:8b": {"size": "4.7 GB", "ram": "8 GB", "quality": 9, "rpi5_tps": 0.6},
"qwen2.5:7b": {"size": "4.4 GB", "ram": "8 GB", "quality": 9, "rpi5_tps": 0.7},
"deepseek-r1:8b": {"size": "4.9 GB", "ram": "8 GB", "quality": 9, "rpi5_tps": 0.5},
# Modelli GRANDI (workstation 24+ GB / server)
"llama3.1:70b": {"size": "40 GB", "ram": "64 GB", "quality": 10, "rpi5_tps": None},
"qwen2.5:72b": {"size": "41 GB", "ram": "64 GB", "quality": 10, "rpi5_tps": None},
"deepseek-r1:32b": {"size": "19 GB", "ram": "32 GB", "quality": 10, "rpi5_tps": None},
}
print("Modelli consigliati per hardware:")
print(" Raspberry Pi 5 (8GB): qwen2.5:1.5b, llama3.2:1b, gemma2:2b")
print(" Laptop 16GB: llama3.1:8b, qwen2.5:7b, mistral:7b")
print(" Mac M2/M3 (24GB): llama3.1:8b, gemma2:9b, qwen2.5:14b")
print(" Workstation 48GB+: llama3.1:70b, deepseek-r1:32b")
Poziomy kwantyzacji: który GGUF wybrać?
Kiedy to się skończy ollama pull llama3.1:8b, Ollama automatycznie pobierze plik
optymalna kwantyzacja dla Twojego sprzętu. Ale możliwy jest wyraźny wybór
poziom kwantyzacji, z ważnym kompromisem w zakresie jakości/rozmiaru/szybkości.
Przewodnik po poziomach kwantyzacji GGUF
| Tagi / Format | Bity/waga | Rozmiar (7B) | Utrata zakłopotania | Polecane dla |
|---|---|---|---|---|
| Q2_K | 2,63 bity | 2,7 GB | +15-20% | Tylko jeśli RAM jest absolutnym ograniczeniem |
| Q4_K_S | 4,37 bitów | 4,5 GB | +2-3% | Dobra równowaga szybkości i jakości |
| Q4_K_M | 4,58 bitów | 4,8 GB | +1-2% | Zalecane ustawienie domyślne (najlepszy punkt) |
| Q5_K_M | 5,68 bitów | 5,7 GB | +0,5-1% | Najwyższa jakość z <6 GB RAM |
| Q6_K | 6,57 bitów | 6,6 GB | +0,1-0,3% | Prawie identyczny jak F16, wymaga więcej pamięci RAM |
| Q8_0 | 8,5 bita | 8,5 GB | ~0% | Najwyższa jakość, wymaga 9+ GB RAM |
| F16 | 16 bitów | 14 GB | 0% (wartość bazowa) | Szkolenie/dostrajanie, a nie wyciąganie wniosków |
# Scegliere esplicitamente la quantizzazione su Ollama
# I tag dipendono dal modello - usa 'ollama show' per vedere le opzioni
# Default (Ollama sceglie automaticamente, solitamente Q4_K_M):
# ollama pull llama3.1:8b
# Specifica quantizzazione manualmente (sintassi dipende dal modello):
# ollama pull llama3.1:8b-instruct-q4_K_M
# ollama pull llama3.1:8b-instruct-q5_K_M
# ollama pull llama3.1:8b-instruct-q8_0
# Per modelli HuggingFace non su Ollama registry:
# Scarica GGUF manualmente e importa con Modelfile:
IMPORT_GGUF_MODELFILE = """
FROM ./path/to/model-q4_k_m.gguf
PARAMETER temperature 0.7
PARAMETER num_ctx 4096
SYSTEM "Sei un assistente utile."
"""
# echo IMPORT_GGUF_MODELFILE > Modelfile
# ollama create mio-modello -f Modelfile
# ollama run mio-modello
# Confronto performance Q4 vs Q5 vs Q8 (Llama 3.1 8B, MacBook M3 Pro):
QUANT_BENCHMARK = {
"Q4_K_M": {"size_gb": 4.8, "tps": 38.2, "quality_vs_f16": "98.5%"},
"Q5_K_M": {"size_gb": 5.7, "tps": 33.1, "quality_vs_f16": "99.2%"},
"Q6_K": {"size_gb": 6.6, "tps": 29.4, "quality_vs_f16": "99.7%"},
"Q8_0": {"size_gb": 8.5, "tps": 24.8, "quality_vs_f16": "99.9%"},
}
for quant, data in QUANT_BENCHMARK.items():
print(f"{quant}: {data['size_gb']}GB, {data['tps']}t/s, qualità={data['quality_vs_f16']}")
Modelfile: Twórz niestandardowych asystentów
Un Plik modelu oraz mechanizm Ollama do tworzenia niestandardowych szablonów. Pozwala na zdefiniowanie: modelu podstawowego, podpowiedzi systemowej, parametrów generacji (temperatura, top_p, okno kontekstowe), a nawet rozszerzyć model o dodatkowe pliki. To jest równoważne do pliku Dockerfile, ale dla modeli językowych.
# ================================================================
# ESEMPI PRATICI DI MODELFILE
# ================================================================
# --- Modelfile 1: Assistente tecnico italiano ---
MODEL_FILE_TECH = """
FROM qwen2.5:7b
# Parametri di generazione
PARAMETER temperature 0.3 # Bassa = risposte più deterministiche
PARAMETER top_p 0.9 # Nucleus sampling
PARAMETER top_k 40 # Top-k sampling
PARAMETER num_ctx 8192 # Context window (4096-32768)
PARAMETER repeat_penalty 1.1 # Evita ripetizioni
# System prompt (definisce il comportamento del modello)
SYSTEM \"\"\"
Sei un assistente tecnico esperto in Python, deep learning e machine learning.
Rispondi SEMPRE in italiano, in modo conciso e tecnico.
Quando mostri codice, usa sempre blocchi markdown con il linguaggio specificato.
Se non sei sicuro di qualcosa, dillo esplicitamente.
Non inventare informazioni o API che non esistono.
\"\"\"
# Messaggio di benvenuto
MESSAGE user "Ciao!"
MESSAGE assistant "Ciao! Sono il tuo assistente tecnico. Come posso aiutarti oggi con Python, deep learning o machine learning?"
"""
# Crea il modello:
# echo MODEL_FILE_TECH > Modelfile-tech-it
# ollama create assistente-tech-it -f Modelfile-tech-it
# ollama run assistente-tech-it
# --- Modelfile 2: Code review assistant ---
MODEL_FILE_CODE = """
FROM llama3.1:8b
PARAMETER temperature 0.1 # Molto deterministico per codice
PARAMETER num_ctx 16384 # Context grande per file lunghi
PARAMETER repeat_penalty 1.05
SYSTEM \"\"\"
You are an expert code reviewer. When reviewing code:
1. Identify bugs, security issues, and performance problems
2. Suggest specific improvements with code examples
3. Follow PEP8/language standards
4. Be concise: list issues with severity (CRITICAL/HIGH/MEDIUM/LOW)
Be direct and actionable. Never hallucinate API methods.
\"\"\"
"""
# --- Modelfile 3: RAG con documenti aziendali ---
MODEL_FILE_RAG = """
FROM qwen2.5:7b
PARAMETER temperature 0.1
PARAMETER num_ctx 32768 # Contesto lungo per documenti
PARAMETER repeat_penalty 1.0
SYSTEM \"\"\"
Sei un assistente che risponde SOLO basandosi sui documenti forniti nel contesto.
Se non trovi la risposta nel contesto, dì esattamente: "Non ho informazioni su questo nei documenti forniti."
Non aggiungere mai informazioni esterne. Cita sempre il documento sorgente nella risposta.
Rispondi in italiano.
\"\"\"
"""
print("Modelfile pronti. Per creare:")
print(" ollama create assistente-tech-it -f Modelfile-tech-it")
print(" ollama create code-reviewer -f Modelfile-code")
print(" ollama create rag-assistant -f Modelfile-rag")
Ollama REST API: Integracja z Pythonem
Ollama udostępnia dwa interfejsy API: własny natywny interfejs API i interfejs API zgodny z OpenAI. Zgodność z OpenAI pozwala na łatwą wymianę interfejsów API OpenAI na Ollama zmianę bazowego adresu URL – bez zmiany kodu aplikacji.
# pip install ollama openai requests
import ollama
import json, time
from typing import Iterator
# ================================================================
# 1. LIBRERIA OLLAMA UFFICIALE (Python)
# ================================================================
# Chat semplice (non-streaming)
def chat_simple(model: str, message: str) -> str:
response = ollama.chat(
model=model,
messages=[{"role": "user", "content": message}]
)
return response['message']['content']
# Chat con streaming (token per token)
def chat_streaming(model: str, messages: list) -> Iterator[str]:
stream = ollama.chat(
model=model,
messages=messages,
stream=True
)
for chunk in stream:
if chunk['message']['content']:
yield chunk['message']['content']
# Embeddings per RAG (usa nomic-embed-text o mxbai-embed-large)
def get_embedding(model: str, text: str) -> list:
response = ollama.embeddings(model=model, prompt=text)
return response['embedding']
# Chat con immagini (modelli multimodali: llava, bakllava, moondream)
def chat_with_image(model: str, prompt: str, image_path: str) -> str:
import base64
with open(image_path, "rb") as f:
image_data = base64.b64encode(f.read()).decode()
response = ollama.chat(
model="llava:7b", # oppure moondream
messages=[{
"role": "user",
"content": prompt,
"images": [image_data]
}]
)
return response['message']['content']
# Chatbot con storia della conversazione
def interactive_chat(model: str = "llama3.2:3b"):
history = []
print(f"Chat con {model} (digita 'exit' per uscire)")
while True:
user_input = input("Tu: ").strip()
if user_input.lower() == "exit":
break
history.append({"role": "user", "content": user_input})
print("Assistant: ", end="", flush=True)
full_response = ""
for chunk in chat_streaming(model, history):
print(chunk, end="", flush=True)
full_response += chunk
print()
history.append({"role": "assistant", "content": full_response})
# ================================================================
# 2. API COMPATIBILE OPENAI (drop-in replacement)
# ================================================================
from openai import OpenAI
# Cambia solo la base_url: zero modifiche al codice!
client = OpenAI(
base_url="http://localhost:11434/v1",
api_key="ollama" # Qualsiasi stringa
)
def chat_openai_compatible(model: str, prompt: str) -> str:
"""Identico all'API OpenAI, ma usa Ollama in locale."""
response = client.chat.completions.create(
model=model,
messages=[{"role": "user", "content": prompt}],
temperature=0.7,
max_tokens=500
)
return response.choices[0].message.content
# ================================================================
# 3. RAW REST API (senza librerie Python)
# ================================================================
import requests
def ollama_raw_api(model: str, prompt: str, stream: bool = False) -> str:
"""Chiama l'API Ollama direttamente con requests."""
resp = requests.post(
"http://localhost:11434/api/generate",
json={
"model": model,
"prompt": prompt,
"stream": stream,
"options": {
"temperature": 0.7,
"num_predict": 200,
"num_ctx": 4096
}
},
timeout=120
)
if not stream:
return resp.json()["response"]
else:
# Streaming: ogni riga e un JSON
result = ""
for line in resp.iter_lines():
if line:
data = json.loads(line)
result += data.get("response", "")
if data.get("done"):
break
return result
# ================================================================
# 4. BENCHMARK VELOCITA MODELLI
# ================================================================
def benchmark_model(model: str, n_runs: int = 3):
"""Misura velocità di generazione in token/s."""
prompt = "Explain quantum computing in one paragraph."
results = []
for _ in range(n_runs):
t0 = time.time()
response = ollama.generate(
model=model,
prompt=prompt,
options={"num_predict": 100}
)
elapsed = time.time() - t0
eval_count = response.get('eval_count', 100)
tps = eval_count / elapsed
results.append(tps)
avg_tps = sum(results) / len(results)
print(f"{model}: {avg_tps:.1f} token/s (media {n_runs} run)")
return avg_tps
# Risultati tipici su MacBook M3 Pro 18GB:
# qwen2.5:1.5b ~85 t/s
# llama3.2:3b ~62 t/s
# qwen2.5:7b ~42 t/s
# llama3.1:8b ~38 t/s
# qwen2.5:14b ~22 t/s
# llama3.1:70b ~8 t/s
Ollama z LangChain: RAG Pipeline offline
Ollama integruje się natywnie z LangChain, umożliwiając budowę rurociągów RAG
(Generacja rozszerzona o pobieranie) całkowicie offline. To, a zwłaszcza
istotne w przypadku aplikacji korporacyjnych, które nie mogą wysyłać wrażliwych danych do chmury.
Model nomic-embed-text i optymalne do osadzania lokalnego.
# pip install langchain langchain-ollama langchain-community
# pip install faiss-cpu chromadb pypdf
from langchain_ollama import OllamaLLM, OllamaEmbeddings
from langchain_community.vectorstores import FAISS
from langchain_community.document_loaders import (
DirectoryLoader, TextLoader, PyPDFLoader
)
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain.chains import RetrievalQA, ConversationalRetrievalChain
from langchain.prompts import PromptTemplate
from langchain.memory import ConversationBufferMemory
import os
# ================================================================
# PIPELINE RAG OFFLINE CON OLLAMA - VERSIONE PRODUZIONE
# ================================================================
class OllamaRAGSystem:
"""
Sistema RAG completo e offline con Ollama.
Supporta PDF, TXT e directory intere.
Usa FAISS per vector store locale.
"""
def __init__(
self,
llm_model: str = "llama3.1:8b",
embed_model: str = "nomic-embed-text", # ollama pull nomic-embed-text
kb_dir: str = "./knowledge_base"
):
self.llm_model = llm_model
self.embed_model = embed_model
self.kb_dir = kb_dir
self.embeddings = OllamaEmbeddings(model=embed_model)
self.llm = OllamaLLM(
model=llm_model,
temperature=0.1,
num_ctx=8192,
num_predict=512
)
self.vectorstore = None
def load_documents(self, docs_dir: str) -> list:
"""Carica documenti da directory (PDF, TXT, MD)."""
docs = []
# Carica TXT e MD
txt_loader = DirectoryLoader(
docs_dir, glob="**/*.txt", loader_cls=TextLoader
)
docs.extend(txt_loader.load())
# Carica PDF
for pdf_file in os.listdir(docs_dir):
if pdf_file.endswith(".pdf"):
loader = PyPDFLoader(os.path.join(docs_dir, pdf_file))
docs.extend(loader.load())
print(f"Caricati {len(docs)} documenti da {docs_dir}")
return docs
def build_knowledge_base(self, docs_dir: str) -> None:
"""Crea e salva la knowledge base da una directory."""
documents = self.load_documents(docs_dir)
splitter = RecursiveCharacterTextSplitter(
chunk_size=1000,
chunk_overlap=200,
separators=["\n\n", "\n", " ", ""]
)
texts = splitter.split_documents(documents)
print(f"Creati {len(texts)} chunk")
self.vectorstore = FAISS.from_documents(texts, self.embeddings)
self.vectorstore.save_local(self.kb_dir)
print(f"Knowledge base salvata in {self.kb_dir}")
def load_knowledge_base(self) -> None:
"""Carica knowledge base esistente da disco."""
self.vectorstore = FAISS.load_local(
self.kb_dir, self.embeddings,
allow_dangerous_deserialization=True
)
print(f"Knowledge base caricata: {self.vectorstore.index.ntotal} vettori")
def create_qa_chain(self) -> RetrievalQA:
"""Crea chain per Q&A su documenti."""
prompt_template = """Usa il seguente contesto per rispondere alla domanda.
Se non trovi la risposta nel contesto, dì esplicitamente che non lo sai.
Non inventare informazioni non presenti nel contesto.
Contesto:
{context}
Domanda: {question}
Risposta in italiano:"""
PROMPT = PromptTemplate(
template=prompt_template,
input_variables=["context", "question"]
)
retriever = self.vectorstore.as_retriever(
search_type="mmr", # Maximum Marginal Relevance (più diversificato)
search_kwargs={"k": 5, "fetch_k": 20}
)
return RetrievalQA.from_chain_type(
llm=self.llm,
chain_type="stuff",
retriever=retriever,
chain_type_kwargs={"prompt": PROMPT},
return_source_documents=True
)
def ask(self, question: str, qa_chain: RetrievalQA) -> dict:
"""Poni una domanda al sistema RAG."""
result = qa_chain.invoke({"query": question})
sources = list(set([
doc.metadata.get("source", "Unknown")
for doc in result["source_documents"]
]))
return {
"answer": result["result"],
"sources": sources,
"n_docs": len(result["source_documents"])
}
# Utilizzo:
# rag = OllamaRAGSystem(llm_model="llama3.1:8b")
# rag.build_knowledge_base("./documenti_aziendali")
# chain = rag.create_qa_chain()
# result = rag.ask("Qual e la policy ferie aziendale?", chain)
# print(result["answer"])
# print("Fonti:", result["sources"])
print("Sistema RAG pronto!")
OpenWebUI: interfejs ChatGPT dla Ollama
Otwarty interfejs WWW (dawniej Ollama WebUI) i najczęściej używany interfejs Ollama, z doświadczeniem użytkownika identycznym jak ChatGPT, ale całkowicie offline. Wsparcie czat, przesyłanie dokumentów, zarządzanie rozmowami, szybkie udostępnianie, zintegrowany RAG i wielomodowy dla obrazów.
# ================================================================
# SETUP OPENWEBUI CON DOCKER
# ================================================================
# Caso 1: Ollama sullo stesso host
# docker run -d -p 3000:8080 \
# -v open-webui:/app/backend/data \
# -e OLLAMA_BASE_URL=http://host.docker.internal:11434 \
# --name open-webui \
# ghcr.io/open-webui/open-webui:main
# Caso 2: OpenWebUI con Ollama integrato (tutto in uno)
# docker run -d -p 3000:8080 \
# -v ollama:/root/.ollama \
# -v open-webui:/app/backend/data \
# --gpus all \
# --name open-webui \
# ghcr.io/open-webui/open-webui:ollama
# Accesso: http://localhost:3000
# ================================================================
# DOCKER COMPOSE (raccomandato per produzione)
# ================================================================
DOCKER_COMPOSE = """
version: '3.8'
services:
ollama:
image: ollama/ollama:latest
container_name: ollama
ports:
- "11434:11434"
volumes:
- ollama_data:/root/.ollama
environment:
- OLLAMA_NUM_PARALLEL=4
- OLLAMA_MAX_LOADED_MODELS=2
# Per GPU NVIDIA:
# deploy:
# resources:
# reservations:
# devices:
# - driver: nvidia
# count: all
# capabilities: [gpu]
restart: unless-stopped
open-webui:
image: ghcr.io/open-webui/open-webui:main
container_name: open-webui
ports:
- "3000:8080"
volumes:
- webui_data:/app/backend/data
environment:
- OLLAMA_BASE_URL=http://ollama:11434
- WEBUI_AUTH=True
- WEBUI_SECRET_KEY=cambia-questa-chiave-segreta
depends_on:
- ollama
restart: unless-stopped
volumes:
ollama_data:
webui_data:
"""
# ================================================================
# OLLAMA COME SERVIZIO SYSTEMD (Linux production)
# ================================================================
SYSTEMD_SERVICE = """
# /etc/systemd/system/ollama.service
[Unit]
Description=Ollama LLM Service
After=network-online.target
[Service]
ExecStart=/usr/bin/ollama serve
User=ollama
Group=ollama
Restart=always
RestartSec=3
Environment="OLLAMA_HOST=0.0.0.0"
Environment="OLLAMA_MODELS=/opt/ollama/models"
Environment="OLLAMA_NUM_PARALLEL=2"
Environment="OLLAMA_MAX_LOADED_MODELS=2"
Environment="OLLAMA_KEEP_ALIVE=10m"
[Install]
WantedBy=default.target
"""
# sudo systemctl enable ollama
# sudo systemctl start ollama
# sudo journalctl -u ollama -f # Log in real-time
print("Setup Ollama come servizio completato!")
Wdrożenie na Raspberry Pi: zoptymalizowana konfiguracja
Raspberry Pi 5 z 8 GB pamięci RAM to najbardziej dostępne urządzenie brzegowe dla lokalnych LLM. Przy odpowiedniej konfiguracji modele o parametrach 1,5B osiągają 4-5 tokenów/s — w zupełności wystarczające dla wielu zastosowań innych niż czas rzeczywisty: chatboty o małej liczbie powtórzeń, wsadowa analiza tekstu, automatyzacja z wyzwalaczami zdarzeń.
# ================================================================
# OLLAMA SU RASPBERRY PI 5 (setup ottimizzato)
# ================================================================
# Installazione (identica a Linux x86):
# curl -fsSL https://ollama.com/install.sh | sh
# Configurazione ottimale per RPi5 in /etc/environment:
# OLLAMA_NUM_PARALLEL=1 # Un request alla volta (RAM limitata)
# OLLAMA_MAX_LOADED_MODELS=1 # Un modello in memoria
# OLLAMA_KEEP_ALIVE=5m # Scarica modello dopo 5 min inattivita
# OLLAMA_NUM_THREAD=4 # Tutti i core Cortex-A76
# Modelli raccomandati per RPi5 (8GB):
# ollama pull qwen2.5:1.5b (veloce: ~4.5 t/s, 1.8 GB RAM)
# ollama pull llama3.2:1b (bilanciato: ~5.1 t/s, 1.4 GB RAM)
# ollama pull gemma2:2b (qualità: ~3.2 t/s, 2.5 GB RAM)
import ollama
import time, statistics, psutil
def benchmark_ollama_rpi(model: str = "qwen2.5:1.5b",
n_tests: int = 5):
"""Test velocità e consistenza su RPi."""
prompt = "Spiega in 3 frasi cos'è il machine learning."
results = []
latencies_to_first = []
print(f"Benchmark {model} su {n_tests} test...")
for i in range(n_tests):
t0 = time.time()
first_token = None
full_response = ""
for chunk in ollama.chat(
model=model,
messages=[{"role": "user", "content": prompt}],
stream=True,
options={"temperature": 0, "top_k": 1, "num_predict": 50}
):
content = chunk['message']['content']
if content and first_token is None:
first_token = time.time() - t0
latencies_to_first.append(first_token * 1000)
full_response += content
elapsed = time.time() - t0
n_tokens = len(full_response.split()) # Approssimazione
tps = n_tokens / elapsed
results.append(tps)
print(f" Test {i+1}: {tps:.1f} t/s, TTFT: {first_token*1000:.0f}ms")
mean_tps = statistics.mean(results)
mean_ttft = statistics.mean(latencies_to_first)
mem = psutil.virtual_memory()
print(f"\nRisultati {model} su RPi5:")
print(f" Velocita media: {mean_tps:.1f} t/s")
print(f" TTFT medio: {mean_ttft:.0f} ms")
print(f" RAM usata: {mem.used/(1024**3):.1f} GB / {mem.total/(1024**3):.1f} GB")
return mean_tps
# ================================================================
# AUTOMAZIONE: Aggiornamento modelli e monitoring
# ================================================================
import subprocess, datetime
def update_ollama_models(models: list = ["qwen2.5:1.5b", "nomic-embed-text"]):
"""Aggiorna i modelli Ollama (da eseguire con cron)."""
log = []
for model in models:
print(f"Aggiornamento {model}...")
result = subprocess.run(
["ollama", "pull", model],
capture_output=True, text=True, timeout=600
)
status = "OK" if result.returncode == 0 else "FAIL"
log.append({
"model": model,
"status": status,
"time": datetime.datetime.now().isoformat()
})
print(f" {model}: {status}")
return log
# Cron job consigliato (ogni domenica alle 3:00):
# 0 3 * * 0 /usr/bin/python3 /home/pi/update_models.py >> /var/log/ollama-update.log 2>&1
Prawdziwe studium przypadku: Chatbot biznesowy offline
Prawdziwy przypadek użycia: firma zarządzająca dokumentami poufnymi (umowy, polityki kadrowe, instrukcje techniczne) chce wewnętrznego chatbota bez udostępniania danych w chmurze. Z Ollamą + RAG buduje całkowicie szczelny system w mniej niż jeden dzień.
# ================================================================
# CHATBOT AZIENDALE OFFLINE - Stack completo
# ================================================================
# Stack:
# - Ollama con llama3.1:8b (o qwen2.5:7b per italiano migliore)
# - nomic-embed-text per embeddings
# - FAISS per vector store
# - FastAPI per REST API
# - OpenWebUI per interfaccia utente
# fastapi_chatbot.py
from fastapi import FastAPI, BackgroundTasks
from pydantic import BaseModel
from typing import Optional
import ollama, json
from pathlib import Path
app = FastAPI(title="Corporate AI Assistant", version="2.0")
# Stato globale (in produzione usa Redis)
conversation_store = {}
class ChatRequest(BaseModel):
session_id: str
message: str
model: str = "qwen2.5:7b"
use_rag: bool = True
class ChatResponse(BaseModel):
session_id: str
response: str
sources: list = []
model: str
tokens_per_sec: float
@app.post("/chat", response_model=ChatResponse)
async def chat(request: ChatRequest):
# Recupera storia conversazione
if request.session_id not in conversation_store:
conversation_store[request.session_id] = []
history = conversation_store[request.session_id]
# Aggiungi contesto RAG se richiesto
context = ""
sources = []
if request.use_rag and rag_system and rag_system.vectorstore:
docs = rag_system.vectorstore.similarity_search(
request.message, k=3
)
context = "\n\n".join([d.page_content for d in docs])
sources = list(set([d.metadata.get("source", "") for d in docs]))
# Inietta contesto nel messaggio
augmented_message = f"""Contesto dai documenti aziendali:
{context}
Domanda: {request.message}"""
else:
augmented_message = request.message
history.append({"role": "user", "content": augmented_message})
# Genera risposta
t0 = time.time()
response = ollama.chat(
model=request.model,
messages=history,
options={"num_ctx": 8192, "temperature": 0.3}
)
elapsed = time.time() - t0
assistant_msg = response['message']['content']
history.append({"role": "assistant", "content": assistant_msg})
# Tronca la storia se troppo lunga (sliding window)
if len(history) > 20:
history = history[-20:]
conversation_store[request.session_id] = history
eval_count = response.get('eval_count', 50)
tps = eval_count / elapsed if elapsed > 0 else 0
return ChatResponse(
session_id=request.session_id,
response=assistant_msg,
sources=sources,
model=request.model,
tokens_per_sec=round(tps, 1)
)
@app.delete("/chat/{session_id}")
async def clear_session(session_id: str):
"""Azzera la storia di una sessione."""
if session_id in conversation_store:
del conversation_store[session_id]
return {"status": "cleared"}
@app.get("/models")
async def list_models():
"""Lista modelli disponibili su questo server Ollama."""
models = ollama.list()
return {
"models": [
{"name": m['name'], "size_gb": m['size'] / 1e9}
for m in models['models']
]
}
# Avvio: uvicorn fastapi_chatbot:app --host 0.0.0.0 --port 8080
Porównanie modeli dla typowych przypadków użycia
| Przypadek użycia | Polecany model | Min. pamięć RAM | Dlaczego |
|---|---|---|---|
| Włoski chatbot | qwen2.5:7b | 8 GB | Doskonały wielojęzyczny, długi kontekst |
| Generowanie kodu | Koder qwen2.5:7b | 8 GB | Dopracowany kod, ponad 90 języków |
| Dokumenty RAG / pytania i odpowiedzi | lama3.1:8b | 8 GB | Doskonałe przestrzeganie instrukcji, kontekst 128 tys |
| Zaawansowane rozumowanie | głębokie szukanie-r1:8b | 8 GB | Łańcuch myślowy, matematyka, logika |
| Raspberry Pi (szybki) | lama3.2:1b | 2 GB | 5+ t/s, proste zadania |
| Raspberry Pi (jakość) | qwen2.5:3b | 4 GB | Optymalny balans jakość/prędkość |
| Mac z serii M (szybki) | qwen2.5:14b | 16 GB | 22+ t/s na M2/M3, jakość zbliżona do GPT-4 |
| Analiza obrazu | llava:7b lub księżycowy sen | 8 GB | Modele multimodalne zoptymalizowane pod kątem widzenia |
Najlepsze praktyki produkcyjne
Używanie Ollama w produkcji wymaga pewnych rozważań specyficznych dla użytkowania osobisty. Oto najważniejsze wzory.
# ================================================================
# PATTERN PRODUZIONE: Load Balancing con più istanze Ollama
# ================================================================
# Se si ha più di un server con Ollama, si può fare load balancing.
# nginx.conf (upstream round-robin):
NGINX_CONFIG = """
upstream ollama_cluster {
least_conn; # Instrada alla connessione con meno richieste
server server1:11434;
server server2:11434;
server server3:11434;
}
server {
listen 80;
location /api/ {
proxy_pass http://ollama_cluster;
proxy_read_timeout 300s; # Timeout elevato per generazione lunga
proxy_connect_timeout 10s;
proxy_set_header Host $host;
}
}
"""
# ================================================================
# HEALTH CHECK E MONITORING
# ================================================================
import requests, time
def monitor_ollama(host: str = "localhost", port: int = 11434):
"""Controlla disponibilità e carico Ollama."""
try:
# API health endpoint
resp = requests.get(f"http://{host}:{port}/api/tags", timeout=5)
if resp.status_code == 200:
models = resp.json().get("models", [])
print(f"Ollama OK: {len(models)} modelli disponibili")
return True
except requests.exceptions.RequestException as e:
print(f"Ollama NON RAGGIUNGIBILE: {e}")
return False
# ================================================================
# GESTIONE ERRORI E RETRY
# ================================================================
import functools, random
def with_ollama_retry(max_attempts: int = 3, backoff: float = 1.0):
"""Decorator per retry automatico su errori Ollama."""
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
for attempt in range(max_attempts):
try:
return func(*args, **kwargs)
except Exception as e:
if attempt == max_attempts - 1:
raise
wait = backoff * (2 ** attempt) + random.uniform(0, 0.5)
print(f"Tentativo {attempt+1} fallito: {e}. Retry in {wait:.1f}s")
time.sleep(wait)
return wrapper
return decorator
@with_ollama_retry(max_attempts=3, backoff=1.0)
def robust_chat(model: str, message: str) -> str:
"""Chat con retry automatico su errori di rete/timeout."""
response = ollama.chat(
model=model,
messages=[{"role": "user", "content": message}],
options={"num_predict": 500}
)
return response['message']['content']
Ograniczenia i uwagi dotyczące produkcji
-
Ollama domyślnie nie obsługuje wielu dzierżawców: na współdzielonym serwerze,
żądania są serializowane. Podatek
OLLAMA_NUM_PARALLEL=4dla obsługuje równoczesne żądania (wymaga więcej pamięci RAM: ~8 GB na żądanie w modelu 7B). -
Limit czasu na RPi z dużymi modelami: lama3.1:8b zajmuje 10-15 sekund
do wygenerowania pierwszej odpowiedzi na RPi. USA
num_ctx=512aby zmniejszyć czas wstępnego wypełnienia w przypadkach wrażliwych na czas. Dla TTFT <2s użyj modeli 1-3B. - Brak automatycznego skalowania: w przeciwieństwie do interfejsów API w chmurze, Ollama to się nie skaluje. W przypadku dużego ruchu użyj równoważenia obciążenia z wieloma instancjami Ollama na serwerach inny lub rozważ vLLM w przypadku wdrożeń GPU.
-
Ciągłe zużycie energii: utrzymuj Ollamę w aktywności z modelką
załadowany zużywa ~15W na RPi5, ~45W na Jetson Orin NX. USA
OLLAMA_KEEP_ALIVE=0aby pobrać model natychmiast po każdym żądaniu. - Bezpieczeństwo: Ollama domyślnie nie uwierzytelnia żądań. W produkcji zawsze umieszczaj odwrotne proxy (nginx) z przodu z uwierzytelnianiem i ograniczaniem szybkości. Nigdy nie udostępniaj portu 11434 bezpośrednio w Internecie.
Wnioski
Ollama obniżyła barierę wejścia do lokalnej sztucznej inteligencji do zera. Jednym poleceniem możesz mieć konkurencyjny LLM działający na swoim laptopie, z całkowitą i zerową prywatnością Koszty API. Trend w kierunku I Lokalny LLM i nie do zatrzymania: prognozuje Gartner że do 2027 r. SLM (modele małego języka) będą trzykrotnie przekraczać LLM w chmurze użytkowania, przy redukcji kosztów operacyjnych o 70%.
W przypadku produkcji Ollama jest doskonałym punktem wyjścia, ale wymaga pewnych przemyśleń: zarządzanie współbieżnością, monitorowanie, aktualizacja modeli, bezpieczeństwo i integracja z istniejącymi systemami. Najpotężniejszym wzorcem jest połączenie Ollama z rurociągiem RAG daj modelowi dostęp do prywatnych baz wiedzy bez wysyłania danych do chmury.
Kolejny artykuł z serii zamyka koło za pomocą i Punkty odniesienia tj Optymalizacja: jak systematycznie mierzyć wydajność wszystkich narzędzia widoczne w serii — kwantyzacja, destylacja, przycinanie, wdrażanie krawędzi — i wybierz optymalną kombinację dla swojego przypadku użycia.
Następne kroki
- Następny artykuł: Test porównawczy i optymalizacja: od 48 GB do 8 GB RTX
- Powiązany: Głębokie uczenie się na urządzeniach brzegowych
- Powiązany: Kwantyzacja: GPTQ, AWQ, GGUF
- Seria inżynierii AI: Rurociąg RAG z lokalnymi LLM
- Seria MLOps: Obsługa modeli w produkcji







