IntermediateOllamaLokal LLMEmbeddings

Kør en LLM lokalt med Ollama

Ingen API-nøgle, ingen data der forlader maskinen, ingen regning per token. Til gengæld skal du selv holde styr på VRAM, quantization og kontekstlængde. Her er regnestykket og koden.

1. september 202613 min læsetid

TL;DR

To kommandoer, og du har en model kørende på localhost:11434 med et HTTP-API.

terminalbash
1curl -fsSL https://ollama.com/install.sh | sh # Linux
2# macOS: brew install ollama
3
4ollama run llama3.1:8b "Forklar quantization i tre sætninger"

Prerequisites

  • • Mindst 16 GB RAM — 8 GB rækker kun til de mindste modeller
  • • Et GPU med 8 GB+ VRAM, eller en Apple Silicon-maskine med unified memory
  • • 5-50 GB fri diskplads, afhængigt af hvor mange modeller du henter
  • • Python 3.11+ hvis du vil bruge kodeeksemplerne

Hvornår lokalt giver mening

En lokal model er ikke en billigere udgave af en frontier-model. Den er et andet værktøj med andre egenskaber, og valget bør træffes på egenskaberne — ikke på prisen.

  • Data må ikke forlade huset. Journaler, kontrakter, kildekode under NDA. Ingen udgående forbindelse betyder ingen databehandleraftale at forhandle.
  • Høj volumen, lav kompleksitet. Klassificering, tagging, oversættelse af korte strenge. En 8B-model på egen GPU kører millioner af kald til strømprisen alene.
  • Embeddings. Skal du indeksere 100.000 dokumenter, er lokale embeddings hurtigere og gratis. Se RAG-guiden for hvordan de bruges.
  • Offline. Fly, laboratorier, feltarbejde, luftgapped netværk.

Omvendt: har du brug for lange kæder af ræsonnement, pålidelig tool use eller kode af produktionskvalitet, er en 8B-model ikke svaret. Forskellen er ikke marginal, den er kategorisk. Mange systemer ender derfor med begge dele — lokalt til volumen, API til det svære.

Step 1: VRAM-regnestykket

Det eneste tal der afgør om en model kan køre hos dig, er hvor meget hukommelse dens vægte fylder. Tommelfingerreglen for quantized modeller i Q4-format:

VRAM ≈ (antal parametre i milliarder × 0,55 GB) + KV-cache

8B  model i Q4  ≈  4,7 GB  + kontekst
14B model i Q4  ≈  8,2 GB  + kontekst
70B model i Q4  ≈   40 GB  + kontekst

KV-cachen vokser med kontekstlængden og er den post folk glemmer. Et 32k-kontekstvindue kan lægge flere gigabyte oveni. Passer det hele ikke i VRAM, flytter Ollama en del af modellen til system-RAM, og gennemløbet falder typisk med en faktor 5-20. Det er ikke en fejl du får en fejlbesked for — det bliver bare pludselig ubrugeligt langsomt.

HukommelseRealistisk modelstørrelseEgner sig til
8 GB3B, embedding-modellerKlassificering, embeddings
16 GB8B i Q4Chat, opsummering, ekstraktion
24-32 GB14B i Q4, 8B i Q8Kodeassistance, RAG
64 GB+70B i Q4, MoE-modellerAlt hvad lokalt kan bruges til

Om quantization: Q4_K_M er standardvalget. Tabet i kvalitet er lille nok til at være svært at måle i praksis, og hukommelsesbesparelsen i forhold til fuld præcision er omtrent 4x. Q8 giver marginalt bedre svar til dobbelt pris. Under Q4 begynder modellen at lave faktuelle fejl den ikke lavede før.

Step 2: Vælg en model

terminalbash
1# Generalist — god balance mellem størrelse og evne
2ollama pull llama3.1:8b
3
4# Kode
5ollama pull qwen2.5-coder:7b
6
7# Åbne vægte fra OpenAI (kræver rigeligt med hukommelse)
8ollama pull gpt-oss:20b
9
10# Embeddings — små, hurtige, ingen chat
11ollama pull nomic-embed-text
12
13ollama list # hvad har du hentet
14ollama ps # hvad kører lige nu, og hvor meget fylder det

ollama ps er den nyttigste kommando i hele værktøjet. Den viser om modellen ligger i GPU eller er faldet ned i CPU — og dermed om du er i den hurtige eller den langsomme verden.

Step 3: API'et

Ollama lytter på localhost:11434. Alt kan gøres med rå HTTP, hvilket gør det trivielt at kalde fra et hvilket som helst sprog.

terminalbash
1curl http://localhost:11434/api/chat -d '{
2 "model": "llama3.1:8b",
3 "stream": false,
4 "messages": [
5 {"role": "user", "content": "Hvad er forskellen på Q4 og Q8 quantization?"}
6 ]
7}'

Fra Python, med den officielle klient:

chat.pypython
1# pip install ollama
2from ollama import Client
3
4client = Client(host="http://localhost:11434")
5
6response = client.chat(
7 model="llama3.1:8b",
8 messages=[
9 {"role": "system", "content": "Du svarer kort og på dansk."},
10 {"role": "user", "content": "Hvorfor er KV-cachen vigtig?"},
11 ],
12 options={
13 "temperature": 0.3,
14 "num_ctx": 8192, # kontekstvindue — koster VRAM
15 "num_predict": 512, # maks. output-tokens
16 },
17)
18
19print(response["message"]["content"])
20
21# Streaming: samme kald med stream=True
22for chunk in client.chat(model="llama3.1:8b", messages=[...], stream=True):
23 print(chunk["message"]["content"], end="", flush=True)

num_ctx er den option der oftest overrasker. Ollama sætter et konservativt standard-kontekstvindue, og hæver du det ikke, bliver lange prompts stiltiende afkortet. Er du i tvivl om hvorfor modellen "glemmer" starten af et dokument, er det næsten altid her fejlen ligger.

OpenAI-kompatibelt endpoint

Ollama eksponerer også et OpenAI-kompatibelt API. Det betyder at eksisterende kode ofte kan pege på localhost uden andre ændringer end base-URL'en — praktisk til at afprøve om en lokal model kan klare en opgave, før du bygger noget om.

openai_compat.pypython
1# pip install openai
2from openai import OpenAI
3
4client = OpenAI(
5 base_url="http://localhost:11434/v1",
6 api_key="ollama", # ignoreres, men klienten kræver en værdi
7)
8
9response = client.chat.completions.create(
10 model="llama3.1:8b",
11 messages=[{"role": "user", "content": "Skriv en regex til danske postnumre"}],
12)
13
14print(response.choices[0].message.content)

Kompatibiliteten dækker chat, completions og embeddings. Den dækker ikke leverandørspecifikke funktioner — så et tool-use-flow bygget mod en bestemt udbyders API kan ikke uden videre flyttes hertil.

Step 4: Structured output

Skal svaret bruges af kode og ikke af et menneske, så bed om et skema frem for at bede modellen "svare med JSON". Ollama tvinger outputtet til at følge skemaet, og du slipper for parse-fejl og retry-løkker.

structured.pypython
1from ollama import Client
2
3client = Client()
4
5schema = {
6 "type": "object",
7 "properties": {
8 "sentiment": {"type": "string", "enum": ["positiv", "neutral", "negativ"]},
9 "emner": {"type": "array", "items": {"type": "string"}},
10 "haster": {"type": "boolean"},
11 },
12 "required": ["sentiment", "emner", "haster"],
13}
14
15response = client.chat(
16 model="llama3.1:8b",
17 messages=[{
18 "role": "user",
19 "content": "Klassificér: 'Serveren har været nede siden i morges. "
20 "Vores kunder kan ikke logge ind.'",
21 }],
22 format=schema,
23 options={"temperature": 0},
24)
25
26import json
27print(json.loads(response["message"]["content"]))
28# {'sentiment': 'negativ', 'emner': ['nedetid', 'login'], 'haster': True}

Sæt temperature til 0 ved klassificering. Du vil have det samme svar hver gang på det samme input, ikke variation.

Step 5: Embeddings lokalt

Embedding-modeller er små — nomic-embed-text fylder omkring 270 MB — og de er det bedste argument for at køre lokalt overhovedet. En indeksering af 100.000 chunks er gratis og tager minutter i stedet for timer.

embed.pypython
1from ollama import Client
2
3client = Client()
4
5def embed(texts: list[str]) -> list[list[float]]:
6 """Embed en liste af tekster med en lokal model."""
7 response = client.embed(model="nomic-embed-text", input=texts)
8 return response["embeddings"]
9
10vektorer = embed([
11 "Python er et programmeringssprog",
12 "JavaScript kører i browseren",
13])
14
15print(f"Dimension: {len(vektorer[0])}") # 768

Vektorerne går derefter i en vector database. Valget mellem ChromaDB, Weaviate og Pinecone er gennemgået i sammenligningen, og hele pipelinen fra dokument til svar står i RAG-guiden.

Én regel er ufravigelig: den model der har embeddet dine dokumenter, skal også embedde forespørgslerne. Skifter du embedding-model, er hele indekset ugyldigt og skal bygges forfra. Vektorerne fra to forskellige modeller ligger i hver deres rum, og afstanden mellem dem betyder ingenting.

Step 6: Egne modelvarianter

En Modelfile pakker systemprompt og parametre sammen med en eksisterende model, så konfigurationen ikke skal gentages i hvert kald:

Modelfiledockerfile
1FROM llama3.1:8b
2
3PARAMETER temperature 0.2
4PARAMETER num_ctx 16384
5
6SYSTEM """
7Du er en dansk teknisk assistent for udviklere.
8Svar kort. Vis kode frem for at forklare kode.
9Er du usikker, så sig det — gæt ikke på API-signaturer.
10"""
terminalbash
1ollama create dansk-dev -f Modelfile
2ollama run dansk-dev "Hvordan læser jeg en JSON-fil i Python?"

Production-noter

  • Hold modellen varm. Ollama frigiver hukommelsen efter få minutters inaktivitet, og næste kald betaler indlæsningstiden igen. Sæt OLLAMA_KEEP_ALIVE=-1 på en server der skal svare hurtigt.
  • Parallelle kald koster hukommelse. OLLAMA_NUM_PARALLEL styrer samtidighed, men hver samtidig session har sin egen KV-cache. Fire parallelle sessioner med langt kontekstvindue kan sagtens fylde mere end selve modellen.
  • Bind ikke til 0.0.0.0 uden videre. Ollama har ingen autentificering. Skal den nås udefra, skal der en proxy med adgangskontrol foran.
  • Lås modelversionen. Skriv llama3.1:8b, ikke llama3.1. Et tag der flytter sig under dig, ændrer opførslen uden at der er ændret en linje kode.

Common pitfalls

  • Modellen falder ned i CPU. Tjek ollama ps. Står der en CPU-andel, har du valgt en for stor model eller et for stort kontekstvindue.
  • Standardkonteksten er lille. Sæt num_ctx eksplicit, ellers bliver lange dokumenter afkortet i stilhed.
  • Forventninger fra API-modeller. En 8B-model er ikke en lille frontier-model. Den skal have snævrere opgaver, tydeligere prompts og gerne et skema at svare i.
  • Skiftende embedding-model. Indekset skal bygges forfra. Der kommer ingen fejl — resultaterne bliver bare tilfældige.
  • Temperature på klassificering. Standardværdien er tunet til samtale. Sæt den til 0 når du vil have det samme svar hver gang.
  • Ingen timeout. Et kald der lander i CPU kan køre i flere minutter. Sæt en timeout i klienten frem for at lade en webserver hænge.