# Eigene Messung: Gemma 4 bei Ollama, MLX-Variante und GGUF-Variante

Diese Ablage enthält die Rohdaten und die Auswertung einer eigenen Messung von ki.herwig.de.
Der Beitrag „Gleicher Name, neuer Digest: Wie Ollama ein Modell ablegt“ stützt sich auf sie.

## Rahmen

- Datum: 09.11.2026, zwischen 17:55 und 18:32 Uhr
- Gerät: MacBook Pro mit Apple M4 Max und 64 GB Arbeitsspeicher
- Programm: Ollama 0.40.15
- Modelle: `gemma4:26b` und `gemma4:31b`, je in der MLX-Variante und in der GGUF-Variante
- Wahl der Variante: Feld `runner` der Programmschnittstelle, Werte `mlx` und `llamacpp`
- Einstellungen: Temperatur 0, `seed` 1, `num_ctx` 8192, `think` false, höchstens 400 Antworttoken
- Messgröße: die Zeitfelder, die Ollama je Aufruf zurückgibt (`eval_count`, `eval_duration`, `prompt_eval_count`, `prompt_eval_duration`)
- Neben der Messung liefen andere Programme. Die Systemlast (Load Average) steht je Lauf in den JSON-Dateien.

## Dateien

| Datei | Inhalt |
| --- | --- |
| `messen.py`, `m26.json`, `m31.json` | Vorlauf: 3 Aufgaben, 3 Runden, Modell geladen. Im Beitrag nicht verwendet. |
| `messen2.py`, `n26.json`, `n31.json` | Messung 2: Laden nach dem Entladen, 3 Aufgaben zum Antworten, 1 Aufgabe zum Einlesen, 4 Runden, Varianten im Wechsel |
| `messen3.py`, `s26.json`, `s31.json` | Messung 3: Variante mal Entwurfsmodell, 3 Aufgaben, 2 Runden |
| `pull-26b.log`, `pull-31b.log` | Herunterladen der GGUF-Variante mit `ollama pull … --runner llamacpp` am selben Tag |
| `gguf_kopf.py` | Hilfsskript zum Auslesen eines GGUF-Kopfs nach der Spezifikation |
| `auswertung.txt` | Ausgabe der ersten Auswertung mit einer Nachkommastelle |

Die drei Aufgaben zum Antworten: eine Erklärung („Erkläre in etwa 200 Wörtern, warum der Himmel blau ist.“), eine Python-Funktion mit Tests und ein kurzer Brief.
Die Aufgabe zum Einlesen ist ein Absatz über Speicherbandbreite, vierzigmal wiederholt, mit einer eindeutigen Kennung davor.
Damit hilft kein Zwischenspeicher für Eingaben.

Das Entwurfsmodell schaltet Messung 3 so ab: bei MLX mit `logprobs: true`, bei GGUF mit der Option `draft_num_predict: 0`.

## Anlass: Funktionsprobe des Modell-Wächters

Der Modell-Wächter prüft jeden Morgen die lokal genutzten Modelle mit einer kurzen Aufgabe und misst die Zeit Ende zu Ende.
Am 28.10.2026 um 15:00 Uhr schrieb er für die neuen Digests diese zwei Zeilen in seinen Zustand, je eine einzige Probe:

```
gemma4:26b@ed3c4359df98  1/1 Faelle, Median 11.0 s (bisher 1/1, 17.9 s)
gemma4:31b@70d0e663aeb9  1/1 Faelle, Median 10.6 s (bisher 1/1, 24.1 s)
```

Die Zeiten enthalten Laden, Einlesen, Antworten und den Netzweg. Sie sind keine Messung des Rechentempos.

## Messung 2: Antworten und Einlesen

Mediane, Spanne von Tiefst- bis Höchstwert, Token je Sekunde.

| Modell | Variante | Aufgabe | n | Median | Spanne |
| --- | --- | --- | ---: | ---: | --- |
| 26B | MLX | Antworten | 12 | 115,8 | 103,6 bis 140,2 |
| 26B | GGUF | Antworten | 12 | 90,6 | 62,8 bis 98,6 |
| 31B | MLX | Antworten | 12 | 30,6 | 26,9 bis 44,5 |
| 31B | GGUF | Antworten | 12 | 25,3 | 21,7 bis 28,4 |
| 26B | MLX | Einlesen von 2612 Token | 4 | 1598 | 1530 bis 1605 |
| 26B | GGUF | Einlesen von 2614 Token | 4 | 1472 | 1096 bis 1490 |
| 31B | MLX | Einlesen von 2612 Token | 4 | 158 | 151 bis 178 |
| 31B | GGUF | Einlesen von 2614 Token | 4 | 152 | 146 bis 161 |

Die beiden Varianten zerlegen dieselbe Eingabe in 2612 und 2614 Token.

Daraus gerechnet, aus den Medianen ohne Rundung:

- Antworten 26B: MLX ist um 28 Prozent schneller (Faktor 1,28).
- Antworten 31B: MLX ist um 21 Prozent schneller (Faktor 1,21).
- Einlesen: MLX ist um 9 Prozent (26B) und 4 Prozent (31B) schneller.
- Höchstwert minus Tiefstwert beim Antworten, geteilt durch den Median: 26B MLX 32 Prozent, 26B GGUF 39 Prozent, 31B MLX 57 Prozent, 31B GGUF 27 Prozent.
- Systemlast in Messung 2: 26B 5,9 bis 10,3, 31B 4,4 bis 10,1.

## Messung 2: Laden nach dem Entladen

Vor jedem Laden entlädt das Skript alle Modelle und wartet 3 Sekunden.
Gemessen ist die Zeit bis zur Antwort auf einen Aufruf ohne Eingabe.
Bei der GGUF-Variante braucht der erste echte Aufruf danach noch einmal Ladezeit (`load_duration`), bei MLX nicht.

| Modell | Variante | n | Laden | dazu beim ersten Aufruf |
| --- | --- | ---: | --- | --- |
| 26B | MLX | 4 | 2,0 bis 6,3 s | 0,0 s |
| 26B | GGUF | 4 | 2,4 bis 12,2 s | 2,2 s |
| 31B | MLX | 4 | 4,0 bis 7,5 s | 0,0 s |
| 31B | GGUF | 4 | 9,4 bis 11,0 s | 12,0 bis 13,5 s |

Je Lauf beim 31B, in Sekunden:

| Runde | MLX Laden | GGUF Laden | GGUF erster Aufruf |
| ---: | ---: | ---: | ---: |
| 0 | 5,33 | 10,95 | 12,36 |
| 1 | 7,53 | 9,43 | 12,01 |
| 2 | 4,03 | 10,74 | 13,55 |
| 3 | 7,33 | 9,46 | 12,37 |

Aus den Spannen der Tabelle oben, ohne Rundung: Beim 31B dauert das Laden der GGUF-Variante 1,9 bis 6,9 Sekunden länger als das der MLX-Variante.
Mit dem ersten Aufruf sind es 13,9 bis 20,5 Sekunden.

## Messung 3: Variante mal Entwurfsmodell

Antworten, Mediane, Token je Sekunde.

| Modell | Variante | Entwurfsmodell | n | Median | Spanne |
| --- | --- | --- | ---: | ---: | --- |
| 26B | MLX | an | 6 | 102,8 | 87,9 bis 132,0 |
| 26B | MLX | aus | 6 | 98,5 | 93,1 bis 101,5 |
| 26B | GGUF | an | 6 | 81,7 | 67,2 bis 98,7 |
| 26B | GGUF | aus | 6 | 41,4 | 41,0 bis 45,3 |
| 31B | MLX | an | 6 | 31,1 | 22,9 bis 36,7 |
| 31B | MLX | aus | 6 | 18,7 | 12,4 bis 21,8 |
| 31B | GGUF | an | 6 | 23,7 | 20,0 bis 27,6 |
| 31B | GGUF | aus | 6 | 12,8 | 11,5 bis 15,2 |

Daraus gerechnet, aus den Medianen ohne Rundung:

- Faktor durch das Entwurfsmodell: 26B MLX 1,04, 26B GGUF 1,97, 31B MLX 1,67, 31B GGUF 1,86.
- Ohne Entwurfsmodell rechnet MLX das 26B 2,4-mal so schnell wie die GGUF-Variante, das 31B 1,46-mal.
- Systemlast in Messung 3: 26B 4,8 bis 15,6, 31B 15,5 bis 42,5. Die Läufe des 31B liefen also unter deutlich höherer Last als alle anderen, in der zweiten Runde stieg sie bis 42,5.

## Obergrenze beim 31B

Speicherbandbreite des M4 Max laut Apple: 546 GB/s.
Bytes je Token beim dichten 31B: das ganze Sprachmodell ohne Bildteil und Entwurfsmodell.

| Variante | Bytes je Token | Obergrenze | ohne Entwurf gemessen | Anteil |
| --- | ---: | ---: | ---: | ---: |
| MLX | 17,27 GB | 31,6 Token je Sekunde | 18,7 | 59 Prozent |
| GGUF | 18,67 GB | 29,2 Token je Sekunde | 12,8 | 44 Prozent |

Die 17,27 GB sind die Summe der NVFP4-Tensoren der MLX-Variante, die 18,67 GB die Modelldatei der GGUF-Variante.
Mit Entwurfsmodell erreicht MLX 31,1 Token je Sekunde, also 98 Prozent dieser Obergrenze.

## Bereinigung

- In den Modellantworten auf die Aufgabe mit der Python-Funktion zitiert das 31B mehrfach einen bekannten deutschen Palindromsatz, der ein rassistisches Schimpfwort enthält. Das Wort ist in den JSON-Dateien durch `[Wort entfernt]` ersetzt: 4-mal in `m26.json`, 18-mal in `m31.json`, 8-mal in `n31.json`, 9-mal in `s31.json`. Sonst sind die JSON-Dateien unverändert, alle Zahlen stammen unverändert aus dem Lauf.
- In den beiden Pull-Logs sind die Steuerzeichen des Terminals entfernt. Von jeder Fortschrittszeile steht nur der Endstand da.
- Die Skripte, `auswertung.txt` und die Pull-Logs enthalten keine Pfade mit Nutzernamen und keine Hostnamen. Die Skripte sprechen Ollama unter `127.0.0.1:11434` an.

## Grenzen

Die Messung lief an einem Tag, auf einem Gerät, mit wenigen Läufen je Zelle und neben anderer Last.
Ob eine Variante besser antwortet als die andere, hat sie nicht untersucht.
