Místní Ollama a LLM: Provozování modelů na vašem vlastním hardwaru
V roce 2023 bylo místní provozování velkého jazykového modelu naprostou záležitostí
měl hluboké technické znalosti: kompilace llama.cpp, převod vah, konfigurace
Parametry GGML, správa složitých závislostí. Pak to dorazilo Ollama,
a všechno se změnilo. Jediným příkazem - ollama run llama3 — kdokoli
mohou mít konkurenční LLM spuštěný na svém notebooku během několika minut.
Trend je výbušný. Ollama dosáhla v roce 2024 více než 1 milionu stažení měsíčně, s 300% meziročním růstem. Trh si jednoznačně vybírá soukromí (data neopustí zařízení), nulové náklady API, přizpůsobení (vlastní modely, pevné systémové výzvy) e dostupnost offline. Tyto výhody jsou hnací silou migrace mnoha obchodních pracovních postupů od cloudového API až po místní nasazení.
V této příručce, od instalace po výrobu: jak nakonfigurovat Ollama, vyberte správný model, vytvářejte vlastní soubory modelů, odhalujte REST API, integrujte LangChain pro offline potrubí RAG a jemné ladění modelů GGUF pro konkrétní případy použití, na noteboocích, serverech a Raspberry Pi.
Co se naučíte
- Instalace Ollama na Windows, MacOS a Linux
- Průvodce výběrem modelu: Llama, Qwen, Phi, Gemma, Mistral, DeepSeek
- Modelfile: vytvořte vlastní asistenty s vlastními parametry
- Ollama REST API: Integrace s Pythonem, JavaScriptem a cURL
- Ollama s Pythonem prostřednictvím oficiální knihovny a kompatibility s OpenAI
- Offline potrubí RAG s LangChain a FAISS
- Nasazení na Raspberry Pi a bezhlavý server se systemd
- OpenWebUI: Zcela offline rozhraní podobné ChatGPT
- Detailní benchmarky a výběr úrovně kvantizace
- Multi-model management a optimalizace pro výrobu
Jak Ollama interně funguje
Před použitím Ollama je užitečné pochopit, co dělá pod kapotou. Ollama a zavinovačka kolem lama.cpp, C++ inferenční engine, který to umožnil spustit kvantované modely na komoditním hardwaru. Ollama dodává:
- Registr modelů: Tah/push systém podobný Docker Hub pro modely GGUF
- Server REST API: zpřístupňuje místní HTTP server na portu 11434
- Ukládání modelu do mezipaměti: udržuje modely načtené v paměti RAM mezi požadavky
- Detekce GPU: automaticky detekuje NVIDIA CUDA, AMD ROCm a Apple Metal
- Správa kontextu: spravuje kontextové okno a mezipaměť 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()
Instalace a první kroky
Ollama se instaluje jediným příkazem a nevyžaduje žádnou konfiguraci. Podpora macOS (Apple Silicon a Intel), Windows (s GPU NVIDIA nebo AMD) a 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")
Úrovně kvantizace: Který GGUF byste si měli vybrat?
Až bude hotovo ollama pull llama3.1:8b, Ollama automaticky stáhne soubor
optimální kvantování pro váš hardware. Je ale možné si vybrat výslovně
úroveň kvantizace s důležitým kompromisem mezi kvalitou, velikostí a rychlostí.
Průvodce úrovněmi kvantizace GGUF
| Tagy / Formát | Bity/hmotnost | Velikost (7B) | Ztráta zmatenosti | Doporučeno pro |
|---|---|---|---|---|
| Q2_K | 2,63 bitů | 2,7 GB | +15–20 % | Pouze pokud je RAM absolutním omezením |
| Q4_K_S | 4,37 bitů | 4,5 GB | +2–3 % | Dobrý poměr rychlost/kvalita |
| Q4_K_M | 4,58 bitů | 4,8 GB | +1–2 % | Doporučené výchozí (sladké místo) |
| Q5_K_M | 5,68 bitů | 5,7 GB | +0,5–1 % | Nejvyšší kvalita s <6GB RAM |
| Q6_K | 6,57 bitů | 6,6 GB | +0,1–0,3 % | Téměř totožné s F16, vyžaduje více RAM |
| Q8_0 | 8,5 bitů | 8,5 GB | ~0 % | Nejvyšší kvalita, vyžaduje 9+ GB RAM |
| F16 | 16 bitů | 14 GB | 0 % (základní hodnota) | Školení/doladění, ne pro dedukci |
# 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: Vytvořte vlastní asistenty
Un Modelfile a Ollamův mechanismus pro vytváření vlastních šablon. Umožňuje definovat: základní model, systémovou výzvu, parametry generování (teplota, top_p, kontextové okno) a dokonce rozšířit model o další soubory. Je to ekvivalentní do Dockerfile, ale pro jazykové modely.
# ================================================================
# 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: Integrace s Pythonem
Ollama odhaluje dvě API: vlastní nativní API a API kompatibilní s OpenAI. Kompatibilita s OpenAI vám umožňuje jednoduše nahradit OpenAI API s Ollama změna základní adresy URL — bez změny kódu aplikace.
# 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 with LangChain: RAG Pipeline Offline
Ollama se nativně integruje s LangChain, což vám umožňuje budovat RAG potrubí
(Retrieval-Augmented Generation) zcela offline. Toto a zvláště
relevantní pro podnikové aplikace, které nemohou odesílat citlivá data do cloudu.
Modelka nomic-embed-text a optimální pro místní zabudování.
# 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: Rozhraní ChatGPT pro Ollama
OpenWebUI (dříve Ollama WebUI) a nejpoužívanějším rozhraním pro Ollama, s uživatelskou zkušeností identickou s ChatGPT, ale zcela offline. Podpora chat, nahrávání dokumentů, správa konverzací, rychlé sdílení, integrovaný RAG a multi-režim pro obrázky.
# ================================================================
# 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!")
Nasazení na Raspberry Pi: Optimalizované nastavení
Raspberry Pi 5 s 8GB RAM je nejdostupnější okrajové zařízení pro místní LLM. Při správné konfiguraci dosahují modely s parametry 1,5B 4-5 tokenů/s — dostačující pro mnoho případů použití mimo reálný čas: nízkoobjemové chatboty, dávková analýza textu, automatizace se spouštěči událostí.
# ================================================================
# 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
Skutečná případová studie: Offline Business Chatbot
Skutečný případ použití: společnost, která spravuje důvěrné dokumenty (smlouvy, personální zásady, technické příručky) chce interního chatbota bez vystavování dat cloudu. S Ollama + RAG vybuduje zcela vzduchotěsný systém za méně než jeden den.
# ================================================================
# 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
Porovnání modelů pro běžné případy použití
| Use Case | Doporučený model | RAM Min | Proč |
|---|---|---|---|
| Italský chatbot | qwen2,5:7b | 8 GB | Výborná vícejazyčnost, dlouhý kontext |
| Generování kódu | qwen2.5-coder:7b | 8 GB | Vyladěný kód, více než 90 jazyků |
| Dokumenty RAG / Q&A | lama 3,1:8b | 8 GB | Vynikající sledování pokynů, 128K kontext |
| Pokročilé uvažování | deepseek-r1:8b | 8 GB | Řetězec myšlenek, matematika, logika |
| Raspberry Pi (rychle) | lama 3,2:1b | 2 GB | 5+ t/s, jednoduché úkoly |
| Raspberry Pi (kvalitní) | qwen2,5:3b | 4 GB | Optimální poměr kvalita/rychlost |
| Mac řady M (rychlý) | qwen2,5:14b | 16 GB | 22+ t/s na M2/M3, kvalita blízká GPT-4 |
| Analýza obrazu | llava:7b nebo moondream | 8 GB | Multimodální modely optimalizované pro vidění |
Nejlepší postupy pro výrobu
Použití Ollama v produkci vyžaduje určité úvahy specifické pro použití osobní. Zde jsou nejdůležitější vzory.
# ================================================================
# 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']
Omezení a úvahy pro výrobu
-
Ollama ve výchozím nastavení není pro více nájemců: na sdíleném serveru,
požadavky jsou serializovány. Daň
OLLAMA_NUM_PARALLEL=4pro zpracovávat souběžné požadavky (vyžaduje více paměti RAM: ~8 GB na požadavek u modelu 7B). -
Časový limit na RPi u velkých modelů: lama3.1:8b trvá 10-15 sekund
vygenerovat první odpověď na RPi. USA
num_ctx=512snížit čas předvyplnění v případech citlivých na čas. Pro TTFT <2s použijte modely 1-3B. - Žádné automatické automatické škálování: na rozdíl od cloudových API, Ollama neškáluje se. Pro vysoký provoz použijte vyrovnávání zátěže s více instancemi Ollama na serverech jiné, nebo zvažte vLLM pro nasazení GPU.
-
Trvalá spotřeba energie: udržujte Ollama aktivní s modelem
nabitý spotřebuje ~15W na RPi5, ~45W na Jetson Orin NX. USA
OLLAMA_KEEP_ALIVE=0stáhnout model ihned po každém požadavku. - Bezpečnost: Ollama ve výchozím nastavení neověřuje požadavky. ve výrobě, vždy dejte dopředu reverzní proxy (nginx) s ověřováním a omezením rychlosti. Nikdy nevystavujte port 11434 přímo internetu.
Závěry
Ollama snížil bariéru vstupu do místní umělé inteligence na nulu. Jediným příkazem můžete mít na svém notebooku spuštěný konkurenční LLM s úplným a nulovým soukromím Náklady na API. Trend směrem k i Místní LLM a nezastavitelný: předpovídá Gartner že do roku 2027 SLM (Small Language Models) překročí ve frekvenci 3x cloudové LLM využití, se snížením provozních nákladů o 70 %.
Pro výrobu je Ollama skvělým výchozím bodem, ale vyžaduje několik úvah: řízení souběžnosti, monitorování, aktualizace modelu, zabezpečení a integrace se stávajícími systémy. Nejvýkonnějším vzorem je kombinace Ollama s potrubím RAG pro poskytnout modelu přístup k soukromým znalostním bázím bez odesílání dat do cloudu.
Další článek v sérii uzavírá kruh s i Benchmarky e Optimalizace: jak systematicky měřit výkon všech nástroje v této sérii — kvantizace, destilace, prořezávání, nasazení hran — a vyberte si optimální kombinaci pro váš případ použití.
Další kroky
- Další článek: Benchmark a optimalizace: od 48 GB do 8 GB RTX
- Související: Hluboké učení na okrajových zařízeních
- Související: Kvantování: GPTQ, AWQ, GGUF
- AI Engineering Series: RAG Pipeline s místními LLM
- Řada MLOps: Servírování modelů ve výrobě







