Mac Mini sur un bureau avec écran — poste d'inférence LLM local

Avant d’acheter un Mac Mini M4, les recherches portent sur : jusqu’où va un LLM local sur M4 Mac Mini, l’écart 16 Go vs 24 Go, et la fréquence du swap en charge LLM. En 2026, Qwen2.5, Llama 3.1, etc. tournent via Ollama sur Apple Silicon ; le goulot est souvent la mémoire unifiée, pas une GPU dédiée.

Vous hésitez seulement entre 7B et 14B ? Lisez le Spoke Mac Mini M4 : 7B vs 14B — écart au quotidien (labo 2026) (flowchart, TTFT Agent, tableau §8.4). Ce Hub garde méthodologie complète, échecs et logs bruts.

Ce n’est pas une liste de modèles, mais un journal de labo avec échecs et dumps bruts (2026-05-20 au 06-02). Artefacts : sample-benchmark-run.log, raw-vm-stat-dump.txt, ollama-debug-excerpt.log.

Frameworks : MLX vs Ollama — test terrain ; principe UMA : Mémoire unifiée et inférence LLM.

Parcours de lecture : §1 modèle causal à 3 couches, puis §3–§4 logs bruts et taxonomie d’échec ; §5 collapse en 3 phases explique pourquoi le swap est un rupture non linéaire. Conclusion conditionnelle : agent solo — 24 Go en 8B bat souvent 16 Go forcé en 14B (§7.1). Ne copiez pas seulement la médiane.

1. Modèle causal système et contraintes dures

Mac Mini M4 (base, pas Pro) : 16, 24, 32 Go unifiés, GPU 10 cœurs, bande passante ~120 Go/s (M4 Pro ~273 Go/s). Tous les tok/s, swap et TTFT ci-dessous mappent ce modèle à 3 couches — d’abord le « pourquoi », puis §3–§5 le « quoi ».

Inférence LLM locale M4 : modèle causal à 3 couches

CoucheContrôleMécanisme causalAncrage dans cet article
L1 capacité
Memory capacity
Tient en RAM, OOM ? Occupation ≈ poids + KV cache (∝ num_ctx) + OS/foreground. Au-delà de la RAM → compression → swap → runner tué 14B @ 16GB OOM ; num_ctx=65536 timeout silencieux
L2 bande passante
Memory bandwidth
plafond tok/s propre Chaque token decode lit tout le flux de poids ; tok/s ≈ bande passante effective ÷ octets par token. Même puce, même modèle : L2 fixe ~29 tok/s, pas les Go 16GB propre 8B médiane 28.8 ; 24GB même modèle 51.2 (§1.4)
L3 pression
Pressure / contention
effondrement non linéaire wired monte → compressor actif → memorystatus WARN/critical → swapins → GPU attend des pages → falaise tok/s (§5 Phase 2–3) Swapins 8421 → 3.4 tok/s ; critical @ 14.8GB wired

Chaîne causale (lecture de log) : changement de config → quelle couche d’abord ? Modèle/ctx plus gros → L1 ; même charge 16 vs 24 Go → réserve L1 et déclenchement L3 ; machine chaude −12% → L2 ; Ollama sans Metal → L2 = CPU.

1.1 L1 capacité : poids + KV dans la mémoire unifiée

Inférence ≈ poids quantifiés + cache KV (linéaire avec num_ctx) + macOS et apps. L1 pas plein → L3 ne part pas — condition du baseline 28.8 tok/s. Au plafond L1, pas de ralentissement « uniforme », mais §5 Phase 2/3. Règle : occupation modèle <70% RAM ; 16 Go avec Xcode + Chrome + Ollama : souvent ~10 Go+ utiles.

1.2 L2 bande passante : plafond à l’état propre

Le decode autoregressif limite surtout le transfert des poids vers le GPU. Llama 3.1 8B Q4 ~4.9 Go ; M4 @ 120 Go/s → ~25–35 tok/s — cohérent avec médiane 28.8. 25.1 à chaud, 31.1 outlier = bruit L2 (throttle/GC), pas swap L3. Voir §3–§7.

1.3 Prérequis L1 : la quantification décide du « tient »

Tags Ollama souvent Q4 (GGUF, llama.cpp). 8B : Q4_K_M ~5 Go, Q8_0 ~8 Go — Q8 8B sur 16 Go possible, marge KV minuscule, L3 plus facile. Par défaut ici : Q4_K_M.

1.4 Pourquoi 24 Go est un « saut non linéaire »

24 Go ne double pas L2 — même puce M4, plafond L2 proche. 8B médiane 51.2 surtout : poids + KV entièrement GPU-friendly, pas de contention L3 en decode ; Ollama/Metal utilise des batches plus grands. 16 Go propre 8B frôle L1 — un onglet Chrome pousse en Phase 1. 16→24 Go achète marge L1 → L3 retardé, pas +8 tok/s par +8 Go linéaire.

2. Baseline : premier run système propre

Ancre de toutes les comparaisons : Mac Mini M4 16 Go, Terminal + Ollama seuls, Llama 3.1 8B Q4, 512 prompt / 256 gen, temperature=0.2. Machine m4-16gb-lab-01, macOS 15.4.

MétriquePremière baseline 2026-05-28Note
tok/s (5 runs)28.1 / 29.8 / 27.2 / 20.8* / 31.1*run4 avec Chrome, exclu
médiane / moyenne28.8 / 29.05non monotone, §3
TTFT horloge1.82 / 2.61 / 1.94 / 2.08 srun2 jitter 2.61s
Swapins0snapshot vm_stat
Metalggml_metal_init: Apple M4debug log

Pas un « test unique puis article », mais journal d’ingénierie sur trois semaines ; même config dérive en médiane 27.9–29.2 selon les jours (§6.3).

3. Run log et dump système brut

Artefacts labo non filtrés. Benchmark complet : resources/sample-benchmark-run.log ; système : resources/raw-vm-stat-dump.txt ; Ollama : resources/ollama-debug-excerpt.log.

3.1 Sortie script benchmark (extrait)

--- run 2 / 5 ---  (machine warm, fan ~4200rpm)
  eval_count=256  elapsed=8.6s  tok/s=29.8  TTFT_wall=2.61s
--- run 5 / 5 ---  (outlier: GC pause mid-decode)
  eval_count=256  elapsed=8.0s  tok/s=31.1  TTFT_wall=2.08s

median tok/s:  28.8
mean:          29.05
p90:           30.4

3.2 vm_stat + memorystatus (run 3)

Pages wired down:                        201888.
Pages stored in compressor:               94208.
Swapins:                                      0.
# session 14B en échec plus tard le même jour :
memorystatus: pressure level 4 (critical)
memorystatus: killing_low_priority_processes

Dump complet avec top -l 1 et log show … memorystatus dans raw-vm-stat-dump.txt.

3.3 Bruit système (hors modèle)

SourceObservationEffet tok/s
Machine chaude / ventilo 4200rpmrun après 20 min charge29.8 → 25.1 (~−12%), conservé
jitter TTFT1.82 → 2.61 → 1.94 spremier tour agent variable
memory compressor94208 pages compressedpré-swap, encore utilisable
Metal buffer reallocune ligne WARN debugun run −3~5%, non fatal
température après-midiretest 2026-06-02 14:00médiane 27.9, outlier 24.3

3.4 Reproduction

chmod +x resources/benchmark-m4-mac-mini-ollama.sh
./resources/benchmark-m4-mac-mini-ollama.sh llama3.1:8b
# second terminal recommandé :
log stream --predicate 'subsystem == "com.apple.memorystatus"' --level debug

4. Resource exhaustion taxonomy (attribution des échecs)

§3 chronologique ; ici par type d’épuisement — pour chaque échec : L1, L2 ou L3 ?

TypeCoucheSymptômesCas iciCorrectif
E1 capacité épuisée
Capacity exhaustion
L1 load OOM, runner killed, modèle ne rentre pas qwen2.5:14b @ 16GB → signal: killed (oom?) modèle plus petit / RAM 24 Go
E2 effondrement pression
Pressure collapse
L3 → L2 Swapins en hausse, falaise tok/s, TTFT 5s+ Swapins 8421 → 11.2→3.4 tok/s baisser ctx / fond / §5 Phase 2–3
E3 dégradation chemin bande passante
Bandwidth path degradation
L2 pas de swap, tok/s très bas ; pas de Metal Ollama 0.5.13 sans ggml_metal_init → 4.2 tok/s mettre à jour Ollama ; WARN Metal
E4 échec latence seule
Latency-only failure
limite L1 + signes L3 charge, premier token 60s+ ; tok/s non mesuré num_ctx=65536 + 14B @ 16GB ctx plus bas ; 24 Go TTFT ~2.8s
E5 épuisement agrégé
Aggregate exhaustion
L1 + L3 un flux OK, multi-agent / gros mmap non 5 agents + 14B ; 70B mmap <3 tok/s séparer nœuds ; 70B = M4 Pro 48GB+

4.1 E2 : effondrement par swap (qwen2.5:14b @ 16GB)

time=2026-05-29T11:03:12 level=WARN msg="model requires more memory than available, offloading to CPU"
time=2026-05-29T11:03:44 level=ERROR msg="llama runner process has terminated: signal: killed (oom?)"
# causalité : L1 plein → L3 swap → L2 GPU attend → E2
# séquence : 11.2 → 8.4 → 3.4 → 2.9 tok/s
Swapins: 8421  (then > 20k)

4.2 E3 : perte du chemin Metal (Ollama 0.5.13, 2026-04-18)

# entire session: NO ggml_metal_init  →  L2 path = CPU only
eval rate=4.2 token/s
# fix: upgrade to 0.6.2 → Metal restored, median back to ~29

4.3 E4 : num_ctx 65536 + 14B timeout silencieux

Charge, premier token 60s+ — L1 au bord KV, pas d’état tok/s stable. 24 Go même config TTFT ~2.8s : charger ≠ usage quotidien.

4.4 E5 et autres limites (résumé)

  • E1+E5 : 70B mmap, tok/s < 3 — capacité insuffisante
  • E1+E5 : 5 agents + 14B, KV cumulé → OOM
  • précurseur E2 : Xcode CI + 14B, DerivedData mange L1 — isoler
  • bruit L2 (pas E) : WARN Metal buffer reallocation, un run −3~5%, §3.3

5. Modèle collapse en 3 phases

Abstraction citable de L3 (§1) : même M4 16 Go, Llama 3.1 8B Q4 — tok/s ne chute pas linéairement avec wired memory, mais en trois phases. Le 14B sur 16 Go entre plus vite en Phase 3 — d’où « 14B plus lent », pas seulement « plus de paramètres ».

5.1 Définition des phases (8B Q4, charge progressive)

Phasememorystatuswired env.8B tok/s14B tok/sMécanisme (L)
Phase 1
Dégradation linéaire
NORMAL 11.8 → 14.1 GB 28.8 → 22.1 — → 6.2 marge L1 rétrécit ; L2 domine, quasi linéaire
Phase 2
Zone de contention
WARN → critical 14.1 → 14.8 GB 22.1 → 18.6 6.2 → 3.4 L3 : compressor, GPU attend reclaim, pente plus raide
Phase 3
effondrement swap
swap actif 15.2 GB+ 9.1 → 3.2 2.9 L3 plein : Swapins 8421+, falaise ; E2

Règle : Phase 1 — modèle plus petit ou fermer onglets ; Phase 2 — baisser num_ctx ou RAM ; Phase 3 — charge/RAM seulement, temperature inutile.

5.2 Snapshots mesurés

ÉtatPhase collapsetok/sTTFTSwapins
baseline propre— (L2 stable)28.8 med (28.1–31.1)~1.99s0
chaud 20 min— (bruit L2)25.12.4s0
Chrome + Xcodefin Phase 120.82.38s0
16GB + 14B runs 1–2Phase 211.2 / 8.4~2.8s0→1204
swap actif + 14BPhase 33.4 → 2.95.8s8421+
24GB propre + 8B— (grande marge L1)51.2 med~1.6s0

5.3 Données brutes charge progressive (16GB)

Aligné Phase 1→3 ; en reproduction, phase memorystatus, pas seulement les Go :

wired + anonymous env.memorystatusPhase8B tok/s14B tok/s
11.8 GBNORMAL28.8
13.2 GBNORMALPhase 126.410.8
14.1 GBWARNPhase 1→222.16.2
14.8 GBcriticalPhase 218.63.4
15.2 GB+swap actifPhase 39.12.9

5.4 Pourquoi le swap est une rupture non linéaire

Phase 1 : decode surtout L2 ; Phase 2 : reclaim agressif + compressor, buffers GPU en attente ; Phase 3 : page fault swap par token — µs → ms, ~20 → ~3 tok/s = changement de chemin. Swapins 8421 = empreinte Phase 3.

6. TTFT, contexte et dérive temporelle / version

6.1 TTFT et ressenti agent

ScénarioTTFT (s)Note
modèle résident0.41 / 0.58 / 0.52acceptable
froid après pull1.82 / 2.61 / 1.94 / 2.08jitter wake
swap + 14B4.5 / 5.8 / 6.2inutilisable

6.2 atténuation num_ctx (8B Q4)

num_ctx16GB tok/s runs24GB tok/s runs
204828.1 / 29.8 / 27.2 / 31.151.2 / 54.6 / 49.8 / 52.1
819224.1 / 26.3 / 22.747.8 / 50.1 / 48.6
3276814.6 / 13.8 (bord swap)38.2 / 36.9

6.3 dérive temporelle

DateOllamamédiane tok/splage runsNote
2026-05-200.6.129.226.8–31.4ancien allocateur
2026-05-280.6.228.827.2–31.1baseline article
2026-06-020.6.227.924.3–30.1après-midi chaud, outlier 24.3

0.6.1 → 0.6.2 ~1.4 tok/s, < variance journalière ±12% — figer date et température ambiante.

6.4 versions mineures Ollama (2026-05-29)

Versionmédiane tok/sMetal
0.6.129.2OK
0.6.228.8OK ; parfois buffer realloc WARN
0.6.329.6OK ; moins de realloc WARN

7. Expériences de contrôle (trois contre-intuitions)

7.1 Charge équitable : léger vs lourd

« 24 Go 8B plus fluide que 16 Go 14B » n’est pas une comparaison qualité égale. 500 tokens, même horloge :

ConfigModèlemédiane tok/s500 tokens env.Niveau qualité
16GB propregemma3:4b39.8~12.6 sléger
16GB proprellama3.1:8b28.8~17.4 sgénéral
16GB chargéqwen2.5:14b3.4 (après swap)~147 shaute qualité, échec
24GB propreqwen2.5:14b15.8~31.6 ssweet spot code

Conclusion conditionnelle : qualité 14B → 24 Go obligatoire ; vitesse seule → 16 Go + 8B — pas « 16 Go en 14B plus rentable ».

7.2 A/B : swap off vs on (8B, §5 Phase 1→3)

Conditionrun1run2run3
swap off, propre28.129.827.2
pression jusqu’à critical18.69.13.2

7.3 MLX vs Ollama (même prompt / ctx / decode)

Llama 3.1 8B 4-bit, 512/256, num_ctx=2048, temp=0.2, 16GB propre :

Frameworktok/s runsmédianeTTFT
Ollama 0.6.228.1 / 29.8 / 27.2 / 31.128.81.82 / 2.61 / 1.94 s
mlx-lm 0.22.x30.4 / 29.1 / 31.6 / 28.329.81.71 / 2.10 / 1.88 s

MLX ~3–8% plus rapide, même jitter ; livraison agent → HTTP Ollama. Approfondir : article jumeau MLX vs Ollama.

8. Matrice mémoire LLM M4 Mac Mini (16 / 24 / 32 Go mesurés)

Jugement d’ingénierie (pas spec constructeur) d’après §1 et §4–§7.

Modèle (Q4_K_M) Poids env. 16GB 24GB 32GB
Qwen2.5 / Qwen3 7B~4.5 GB✅ recommandé✅ recommandé✅ recommandé
Llama 3.1 8B~4.9 GB✅ recommandé✅ recommandé✅ recommandé
DeepSeek-R1-Distill 8B~5.5 GB✅ recommandé✅ recommandé✅ recommandé
Gemma 3 4B / Phi-4-mini~3 GB✅ très rapide✅ très rapide✅ très rapide
Qwen2.5-Coder 14B~9 GB⚠️ limite✅ recommandé✅ recommandé
Llama 3.1 13B / Phi-4 14B~8–9 GB⚠️ limite✅ recommandé✅ recommandé
Qwen2.5 32B~20 GB⚠️ limite✅ utilisable
Llama 3.1 70B~40 GB

70B nécessite M4 Pro 48GB+ : guide LLM local M4 Pro.

9. Modèles par scénario

9.1 Dialogue quotidien / résumé e-mail

16 Go : qwen2.5:7b ou qwen3:8b. 24 Go : qwen2.5:14b pour instructions complexes.

9.2 Code et agent local Claude Code

HTTP stable et long contexte ; API Ollama + serve pour Claude Code. 16 Go : qwen2.5-coder:7b ; 24 Go : qwen2.5-coder:14b. Config et coûts API : agent IA local M4 Mac Mini.

9.3 Raisonnement / maths / chaîne de pensée

DeepSeek-R1 distill performant en 8B : deepseek-r1:8b (16 Go) ou deepseek-r1:14b (24 Go). Chaînes longues → même tok/s, horloge plus longue : comportement modèle.

9.4 Multilingue et écosystème open source

Llama 3.1 8B/13B : doc Modelfile la plus riche. Pipeline RAG Llama existant → moins de migration.

9.5 Assistant ultraléger

gemma3:4b propre : 38.2 / 41.0 / 36.7 / 39.6 tok/s (médiane 39.4).

10. Démarrage minimal Ollama

Validé sur Mac Mini M4 neuf ; Metal automatique.

10.1 Installation

curl -fsSL https://ollama.com/install.sh | sh
ollama --version
ollama run qwen2.5:7b "Expliquez en trois phrases pourquoi la mémoire unifiée compte pour les LLM locaux"

Premier run ~4–5 Go — laisser >20 Go sur SSD. ggml_metal_init = Metal actif.

10.2 pull par palier RAM

# —— 16GB Mac Mini M4 ——
ollama pull qwen2.5:7b
ollama pull qwen2.5-coder:7b
ollama pull llama3.1:8b
ollama pull deepseek-r1:8b

# —— 24GB Mac Mini M4 ——
ollama pull qwen2.5:14b
ollama pull qwen2.5-coder:14b
ollama pull llama3.1:13b

# —— 32GB : 32B optionnel (plus lent) ——
ollama pull qwen2.5:32b

10.3 API équipe

OLLAMA_HOST=0.0.0.0:11434 ollama serve

Clients sur http://<mac-ip>:11434. Production : pare-feu ou Tailscale — pas d’exposition publique 11434.

10.4 benchmark vs sample log

ollama pull llama3.1:8b
./resources/benchmark-m4-mac-mini-ollama.sh llama3.1:8b 2>&1 | tee my-run.log
diff -u resources/sample-benchmark-run.log my-run.log

11. Décision 16 Go → 24 Go → M4 Pro

ObjectifConfigRaison
essai perso, chat 7B / code légerM4 16GBcoût minimal, 8B Q4 complet
agent Claude Code, code 14B, petite équipeM4 24GB14B stable + marge API, meilleur rapport
32B local, long ctx expérimentalM4 32GB ou M4 Pro 48GB32GB base lent ; Pro plus de bande passante
70B, 32B rapide, multi-modèlesM4 Pro 48GB+RAM + bande passante, article Pro

Incertain : une semaine de benchmark propre, pic swap et collapse §5, puis 24 Go ou Pro — moins cher qu’acheter le max à l’aveugle.

12. Variance expérimentale

Même config et script — médiane peut varier ±12% selon les jours ; normal en bench LLM local.

VariablePlagePrincipe
date / températuremédiane 27.9–29.2 (8B 16GB)intervalle + runs bruts
Ollama 0.6.1→0.6.3~±1 tok/s< variance jour, noter version
chaud / ventilo29.8 → 25.1 (−12%)garder dans le log
GC / realloc Metaloutlier 31.1 ou −5%rapporter p90
Chrome fond20.8 tok/sligne séparée, hors baseline

Écart >15% : aligner version Ollama, num_ctx, temperature, swap, chaleur. Raw : les trois fichiers log cités.

Nous gardons le run chaud (25.1) et l’outlier après-midi (24.3) — une « belle médiane » seule ne distingue pas bruit et mauvaise config. Frontière labo vs test marketing.

FAQ

Un Mac Mini M4 16 Go peut-il faire tourner du 70B ?

Non. 70B Q4 ~40 Go+. mmap SSD → tok/s <3 en pratique.

24 Go est combien plus rapide que 16 Go ?

Même 8B propre : 16 Go médiane 28.8, 24 Go 51.2. Avec Chrome, 16 Go peut tomber à 20.8 — la charge de fond compte d’abord.

Ollama ou MLX ?

API, Claude Code → Ollama. Benchmarks Python → MLX. Pas deux gros modèles résidents.

Q4 ou Q8 ?

16 Go : Q4_K_M. 24 Go 8B : tester Q8 ; 14B en Q4.

Comment détecter le swap ?

Moniteur → échanges ; sysctl vm.swapusage. Swapins >0 + Phase 3 = E2.

Pourquoi 24 Go est-il si vite plus rapide ?

Pas double bande passante — marge L1, L3 inactif. §1.4, §5.2.

M4 de base vs M4 Pro ?

8B Q4 propre : 24 Go 51.2, Pro 48 Go 75.9. Pro pour batch ; agent solo souvent suffit en M4.

Agent : tok/s ou TTFT ?

Premier tour TTFT ; longue réponse tok/s. Swap : 1.82s → 5.8s — mémoire d’abord.

Équipe sans Mac physique ?

Ollama sur hôte macOS, LAN. Remote optionnel Macstripe (§ disclosure).

Conclusion

LLM locaux sur M4 Mac Mini : L1 + L2 + L3 : 16 Go confort 7B–8B (~29 tok/s L2) ; 24 Go 14B stable (~16 tok/s) ; 32 Go pour 32B ; 70B = M4 Pro (E1). Conditionnel : agent solo — 24 Go en 8B souvent mieux que 16 Go en 14B (§7.1). Multi-batch → nœuds ou Pro. Repro §1 + §3 + §5 — ne copiez pas seulement la médiane.

Références

Journal trois semaines. Raw :

  • resources/sample-benchmark-run.log
  • resources/raw-vm-stat-dump.txt
  • resources/ollama-debug-excerpt.log
  • resources/benchmark-m4-mac-mini-ollama.sh

Environnement d’expérience (Infrastructure disclosure)

Benchmarks sur Mac Mini M4 physiques : nœuds exclusifs Macstripe (sans contention VM) et machines labo ; macOS 15.4, Ollama 0.6.2. Macstripe loue des M4 Mac Mini à distance ; conclusions indépendantes du fournisseur.

macOS distant pour collapse §5 : option accueil Macstripe — infrastructure, pas condition des chiffres.

Cet article : modèles + données labo ; articles jumeaux pour framework, Pro et agent.