Het is vrij eenvoudig om lokaal en privé een LLM (Large Language Model) te draaien, het enige wat je nodig hebt is een wat krachtige CPU 8-16 cores en/of een videokaart met het liefst 8-16GB aan videogeheugen.
In dit voorbeeld gebruiken we Linux/WSL met python, zo heb je meer vrijheid dan LM studio om leuke toepassingen te maken met een LLM.
GEITje 7B ULTRA #

Introduction
GEITje is a powerful Dutch chatbot, which ultimately is Mistral-based model, further pretrained on Dutch and additionally treated with supervised-finetuning and DPO alignment.
A conversational model for Dutch, aligned through AI feedback.
GEITje is a large open Dutch language model with 7 billion parameters, based on Mistral 7B. It has been further trained on 10 billion tokens of Dutch text. This has improved its Dutch language skills and increased its knowledge of Dutch topics.
Model description #
GEITje is based on Mistral 7B. It’s a large open language model with 7 billion parameters, trained by Mistral AI. According to Mistral AI, the 7B model performs better than Llama 2 13B on all (English-language) benchmarks they tested it on. Mistral 7B has been released under the Apache 2.0 open source license.
GEITje – Trained Further on Dutch Texts
GEITje was created by further training Mistral 7B on no less than 10 billion tokens of Dutch text from the Dutch Gigacorpus and the MADLAD-400 web crawling corpus. It is a so-called full-parameter finetune: performed on all parameters. It is not a PEFT or LoRA finetune. Like Mistral, GEITje has a context length of 8,192 tokens.
GEITje 7B ULTRA safetensors: https://huggingface.co/BramVanroy/GEITje-7B-ultra
GEITje 7B ULTRA GUFF: https://huggingface.co/BramVanroy/GEITje-7B-ultra-GGUF
“Mistral-based model” betekent dat het model is gebouwd op de Mistral-architectuur, een moderne, super-efficiënte variant van een Transformer-LLM, ontwikkeld door het bedrijf Mistral AI (Frankrijk).
GEITje-7B-ultra = Mistral-7B → verder getraind (fine-tuned) op Nederlandse data.
Wat is Mistral (de basis)? #
Mistral AI heeft een eigen LLM-architectuur gebouwd, die rivaliseert met LLaMA, GPT, Falcon, etc.
Het belangrijkste open model is: Mistral-7B
- ~7 miljard parameters
- extreem efficiënt
- presteert vaak beter dan grotere modellen zoals LLaMA-13B
- open-source (Apache 2.0 licentie)
Wat maakt de Mistral-architectuur bijzonder?
Het is technisch nog steeds een Transformer-model, maar met verbeteringen:
Belangrijkste innovaties in Mistral #
1. Sliding Window Attention (SWA) #
- Normale attention kijkt naar de hele sequence → duur.
- SWA kijkt naar een slim “venster + samengevat verleden”.
- Voordeel:
- veel sneller
- veel minder geheugen
- langere context mogelijk met beperkte hardware
2. GQA (Grouped Query Attention) #
- Minder duplicatie van attention-heads.
- Maintains performance met veel minder compute.
3. Efficient multi-head attention kernels #
- Speciale optimalisaties waardoor inference sneller is, vooral op CPU en GPU.
4. Architectuur mix #
- Simpele, schone Transformer
- Maar met veel optimalisaties die je niet in GPT-3, LLaMA-1 of andere oude modellen vindt.
Waarom zijn Mistral-based modellen zo populair? #
- relatief klein maar supersterk
- performt veel beter dan vergelijkbare LLaMA-7B
- werkt goed met kwantisatie (3–8 bit)
- dun in geheugen (fijn voor laptops / desktops / Jetson-achtige setups)
- open-license
Voor veel mensen is Mistral-7B Q4_K_M GGUF sneller én slimmer dan LLaMA-13B Q4.
Voorbereiding (Linux / WSL) #
WSL #
WSL is een (ubuntu) Linux omgeving in Windows, deze kan men installeren via de APP store.
Zit je in een (verse) WSL prompt dan moet men eerst nog PIP (python package manager) en VENV (python virtual environment) installeren:
sudo apt install -y python3-pip python3-venv
VENV aanmaken #
Python werkt tegenwoordig met virtual environments om conflicten met bibliotheken tegen te gaan en uit te sluiten.
Maak een virtual environment aan met het commando:
python3 -m venv ~/venv
en activeer deze:
source ~/venv/bin/activate
llama-cpp-python #
Eenmaal binnen je VENV installeer llama-cpp-python om GUFF modellen te gebruiken.
pip3 install llama-cpp-python
Het je een nVidia videokaart? of CUDA cores beschikbaar? (bv het nVidia Jetson platform), gebruik dan:
pip3 install llama-cpp-python[cuda]
Het model downloaden #
GEITje 7B ULTRA GUFF: https://huggingface.co/BramVanroy/GEITje-7B-ultra-GGUF
Download als voorbeeld het “geitje-7b-ultra-q4_k_m.gguf” bestand en plaats deze in je WSL home folder.
ps je kan gemakkelijk via de Windows explorer naar de WSL home folder:
\\wsl.localhost\Ubuntu\home\
(en dan door naar de map van de gebruiker van het systeem)
Het model gebruiken en testen #
Ps. bekijk hier een hele tutorial met Q&A van DeepSeek: https://domoticx.net/docs/deepseek-linux-wsl-python/
Hieronder een script dat ik samen met ChatGPT heb samengesteld:
import os
# llama_perf_context_print uitzetten (moet vóór import Llama)
os.environ["LLAMA_LOG_LEVEL"] = "ERROR"
import time
from llama_cpp import Llama
# === ANSI COLORS ===
RESET = "\033[0m"
BOLD = "\033[1m"
DIM = "\033[2m"
FG_RED = "\033[31m"
FG_GREEN = "\033[32m"
FG_YELLOW = "\033[33m"
FG_BLUE = "\033[34m"
FG_MAGENTA = "\033[35m"
FG_CYAN = "\033[36m"
FG_WHITE = "\033[37m"
# Typing delay per token (0.0 = zo snel mogelijk; 0.01 geeft "type"-effect)
TYPING_DELAY = 0.01
# === Model config ===
# Gebruik hier je GEITje-GGUF bestand
MODEL_PATH = r"geitje-7b-ultra-q4_k_m.gguf" # <-- pas dit pad aan
# === Persona / mode config ===
MODES = {
"default": {
"description": "Standaardmodus – professioneel, behulpzaam, rustig, directe antwoorden.",
"system": (
"Je bent GEITje 7B Ultra, een krachtige Nederlandstalige chatbot.\n"
"Je antwoordt ALTIJD in helder, vloeiend en natuurlijk Nederlands.\n"
"\n"
"BELANGRIJKE REGELS:\n"
"- Geef ALLEEN het uiteindelijke antwoord, niet je interne redenering.\n"
"- Laat geen chain-of-thought of verborgen denkstappen zien.\n"
"- Speel geen systeem- of gebruikersrollen na.\n"
"- Geen onnodige meta-commentaar, excuses of vulling.\n"
"- Hou antwoorden feitelijk, precies en behulpzaam.\n"
"- Leg uit in duidelijke, gestructureerde stappen als dat nuttig is.\n"
"- Als de gebruiker om code vraagt, geef dan nette, uitvoerbare code.\n"
"- Bij complexe vragen: geef vooral de conclusie met een korte onderbouwing.\n"
"\n"
"Je toon: professioneel, rustig, vriendelijk en to-the-point."
),
"temperature": 0.6,
"top_p": 0.92,
"max_tokens": 2048,
},
"creative": {
"description": "Creatieve modus – beeldend, verhalend, speels, maar toch gefocust.",
"system": (
"Je bent GEITje 7B Ultra in een creatieve modus.\n"
"Je reageert in vloeiend en expressief Nederlands.\n"
"\n"
"Je creatieve stijl:\n"
"- Verbeeldingsrijk, levendig en boeiend\n"
"- Soepele, natuurlijke zinnen met eventueel humor\n"
"- Je mag metaforen, beelden en verhaalelementen gebruiken\n"
"- Je mag ideeën op een verrassende maar zinvolle manier uitbreiden\n"
"\n"
"Belangrijke regels:\n"
"- Laat geen chain-of-thought of interne redenering zien.\n"
"- Geen onnodige meta-commentaar of zelfreflectie.\n"
"- Blijf binnen de intentie van de gebruiker; ga niet onnodig off-topic.\n"
"- Als de gebruiker om een verhaal of creatieve tekst vraagt, lever iets moois.\n"
"- Als de gebruiker om ideeën vraagt, geef meerdere, originele suggesties.\n"
"- Vermijd eindeloos uitweiden; creativiteit blijft gericht en duidelijk."
),
# iets creatiever
"temperature": 0.9,
"top_p": 0.95,
"max_tokens": 768,
},
"expert": {
"description": "Technische expertmodus – precies, gestructureerd en deskundig.",
"system": (
"Je bent GEITje 7B Ultra in technische expertmodus.\n"
"Je antwoordt altijd in professioneel, nauwkeurig en helder Nederlands.\n"
"\n"
"Je rol:\n"
"- Gedraag je als een senior technisch expert / domeinspecialist.\n"
"- Geef gestructureerde, goed onderbouwde uitleg.\n"
"- Gebruik correcte terminologie en relevante concepten.\n"
"- Geef korte motivaties en samenvattingen wanneer nuttig.\n"
"\n"
"Belangrijke regels:\n"
"- Laat geen chain-of-thought, interne redenering of stap-voor-stap nadenken zien.\n"
"- Geef bondige conclusies met korte toelichting.\n"
"- Geen onnodige vulling, excuses of meta-commentaar.\n"
"- Blijf feitelijk en gebaseerd op echte kennis.\n"
"- Bij vergelijkingen: maak duidelijke, gestructureerde onderscheidingen.\n"
"- Bij berekeningen: geef het juiste resultaat zonder je tussenstappen te tonen."
),
# stabiel en zakelijk
"temperature": 0.55,
"top_p": 0.9,
"max_tokens": 640,
},
"code": {
"description": "Developer / Code-only modus – gefocust op code, minimale tekst.",
"system": (
"Je bent GEITje 7B Ultra in developer / code-only modus.\n"
"Je antwoorden zijn kort en in helder Nederlands, maar de focus ligt op code.\n"
"\n"
"Je gedrag:\n"
"- Als de gebruiker om code vraagt, geef ALLEEN een codeblok.\n"
"- Geen uitleg tenzij daar expliciet om wordt gevraagd.\n"
"- Code moet schoon, minimaal en direct uitvoerbaar zijn.\n"
"- Gebruik bij voorkeur algemeen ondersteunde libraries, tenzij anders gevraagd.\n"
"- Pas best practices toe voor leesbaarheid en onderhoudbaarheid.\n"
"\n"
"Belangrijke regels:\n"
"- Laat geen chain-of-thought of interne redenering zien.\n"
"- Geen extra commentaar, excuses of meta-zinnen.\n"
"- Herhaal de vraag niet.\n"
"- Bij het aanpassen van code: geef altijd de volledige, bijgewerkte versie.\n"
"- Bij bugs: geef de gecorrigeerde code."
),
# iets creatief maar nog vrij strak
"temperature": 0.6,
"top_p": 0.9,
"max_tokens": 512,
},
}
# Start automatisch in de "default" persona
DEFAULT_MODE = "default"
# === LLM init ===
def create_llm():
return Llama(
model_path=MODEL_PATH,
n_ctx=8192,
n_threads=12,
n_gpu_layers=0, # 0 = CPU-only; >0 = GPU-layers (GPU-build nodig)
seed=-1, # -1 = random seed per run
verbose=False, # llama_perf_context_print etc uitzetten
# Optioneel rope scaling, als je dat wilt:
# rope_scaling_type="yarn",
# rope_freq_base=1e6,
)
_llm = create_llm()
# === Prompt builder (Zephyr/GEITje chattemplate) ===
def build_chatml_prompt(messages, mode_name: str) -> str:
"""
Zet chatgeschiedenis om naar het Zephyr/GEITje-formaat.
GEITje 7B Ultra gebruikt een Zephyr-achtige template:
<|system|>
...
</s>
<|user|>
...
</s>
<|assistant|>
...
messages = [{"role": "user"|"assistant"|"system", "content": "..."}]
"""
mode = MODES.get(mode_name, MODES[DEFAULT_MODE])
system_message = mode["system"]
parts = []
# Systeemboodschap vanuit de geselecteerde modus
parts.append("<|system|>\n")
parts.append(system_message)
parts.append("</s>\n")
for msg in messages:
role = msg["role"]
content = msg["content"]
if role == "user":
parts.append("<|user|>\n")
parts.append(content)
parts.append("</s>\n")
elif role == "assistant":
parts.append("<|assistant|>\n")
parts.append(content)
parts.append("</s>\n")
elif role == "system":
# Extra systeemboodschappen uit de history (zeldzaam, maar kan)
parts.append("<|system|>\n")
parts.append(content)
parts.append("</s>\n")
else:
# Fallback: behandel onbekende rollen als user
parts.append("<|user|>\n")
parts.append(content)
parts.append("</s>\n")
# Generation prompt: start van het assistant-antwoord
parts.append("<|assistant|>\n")
return "".join(parts)
# === Helpers ===
def print_modes():
print(f"{FG_CYAN}Beschikbare modi:{RESET}")
for name, cfg in MODES.items():
marker = "*" if name == DEFAULT_MODE else " "
print(f" {marker} {FG_YELLOW}{name:8s}{RESET} - {cfg['description']}")
# === REPL ===
def start_repl():
current_mode = DEFAULT_MODE
history = []
last_stats = None # wordt gevuld na elke generatie
print(
f"{FG_MAGENTA}{BOLD}=======================================================\n"
" GEITje 7B Ultra REPL – streaming, multi-persona (Nederlands)\n"
" Commando's:\n"
" /exit – stop\n"
" /quit – stop\n"
" /reset – wis conversatiegeschiedenis\n"
" /mode – toon beschikbare modi\n"
" /mode <naam> – wissel modus (default | creative | expert | code)\n"
" /stats – toon statistieken van het laatste antwoord\n"
"=======================================================\n"
f"{RESET}"
)
print(
f"Huidige modus: {FG_YELLOW}{current_mode}{RESET} "
f"({MODES[current_mode]['description']})"
)
while True:
try:
user_input = input(f"\n{FG_GREEN}Jij{RESET}: ").strip()
except EOFError:
print(f"\n{FG_RED}EOF, afsluiten…{RESET}")
break
if not user_input:
continue
# Commando's
low = user_input.lower()
if low in ("/exit", "/quit"):
print(f"{FG_RED}Afsluiten…{RESET}")
break
if low == "/reset":
history = []
print(f"{FG_CYAN}Conversatiegeschiedenis gewist.{RESET}")
continue
if low == "/mode":
print_modes()
continue
if low.startswith("/mode "):
parts = user_input.split()
if len(parts) >= 2:
requested = parts[1].lower()
if requested in MODES:
current_mode = requested
print(
f"{FG_CYAN}Modus gewijzigd naar:{RESET} "
f"{FG_YELLOW}{current_mode}{RESET}"
)
print(f" {MODES[current_mode]['description']}")
else:
print(f"{FG_RED}Onbekende modus:{RESET} {requested}")
print_modes()
else:
print_modes()
continue
if low == "/stats":
if last_stats is None:
print(f"{FG_YELLOW}Nog geen statistieken. Stel eerst een vraag.{RESET}")
else:
print(f"\n{FG_CYAN}Statistieken laatste antwoord:{RESET}")
print(f" Modus: {last_stats['mode']}")
print(f" Prompt tokens: {last_stats['prompt_tokens']}")
print(f" Antwoord tokens: {last_stats['completion_tokens']}")
print(f" Totaal tokens: {last_stats['total_tokens']}")
print(f" Genereertijd: {last_stats['time_sec']:.3f} s")
print(f" Tokens/sec (gen): {last_stats['tokens_per_sec']:.2f}")
continue
# Normale user input → naar model
history.append({"role": "user", "content": user_input})
mode_cfg = MODES.get(current_mode, MODES[DEFAULT_MODE])
prompt = build_chatml_prompt(history, current_mode)
print(f"{FG_BLUE}{current_mode.capitalize()}{RESET}: ", end="", flush=True)
start_time = time.time()
full_answer = ""
# Streaming generatie
for chunk in _llm(
prompt,
max_tokens=mode_cfg["max_tokens"],
temperature=mode_cfg["temperature"],
top_p=mode_cfg["top_p"],
# Voor Zephyr/GEITje is </s> de belangrijkste stop-token;
# de rest zijn extra veiligheid.
stop=["</s>", "<|user|>", "<|system|>"],
stream=True,
):
token = chunk["choices"][0]["text"]
full_answer += token
print(token, end="", flush=True)
if TYPING_DELAY > 0.0:
time.sleep(TYPING_DELAY)
end_time = time.time()
print(f"\n{DIM}-----------------------------{RESET}\n")
# Schoon antwoord: knip eventueel alles na </s> eraf
clean_answer = full_answer.split("</s>")[0].strip()
# History updaten met opgeschoonde tekst
history.append({"role": "assistant", "content": clean_answer})
# Stats berekenen
elapsed = max(end_time - start_time, 1e-6)
def count_tokens(text: str) -> int:
# llama_cpp verwacht bytes in sommige versies → encode naar utf-8
return len(_llm.tokenize(text.encode("utf-8")))
prompt_tokens = count_tokens(prompt)
completion_tokens = count_tokens(clean_answer)
total_tokens = prompt_tokens + completion_tokens
tokens_per_sec = completion_tokens / elapsed
last_stats = {
"mode": current_mode,
"prompt_tokens": prompt_tokens,
"completion_tokens": completion_tokens,
"total_tokens": total_tokens,
"time_sec": elapsed,
"tokens_per_sec": tokens_per_sec,
}
if __name__ == "__main__":
start_repl()

