04 · Running Local Models with Ollama¶
Every previous module called a hosted API. Sometimes you need the model running on hardware you control — privacy-sensitive data that can't leave your network, offline development, or just avoiding per-token cost during heavy experimentation. Ollama packages open-weight models for easy local execution, with an HTTP API shaped closely enough to what you've already used that switching is mostly a base-URL change.
Installing and pulling a model¶
# macOS / Linux
curl -fsSL https://ollama.com/install.sh | sh
# Pull a model (downloads weights, quantized by default — see module 7)
ollama pull llama3.1:8b
# Quick sanity check from the CLI
ollama run llama3.1:8b "Explain what a race condition is in one sentence."
The tag after the colon (8b) picks the parameter count / quantization
variant — larger tags need more RAM and are slower but generally more
capable; ollama list shows what you have locally.
Calling Ollama from Python¶
Ollama exposes an HTTP API on localhost:11434 with both its native
format and an OpenAI-compatible endpoint:
import requests
def generate(prompt: str, model: str = "llama3.1:8b") -> str:
resp = requests.post(
"http://localhost:11434/api/generate",
json={"model": model, "prompt": prompt, "stream": False},
)
resp.raise_for_status()
return resp.json()["response"]
print(generate("List three uses for a hash map."))
For multi-turn conversation, the /api/chat endpoint accepts the same
messages list shape used throughout this course:
def chat(messages: list[dict], model: str = "llama3.1:8b") -> str:
resp = requests.post(
"http://localhost:11434/api/chat",
json={"model": model, "messages": messages, "stream": False},
)
return resp.json()["message"]["content"]
history = [{"role": "user", "content": "What's the time complexity of binary search?"}]
reply = chat(history)
print(reply)
Streaming from Ollama¶
Setting "stream": True switches the response to newline-delimited JSON
chunks, mirroring Level 1's streaming module but over a local HTTP
connection instead of the hosted API:
def chat_stream(messages: list[dict], model: str = "llama3.1:8b"):
with requests.post(
"http://localhost:11434/api/chat",
json={"model": model, "messages": messages, "stream": True},
stream=True,
) as resp:
for line in resp.iter_lines():
if not line:
continue
chunk = requests.compat.json.loads(line)
yield chunk["message"]["content"]
if chunk.get("done"):
break
for token in chat_stream(history):
print(token, end="", flush=True)
Tool calling with local models¶
Newer Ollama-packaged models support tool calling with a schema shape compatible with what you built in Level 1 — but be aware reliability varies far more by model than with frontier hosted models, so validate tool-use accuracy against your own eval set (Level 2, module 6) before trusting it in a pipeline:
TOOLS = [{
"type": "function",
"function": {
"name": "get_weather",
"description": "Get current weather for a city.",
"parameters": {
"type": "object",
"properties": {"city": {"type": "string"}},
"required": ["city"],
},
},
}]
resp = requests.post(
"http://localhost:11434/api/chat",
json={"model": "llama3.1:8b", "messages": history, "tools": TOOLS, "stream": False},
)
message = resp.json()["message"]
if "tool_calls" in message:
for call in message["tool_calls"]:
print(call["function"]["name"], call["function"]["arguments"])
Using the OpenAI-compatible endpoint¶
If you already have code written against an OpenAI-shaped client, Ollama's compatibility layer lets you point it at localhost with minimal changes:
from openai import OpenAI
client = OpenAI(base_url="http://localhost:11434/v1", api_key="ollama") # key is unused, but required by the client
resp = client.chat.completions.create(
model="llama3.1:8b",
messages=[{"role": "user", "content": "Summarize the plot of a heist movie in two sentences."}],
)
print(resp.choices[0].message.content)
This is often the fastest way to prototype "what would this cost/behave like locally" without rewriting your application's call sites.
Hardware sizing¶
Rough rule of thumb for a quantized model (module 7 covers quantization in depth): you need roughly the model's parameter count in GB of RAM/VRAM at 4-bit quantization (an 8B model needs ~5-6GB, a 70B model needs ~40GB+), plus headroom for context and activations. Running on CPU works but is noticeably slower than GPU/unified-memory (Apple Silicon) execution — acceptable for development, often too slow for interactive production use at larger model sizes.
How It Actually Works¶
Ollama is not a different kind of model — it's a runtime that loads the same transformer architecture from module 1 (attention, feed-forward layers, embeddings) from a weights file into local memory and runs the forward pass on your own CPU/GPU instead of a remote data center's. The API shape difference from the hosted Claude API you've used throughout this course is purely a software convention chosen by each provider; the underlying computation — tokenize input, run it through the stacked transformer blocks, sample from the resulting logits — is architecturally the same process either way.
The practical differences you'll actually notice all trace back to one fact: you now own the compute. Latency depends on your hardware's raw throughput rather than a provider's fleet of large accelerators, which is why local generation is often visibly slower token-by-token than the hosted API for a comparably-sized model. Tool-calling reliability differences trace back to training, not the runtime: a model only calls tools well if it was specifically fine-tuned on tool-use examples in a format the runtime's chat template reproduces faithfully — Ollama runs whatever chat template ships with the model file, so a model's tool reliability locally is a property of that specific open-weight model's training, not of Ollama itself.
Cheat sheet¶
| Concept | Key fact |
|---|---|
ollama pull |
Downloads a model's weights, quantized by tag |
/api/generate |
Single-prompt completion |
/api/chat |
Multi-turn, same messages shape as the hosted API |
| Streaming | "stream": true → newline-delimited JSON chunks |
| Tool calling | Supported by newer models; validate reliability per model |
| OpenAI-compat endpoint | base_url="http://localhost:11434/v1" |
| Hardware rule of thumb | ~parameter count in GB of RAM at 4-bit quantization |
Exercise¶
Pull two differently-sized Ollama models (e.g. an 8B and a smaller 3B
variant), and run the same 10-question eval set (reuse Level 2 module 6's
golden-dataset pattern) against both plus the hosted Claude API. Compare
accuracy, latency, and — using time.monotonic() around each call — total
wall-clock time for the full set. Write up which model you'd choose for
an offline-only feature versus a latency-sensitive interactive one.