Local Ollama と LLM: 独自のハードウェアでモデルを実行する
2023 年、大規模言語モデルをローカルで実行することは誰にでもできることだった
彼は、llama.cpp のコンパイル、重みの変換、設定などの深い技術的専門知識を持っていました。
GGML パラメータ、複雑な依存関係を管理します。それからそれは到着しました オラマ、
そしてすべてが変わりました。単一のコマンドで — ollama run llama3 — 誰でも
数分でラップトップ上で競争力のある LLM を実行できるようになります。
この傾向は爆発的です。 Ollama は 2024 年に月間ダウンロード数 100 万を超えました。 前年比 300% の成長を遂げています。市場が明らかに選択しているのは、 プライバシー (データはデバイスから出ません)、 コストゼロ APIの、 カスタマイズ (カスタム モデル、固定システム プロンプト) e 可用性 オフライン。これらの利点が移行を促進しています クラウド API からローカル展開までの多くのビジネス ワークフローを実現します。
このガイドでは、インストールから運用まで: Ollama の構成方法、選択方法について説明します。 適切なモデル、カスタム Modelfile の作成、REST API の公開、LangChain の統合 オフライン RAG パイプラインの場合、および特定のユースケースに合わせて GGUF モデルを微調整する場合、 ラップトップ、サーバー、Raspberry Pi 上で。
何を学ぶか
- Windows、macOS、Linux への Ollama のインストール
- モデル選択ガイド: Llama、Qwen、Phi、Gemma、Mistral、DeepSeek
- Modelfile: カスタム パラメータを使用してカスタム アシスタントを作成する
- Ollama REST API: Python、JavaScript、cURL との統合
- 公式ライブラリと OpenAI 互換性を介した Python を使用した Ollama
- LangChain と FAISS を使用したオフライン RAG パイプライン
- systemd を使用した Raspberry Pi およびヘッドレス サーバーへの展開
- OpenWebUI: 完全にオフラインの ChatGPT のようなインターフェイス
- 詳細なベンチマークと量子化レベルの選択
- 生産のためのマルチモデルの管理と最適化
Ollama の内部での仕組み
Ollama を使用する前に、Ollama が内部で何をしているのかを理解しておくと役立ちます。オラマとラッパー 周り ラマ.cpp、それを可能にした C++ 推論エンジン 市販のハードウェアで量子化モデルを実行します。オラマは次のように付け加えます。
- モデルレジストリ: GGUF モデル用の Docker Hub のようなプル/プッシュ システム
- REST APIサーバー: ポート 11434 でローカル HTTP サーバーを公開します
- モデルのキャッシュ: リクエスト間でモデルを RAM にロードしたままにします
- GPUの検出: NVIDIA CUDA、AMD ROCm、Apple Metal を自動的に検出します
- コンテキスト管理: コンテキストウィンドウと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()
インストールと最初のステップ
Ollama は 1 つのコマンドでインストールされ、構成は必要ありません。サポート macOS (Apple Silicon および Intel)、Windows (NVIDIA または AMD GPU 搭載)、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")
量子化レベル: どの GGUF を選択する必要がありますか?
終わったら ollama pull llama3.1:8b, Ollama は自動的にダウンロードします。
ハードウェアに最適な量子化。ただし、明示的に選択することも可能です
重要な品質/サイズ/速度のトレードオフを伴う量子化レベル。
GGUF 量子化レベルのガイド
| タグ/フォーマット | ビット/重量 | サイズ(7B) | 複雑性の損失 | こんな方におすすめ |
|---|---|---|---|---|
| Q2_K | 2.63ビット | 2.7GB | +15-20% | RAM が絶対的な制約である場合のみ |
| Q4_K_S | 4.37ビット | 4.5GB | +2-3% | 速度と品質のバランスが良い |
| Q4_K_M | 4.58ビット | 4.8GB | +1-2% | 推奨デフォルト(スイートスポット) |
| Q5_K_M | 5.68ビット | 5.7GB | +0.5-1% | 6GB 未満の RAM で最高品質 |
| Q6_K | 6.57ビット | 6.6GB | +0.1~0.3% | F16 とほぼ同じですが、より多くの RAM が必要です |
| Q8_0 | 8.5ビット | 8.5GB | ~0% | 最高品質、9 GB 以上の RAM が必要 |
| F16 | 16ビット | 14GB | 0% (ベースライン) | 推論のためではなく、トレーニング/微調整 |
# 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']}")
モデルファイル: カスタム アシスタントの作成
Un モデルファイル カスタム テンプレートを作成するための Ollama のメカニズム。 これにより、基本モデル、システム プロンプト、生成パラメータ (温度、 top_p、コンテキスト ウィンドウ)、さらに追加ファイルを使用してモデルを拡張することもできます。同等だよ Dockerfile に変換されますが、言語モデル用です。
# ================================================================
# 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: Python との統合
Ollama は、独自のネイティブ API と OpenAI 互換 API の 2 つの API を公開しています。 OpenAI との互換性により、OpenAI API を Ollama に簡単に置き換えることができます アプリケーションコードを変更せずに、ベース URL を変更します。
# 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 と LangChain: オフラインの RAG パイプライン
Ollama は LangChain とネイティブに統合し、RAG パイプラインを構築できるようにします。
(検索拡張生成) は完全にオフラインです。これと特に
機密データをクラウドに送信できないエンタープライズ アプリケーションに関連します。
モデル nomic-embed-text ローカル埋め込みに最適です。
# 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: Ollama 用 ChatGPT インターフェイス
OpenWebUI (以前の Ollama WebUI) および最もよく使用されるインターフェイス Ollama は、ChatGPT と同じユーザー エクスペリエンスを備えていますが、完全にオフラインです。サポート チャット、ドキュメントのアップロード、会話管理、プロンプト共有、統合 RAG 画像のマルチモード。
# ================================================================
# 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!")
Raspberry Pi への展開: 最適化されたセットアップ
8GB RAM を搭載した Raspberry Pi 5 は、ローカル LLM にとって最もアクセスしやすいエッジ デバイスです。 適切な構成を使用すると、1.5B パラメーターを持つモデルは 4 ~ 5 トークン/秒に達します。これは十分な速度です。 多くの非リアルタイムのユースケース: 少量のチャットボット、バッチテキスト分析、 イベントトリガーによる自動化。
# ================================================================
# 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
実際のケーススタディ: オフライン ビジネス チャットボット
実際の使用例: 機密文書 (契約書、人事ポリシー、 技術マニュアルなど)は、データをクラウドに公開せずに内部チャットボットを望んでいます。オラマと+ RAG は、完全にエアギャップのあるシステムを 1 日以内に構築します。
# ================================================================
# 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
一般的なユースケースのモデルの比較
| 使用事例 | 推奨モデル | 最小RAM | なぜ |
|---|---|---|---|
| イタリアのチャットボット | qwen2.5:7b | 8GB | 優れた多言語、長い文脈 |
| コード生成 | qwen2.5-coder:7b | 8GB | 細かく調整されたコード、90 以上の言語 |
| RAG / Q&A ドキュメント | ラマ3.1:8b | 8GB | 優れた命令フォロー、128K コンテキスト |
| 高度な推論 | ディープシーク-r1:8b | 8GB | 思考の連鎖、数学、論理 |
| ラズベリーパイ (高速) | ラマ3.2:1b | 2GB | 5 t/s 以上、単純なタスク |
| Raspberry Pi (品質) | qwen2.5:3b | 4ギガバイト | 最適な品質と速度のバランス |
| Mac Mシリーズ(高速) | qwen2.5:14b | 16ギガバイト | M2/M3 で 22+ t/s、GPT-4 に近い品質 |
| 画像解析 | ラヴァ:7b またはムーンドリーム | 8GB | ビジョンに最適化されたマルチモーダル モデル |
本番環境のベストプラクティス
Ollama を運用環境で使用するには、用途に応じた考慮事項がいくつか必要です 個人的な。ここでは最も重要なパターンを紹介します。
# ================================================================
# 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']
本番環境の制限と考慮事項
-
Ollama はデフォルトではマルチテナントではありません。 共有サーバーでは、
リクエストはシリアル化されます。税
OLLAMA_NUM_PARALLEL=4のために 同時リクエストを処理します (より多くの RAM が必要です: 7B モデルではリクエストあたり最大 8 GB)。 -
大規模モデルの RPi でのタイムアウト: llama3.1:8b には 10 ~ 15 秒かかります
RPi で最初の応答を生成します。アメリカ合衆国
num_ctx=512を減らすために 時間に敏感な場合のプレフィル時間。 TTFT <2s の場合は、1 ~ 3B モデルを使用します。 - 自動自動スケーリングなし: クラウド API とは異なり、Ollama それはスケールしません。トラフィックが多い場合は、サーバー上の複数の Ollama インスタンスによる負荷分散を使用します。 異なる場合は、GPU の導入に vLLM を検討してください。
-
連続消費電力: オラマをモデルでアクティブに保つ
ロードされた場合、RPi5 では最大 15 W、Jetson Orin NX では最大 45 W を消費します。アメリカ合衆国
OLLAMA_KEEP_ALIVE=0各リクエストの直後にモデルをダウンロードします。 - 安全性: Ollama はデフォルトでリクエストを認証しません。生産では、 認証とレート制限を備えたリバース プロキシ (nginx) を常に前面に配置します。 ポート 11434 をインターネットに直接公開しないでください。
結論
Ollama は、ローカル AI への参入障壁をゼロに下げました。単一のコマンドで 完全かつプライバシーをゼロにした状態で、ラップトップ上で競争力のある LLM を実行できます。 API のコスト。私への傾向 ローカルLLM そして止められない:ガートナーは予測 2027 年までに、SLM (Small Language Model) はクラウド LLM の頻度を 3 倍上回ると予想されています。 使用量が削減され、運用コストが 70% 削減されます。
本番環境では、Ollama は優れた出発点ですが、いくつかの考慮事項が必要です。 同時実行管理、モニタリング、モデル更新、セキュリティと統合 既存のシステムと。最も強力なパターンは、Ollama と RAG パイプラインを組み合わせて、 データをクラウドに送信せずに、モデルにプライベート ナレッジ ベースへのアクセスを許可します。
シリーズの次の記事では、i で円を閉じます。 ベンチマーク e 最適化: すべてのパフォーマンスを体系的に測定する方法 シリーズに登場するツール — 量子化、蒸留、枝刈り、エッジ展開 — ユースケースに最適な組み合わせを選択してください。
次のステップ
- 次の記事: ベンチマークと最適化: 48GB から 8GB RTX まで
- 関連している: エッジデバイス上のディープラーニング
- 関連している: 量子化: GPTQ、AWQ、GGUF
- AIエンジニアリングシリーズ: ローカル LLM を使用した RAG パイプライン
- MLOps シリーズ: 本番環境でモデルを提供する







