{"id":1240,"date":"2026-10-27T14:59:58","date_gmt":"2026-10-27T13:59:58","guid":{"rendered":"https:\/\/remivernier.com\/?p=1240"},"modified":"2026-10-27T21:54:40","modified_gmt":"2026-10-27T20:54:40","slug":"mlx-gguf-lm-studio-mac-mini-m4-methode-comparaison","status":"publish","type":"post","link":"https:\/\/remivernier.com\/index.php\/2026\/10\/27\/mlx-gguf-lm-studio-mac-mini-m4-methode-comparaison\/","title":{"rendered":"MLX ou GGUF dans LM Studio sur Mac mini M4: une m\u00e9thode de comparaison reproductible"},"content":{"rendered":"<p>Je ne vais pas vous dire dans cet article lequel de MLX ou de GGUF est le plus rapide sur un Mac. Des tableaux comparatifs, il en circule d\u00e9j\u00e0 beaucoup. La plupart comparent un mod\u00e8le quantifi\u00e9 en 4 bits d&rsquo;un c\u00f4t\u00e9 avec un autre en 5 bits de l&rsquo;autre, avec des contextes diff\u00e9rents et un seul essai. Leurs chiffres ne disent pas grand chose de ce qui se passera sur votre machine, avec votre mod\u00e8le et vos prompts.<\/p>\n<p>Dans LM Studio, la question revient \u00e0 chaque t\u00e9l\u00e9chargement. L&rsquo;onglet de recherche propose souvent le m\u00eame mod\u00e8le dans les deux formats, et le choix se fait au hasard ou par habitude. Nous allons voir comment comparer le m\u00eame mod\u00e8le dans les deux formats sur votre propre Mac, avec trois scripts que vous pourrez relancer \u00e0 chaque mise \u00e0 jour de LM Studio ou de ses runtimes. Les chiffres seront les v\u00f4tres, pas les miens.<\/p>\n<h2>Pr\u00e9sentation de la maquette<\/h2>\n<ul>\n<li>Machine: Mac mini M4, 32 Go de m\u00e9moire unifi\u00e9e<\/li>\n<li>Logiciel: LM Studio 0.4.x avec la CLI <code>lms<\/code> et le serveur local sur le port 1234<\/li>\n<li>Outils: Python 3 (biblioth\u00e8que standard uniquement), <code>vm_stat<\/code>, <code>sysctl<\/code>, Moniteur d&rsquo;activit\u00e9<\/li>\n<li>R\u00e9pertoire de travail: <code>~\/bench-mlx-gguf<\/code><\/li>\n<\/ul>\n<p>Rien d&rsquo;autre \u00e0 installer. Les scripts ci dessous n&rsquo;utilisent aucune d\u00e9pendance externe, pour \u00e9viter qu&rsquo;une biblioth\u00e8que cliente ajoute sa propre latence ou ses propres param\u00e8tres par d\u00e9faut.<\/p>\n<h2>Deux formats, deux moteurs<\/h2>\n<p>Derri\u00e8re chaque format, il y a un moteur d&rsquo;inf\u00e9rence diff\u00e9rent, et c&rsquo;est lui que l&rsquo;on compare r\u00e9ellement. LM Studio embarque plusieurs runtimes et choisit celui qui correspond au format du mod\u00e8le.<\/p>\n<h3>GGUF et llama.cpp<\/h3>\n<p>Un mod\u00e8le GGUF tient dans un seul fichier, qui contient les poids quantifi\u00e9s et les m\u00e9tadonn\u00e9es: architecture, tokenizer, template de chat, etc\u2026 Il est ex\u00e9cut\u00e9 par llama.cpp, compil\u00e9 pour Apple Silicon avec le backend Metal. La documentation de l&rsquo;API REST de LM Studio donne d&rsquo;ailleurs un exemple de r\u00e9ponse o\u00f9 le runtime s&rsquo;appelle <code>llama.cpp-mac-arm64-apple-metal-advsimd<\/code>, avec <code>\"format\": \"gguf\"<\/code> et <code>\"quant\": \"Q4_K_M\"<\/code> dans le bloc <code>model_info<\/code> (<a href=\"https:\/\/lmstudio.ai\/docs\/developer\/rest\/endpoints\">REST API v0<\/a>). Nous allons nous servir de ces champs pour v\u00e9rifier \u00e0 chaque requ\u00eate quel moteur a vraiment r\u00e9pondu.<\/p>\n<p>Les quantifications GGUF portent des noms du type <code>Q4_K_M<\/code>, <code>Q5_K_M<\/code>, <code>Q6_K<\/code>, <code>Q8_0<\/code> ou <code>IQ4_XS<\/code>. Les \u00ab K-quants \u00bb m\u00e9langent plusieurs pr\u00e9cisions selon les tenseurs. Un <code>Q4_K_M<\/code> n&rsquo;est donc pas un 4 bits pur: en moyenne il tourne plut\u00f4t autour de 4,8 bits par poids (c&rsquo;est un ordre de grandeur, la valeur exacte d\u00e9pend de l&rsquo;architecture du mod\u00e8le).<\/p>\n<h3>MLX et mlx-engine<\/h3>\n<p>Un mod\u00e8le MLX se pr\u00e9sente comme un dossier: des fichiers <code>safetensors<\/code>, un <code>config.json<\/code> et les fichiers du tokenizer. C\u00f4t\u00e9 LM Studio, il est ex\u00e9cut\u00e9 par <a href=\"https:\/\/github.com\/lmstudio-ai\/mlx-engine\">mlx-engine<\/a>, un moteur open source (licence MIT) construit sur mlx-lm d&rsquo;Apple pour le texte et mlx-vlm pour la vision. D&rsquo;apr\u00e8s son d\u00e9p\u00f4t, ce moteur est livr\u00e9 avec LM Studio pour Mac depuis la version 0.3.4. L&rsquo;\u00e9quipe LM Studio a d\u00e9crit dans <a href=\"https:\/\/lmstudio.ai\/blog\/unified-mlx-engine\">cet article<\/a> comment elle a unifi\u00e9 les deux biblioth\u00e8ques: les impl\u00e9mentations texte de mlx-lm servent pour tous les mod\u00e8les, et les composants vision de mlx-vlm s&rsquo;y ajoutent sous forme d&rsquo;add-ons.<\/p>\n<p>La quantification MLX classique est affine, par groupes de poids (64 par d\u00e9faut), avec une \u00e9chelle et un biais en 16 bits par groupe. Ces deux valeurs ajoutent environ 0,5 bit par poids. Un MLX \u00ab 4bit \u00bb revient donc \u00e0 environ 4,5 bits par poids, et un \u00ab 8bit \u00bb \u00e0 environ 8,5. On trouve aussi des variantes 3, 5 ou 6 bits, du bf16 non quantifi\u00e9 (la note de version de <a href=\"https:\/\/lmstudio.ai\/changelog\/lmstudio-v0.4.0\">LM Studio 0.4.0<\/a> corrige justement un bug o\u00f9 bf16 n&rsquo;\u00e9tait pas reconnu comme quantification valide), et sur Hugging Face des variantes \u00ab DWQ \u00bb publi\u00e9es par la communaut\u00e9.<\/p>\n<p>Un point m&rsquo;a \u00e9t\u00e9 demand\u00e9 plusieurs fois: LM Studio ne quantifie rien lui m\u00eame. Les quantifications disponibles d\u00e9pendent de ce que les auteurs ont publi\u00e9 sur Hugging Face pour chaque mod\u00e8le. La liste change donc d&rsquo;un mod\u00e8le \u00e0 l&rsquo;autre: v\u00e9rifiez sur la page du mod\u00e8le que les deux formats existent dans des quantifications comparables avant de t\u00e9l\u00e9charger quoi que ce soit.<\/p>\n<h3>Quelle paire de quantifications comparer?<\/h3>\n<p>Je compare des variantes dont le nombre de bits par poids est le plus proche possible:<\/p>\n<table>\n<thead>\n<tr>\n<th>GGUF<\/th>\n<th>MLX<\/th>\n<th>Bits par poids (approx.)<\/th>\n<th>Commentaire<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td><code>Q8_0<\/code><\/td>\n<td>8bit<\/td>\n<td>8,5 \/ 8,5<\/td>\n<td>La paire la plus propre, les deux formats sont quasiment \u00e0 \u00e9galit\u00e9<\/td>\n<\/tr>\n<tr>\n<td><code>Q4_K_M<\/code><\/td>\n<td>4bit<\/td>\n<td>~4,8 \/ ~4,5<\/td>\n<td>La paire la plus utilis\u00e9e, l\u00e9ger avantage de taille pour MLX<\/td>\n<\/tr>\n<tr>\n<td><code>Q6_K<\/code><\/td>\n<td>6bit<\/td>\n<td>~6,6 \/ ~6,5<\/td>\n<td>Bon compromis si les deux existent<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>Je commence toujours par la paire <code>Q8_0<\/code> \/ 8bit: les deux formats y stockent quasiment le m\u00eame nombre de bits par poids, donc un \u00e9cart de vitesse ou de qualit\u00e9 \u00e0 ce niveau s&rsquo;explique par le moteur. Ensuite seulement je regarde la paire 4 bits, qui est celle que l&rsquo;on utilise vraiment au quotidien sur 32 Go.<\/p>\n<figure class=\"wp-block-image\">\n<svg viewBox=\"0 0 720 320\" width=\"100%\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\" font-family=\"sans-serif\" font-size=\"14\">\n<defs><marker id=\"fl\" viewBox=\"0 0 10 10\" refX=\"9\" refY=\"5\" markerWidth=\"7\" markerHeight=\"7\" orient=\"auto\"><path d=\"M0,0 L10,5 L0,10 z\" fill=\"#333\"\/><\/marker><\/defs>\n<rect x=\"20\" y=\"95\" width=\"150\" height=\"80\" rx=\"6\" fill=\"#fdf3e1\" stroke=\"#333\"\/>\n<text x=\"95\" y=\"128\" text-anchor=\"middle\">bench.py<\/text>\n<text x=\"95\" y=\"150\" text-anchor=\"middle\" font-size=\"13\">mem.sh (vm_stat)<\/text>\n<rect x=\"225\" y=\"85\" width=\"190\" height=\"100\" rx=\"6\" fill=\"#e8f0fe\" stroke=\"#333\"\/>\n<text x=\"320\" y=\"115\" text-anchor=\"middle\">LM Studio<\/text>\n<text x=\"320\" y=\"137\" text-anchor=\"middle\" font-size=\"13\">localhost:1234<\/text>\n<text x=\"320\" y=\"159\" text-anchor=\"middle\" font-size=\"13\">\/api\/v0\/chat\/completions<\/text>\n<line x1=\"170\" y1=\"135\" x2=\"223\" y2=\"135\" stroke=\"#333\" stroke-width=\"1.5\" marker-end=\"url(#fl)\"\/>\n<rect x=\"470\" y=\"30\" width=\"230\" height=\"90\" rx=\"6\" fill=\"#e6f4ea\" stroke=\"#333\"\/>\n<text x=\"585\" y=\"58\" text-anchor=\"middle\">Runtime llama.cpp (Metal)<\/text>\n<text x=\"585\" y=\"82\" text-anchor=\"middle\" font-size=\"13\">fichier unique .gguf<\/text>\n<text x=\"585\" y=\"102\" text-anchor=\"middle\" font-size=\"13\">Q8_0, Q6_K, Q4_K_M\u2026<\/text>\n<rect x=\"470\" y=\"150\" width=\"230\" height=\"90\" rx=\"6\" fill=\"#e6f4ea\" stroke=\"#333\"\/>\n<text x=\"585\" y=\"178\" text-anchor=\"middle\">Runtime MLX (mlx-engine)<\/text>\n<text x=\"585\" y=\"202\" text-anchor=\"middle\" font-size=\"13\">dossier safetensors + config<\/text>\n<text x=\"585\" y=\"222\" text-anchor=\"middle\" font-size=\"13\">8bit, 6bit, 4bit\u2026<\/text>\n<line x1=\"415\" y1=\"120\" x2=\"468\" y2=\"80\" stroke=\"#333\" stroke-width=\"1.5\" marker-end=\"url(#fl)\"\/>\n<line x1=\"415\" y1=\"150\" x2=\"468\" y2=\"190\" stroke=\"#333\" stroke-width=\"1.5\" marker-end=\"url(#fl)\"\/>\n<rect x=\"20\" y=\"265\" width=\"680\" height=\"40\" rx=\"6\" fill=\"#fdf3e1\" stroke=\"#333\"\/>\n<text x=\"360\" y=\"290\" text-anchor=\"middle\">M\u00e9moire unifi\u00e9e 32 Go (CPU + GPU), mesur\u00e9e globalement via la m\u00e9moire c\u00e2bl\u00e9e<\/text>\n<\/svg><figcaption>Un seul point d&rsquo;entr\u00e9e, deux moteurs: le script ne change pas, seul le mod\u00e8le charg\u00e9 change.<\/figcaption><\/figure>\n<h2>Choix de deux mod\u00e8les qui tiennent en 32 Go<\/h2>\n<p>Sur un Mac \u00e0 m\u00e9moire unifi\u00e9e, tout n&rsquo;est pas disponible pour le GPU. macOS limite la part de m\u00e9moire que Metal peut verrouiller. On lit souvent que cette limite se situe entre deux tiers et trois quarts de la RAM selon la machine, mais je n&rsquo;ai trouv\u00e9 aucune documentation Apple officielle qui le confirme. Je pars donc du principe qu&rsquo;il faut viser un mod\u00e8le et son cache de contexte sous 20 Go environ, pour garder de la marge pour macOS et LM Studio.<\/p>\n<p>Le calcul de base est simple: nombre de param\u00e8tres \u00d7 bits par poids \/ 8. Il faut ensuite ajouter le cache KV, qui grossit avec la longueur de contexte.<\/p>\n<table>\n<thead>\n<tr>\n<th>Mod\u00e8le (exemple)<\/th>\n<th>4 bits (~4,5 \u00e0 4,8 bpp)<\/th>\n<th>8 bits (~8,5 bpp)<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>Dense ~14 milliards de param\u00e8tres<\/td>\n<td>~8 \u00e0 8,5 Go<\/td>\n<td>~15 Go<\/td>\n<\/tr>\n<tr>\n<td>MoE ~30 milliards (3 milliards actifs)<\/td>\n<td>~17 \u00e0 18 Go<\/td>\n<td>~32 Go, hors jeu<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>Je retiens donc deux profils:<\/p>\n<ol>\n<li>Un mod\u00e8le dense d&rsquo;environ 14 milliards de param\u00e8tres (la famille Qwen3 14B par exemple, ou son successeur s&rsquo;il existe dans votre catalogue). Il passe dans les deux paires, <code>Q8_0<\/code> \/ 8bit et <code>Q4_K_M<\/code> \/ 4bit.<\/li>\n<li>Un mod\u00e8le MoE d&rsquo;environ 30 milliards de param\u00e8tres totaux avec peu de param\u00e8tres actifs (du type Qwen3-30B-A3B). Uniquement en 4 bits. Son int\u00e9r\u00eat est ailleurs: la lecture des poids ne concerne qu&rsquo;une fraction des experts \u00e0 chaque token, et les deux moteurs ne g\u00e8rent pas forc\u00e9ment ce cas avec la m\u00eame efficacit\u00e9.<\/li>\n<\/ol>\n<p>Sur une machine dont la bande passante m\u00e9moire est limit\u00e9e (de l&rsquo;ordre de 120 Go\/s sur le M4 de base), un mod\u00e8le dense et un MoE ne sollicitent pas la m\u00e9moire de la m\u00eame fa\u00e7on, et un moteur peut s&rsquo;en sortir mieux que l&rsquo;autre sur l&rsquo;un des deux. Avant de t\u00e9l\u00e9charger quoi que ce soit, la CLI peut estimer la m\u00e9moire n\u00e9cessaire sans charger le mod\u00e8le (<a href=\"https:\/\/lmstudio.ai\/docs\/cli\/local-models\/load\">documentation de lms load<\/a>):<\/p>\n<pre class=\"wp-block-code\"><code>lms ls\nlms load --estimate-only &lt;cle_du_modele&gt; --context-length 8192<\/code><\/pre>\n<p>D&rsquo;apr\u00e8s la documentation, l&rsquo;estimation tient compte de la longueur de contexte, de flash attention et du fait que le mod\u00e8le g\u00e8re ou non la vision.<\/p>\n<h2>Pr\u00e9parer un banc \u00e9quitable<\/h2>\n<p>La plupart des \u00e9carts que je vois passer viennent d&rsquo;ailleurs que du format. Avant toute mesure, je fixe donc les points suivants:<\/p>\n<ol>\n<li>Un seul mod\u00e8le charg\u00e9 \u00e0 la fois: <code>lms unload --all<\/code> entre chaque s\u00e9rie.<\/li>\n<li>M\u00eame longueur de contexte des deux c\u00f4t\u00e9s, pass\u00e9e explicitement avec <code>--context-length<\/code>. Je prends 8192, assez pour mes prompts sans gonfler inutilement le cache KV.<\/li>\n<li>D\u00e9codage sp\u00e9culatif d\u00e9sactiv\u00e9 (il existe dans LM Studio depuis la 0.3.10). Un mod\u00e8le brouillon fausse compl\u00e8tement les tokens\/s.<\/li>\n<li>Quantification du cache KV d\u00e9sactiv\u00e9e des deux c\u00f4t\u00e9s, et pour le GGUF, d\u00e9chargement complet des couches sur le GPU. V\u00e9rifiez ces r\u00e9glages dans le panneau de chargement du mod\u00e8le, les libell\u00e9s \u00e9voluent d&rsquo;une version \u00e0 l&rsquo;autre.<\/li>\n<li>Temp\u00e9rature \u00e0 0 et m\u00eame <code>max_tokens<\/code>. M\u00eame \u00e0 temp\u00e9rature 0, les deux moteurs ne produiront pas exactement le m\u00eame texte: les calculs en virgule flottante diff\u00e8rent et une seule divergence de token suffit \u00e0 changer toute la suite.<\/li>\n<li>Pour un mod\u00e8le \u00ab thinking \u00bb, m\u00eame mode de raisonnement des deux c\u00f4t\u00e9s, sinon les longueurs de r\u00e9ponse ne sont plus comparables.<\/li>\n<li>Mac sur secteur, autres applications ferm\u00e9es, et des s\u00e9ries altern\u00e9es (GGUF, MLX, GGUF, MLX) plut\u00f4t que tout un format puis tout l&rsquo;autre, pour lisser les effets thermiques et le bruit de fond du syst\u00e8me.<\/li>\n<\/ol>\n<p>Le chargement se fait avec un identifiant explicite, ce qui permet au script de cibler le bon mod\u00e8le sans ambigu\u00eft\u00e9:<\/p>\n<pre class=\"wp-block-code\"><code>lms server start\nlms unload --all\nlms load &lt;cle_variante_gguf&gt; --identifier bench-14b-q8-gguf --context-length 8192\nlms ps<\/code><\/pre>\n<p>Si les deux variantes apparaissent sous des cl\u00e9s proches dans <code>lms ls<\/code>, prenez le temps de v\u00e9rifier laquelle est charg\u00e9e. Le script relit de toute fa\u00e7on le format et le moteur dans chaque r\u00e9ponse.<\/p>\n<h2>Script d&rsquo;appel via l&rsquo;API locale avec chronom\u00e9trage<\/h2>\n<p>LM Studio expose une API compatible OpenAI sur <code>\/v1<\/code>, mais elle ne renvoie pas les statistiques de g\u00e9n\u00e9ration. Son API REST propre les renvoie: le bloc <code>stats<\/code> contient <code>tokens_per_second<\/code>, <code>time_to_first_token<\/code> et <code>generation_time<\/code>, et les blocs <code>model_info<\/code> et <code>runtime<\/code> indiquent le format, la quantification et le moteur (<a href=\"https:\/\/lmstudio.ai\/docs\/developer\/rest\/endpoints\">REST API v0<\/a>). La documentation recommande d\u00e9sormais la nouvelle API v1 pour les nouveaux projets. Son endpoint de chat renvoie lui aussi <code>tokens_per_second<\/code> et <code>time_to_first_token_seconds<\/code> (<a href=\"https:\/\/lmstudio.ai\/docs\/developer\/rest\/chat\">Chat with a model<\/a>). J&rsquo;utilise ici la v0 parce que son format de requ\u00eate est celui de l&rsquo;API OpenAI et qu&rsquo;elle renvoie le moteur utilis\u00e9. Le passage \u00e0 la v1 ne change que quelques lignes.<\/p>\n<p>Je ne me contente pas des statistiques du serveur. Le script mesure aussi le temps mur c\u00f4t\u00e9 client, ce qui permet de rep\u00e9rer une incoh\u00e9rence (temps de chargement cach\u00e9, file d&rsquo;attente, etc\u2026).<\/p>\n<p>Le jeu de prompts est un fichier JSONL. Chaque ligne contient un identifiant, le prompt et les \u00e9l\u00e9ments attendus dans la r\u00e9ponse, qui serviront pour la notation de qualit\u00e9:<\/p>\n<pre class=\"wp-block-code\"><code>{\"id\": \"krb01\", \"prompt\": \"Explique le r\u00f4le du PAC dans un ticket Kerberos Active Directory.\", \"attendu\": [\"MS-PAC\", \"SID de groupes\", \"signature KDC\"]}\n{\"id\": \"ntlm01\", \"prompt\": \"Pourquoi le relais NTLM fonctionne-t-il sans conna\u00eetre le mot de passe ?\", \"attendu\": [\"challenge\/r\u00e9ponse\", \"absence de liaison au canal\", \"signature SMB\"]}\n{\"id\": \"saml01\", \"prompt\": \"Quelle est la diff\u00e9rence entre la signature de la Response et celle de l'Assertion en SAML 2.0 ?\", \"attendu\": [\"XML Signature\", \"assertion seule\", \"wrapping\"]}\n{\"id\": \"long01\", \"prompt\": \"R\u00e9sume le texte suivant en 10 points : ...\", \"attendu\": []}<\/code><\/pre>\n<p>Je m\u00e9lange volontairement des prompts courts, o\u00f9 c&rsquo;est la g\u00e9n\u00e9ration qui domine, et au moins un prompt long (quelques milliers de tokens de texte coll\u00e9), o\u00f9 c&rsquo;est le traitement du prompt qui domine. Les deux moteurs ne se classent pas forc\u00e9ment de la m\u00eame fa\u00e7on sur les deux phases.<\/p>\n<pre class=\"wp-block-code\"><code>#!\/usr\/bin\/env python3\n# bench.py - m\u00eames prompts, m\u00eames param\u00e8tres, un mod\u00e8le charg\u00e9 \u00e0 la fois\n# usage: python3 bench.py bench-14b-q8-gguf prompts.jsonl 3\nimport csv, json, sys, time, urllib.request\n\nAPI = \"http:\/\/localhost:1234\/api\/v0\/chat\/completions\"\nMODEL = sys.argv[1]\nPROMPTS = sys.argv[2]\nRUNS = int(sys.argv[3]) if len(sys.argv) &gt; 3 else 3\n\ndef ask(prompt):\n    body = {\n        \"model\": MODEL,\n        \"messages\": [{\"role\": \"user\", \"content\": prompt}],\n        \"temperature\": 0,\n        \"max_tokens\": 1024,\n        \"stream\": False,\n    }\n    req = urllib.request.Request(API, data=json.dumps(body).encode(),\n                                 headers={\"Content-Type\": \"application\/json\"})\n    t0 = time.perf_counter()\n    with urllib.request.urlopen(req, timeout=900) as r:\n        data = json.load(r)\n    return data, time.perf_counter() - t0\n\nprompts = [json.loads(l) for l in open(PROMPTS, encoding=\"utf-8\") if l.strip()]\n\nask(\"R\u00e9ponds uniquement: OK\")   # \u00e9chauffement, non mesur\u00e9\n\nwith open(f\"mesures-{MODEL}.csv\", \"w\", newline=\"\") as f_csv, \\\n     open(f\"reponses-{MODEL}.jsonl\", \"w\", encoding=\"utf-8\") as f_rep:\n    w = csv.writer(f_csv)\n    w.writerow([\"modele\", \"format\", \"quant\", \"runtime\", \"id\", \"run\",\n                \"prompt_tokens\", \"completion_tokens\", \"ttft_s\",\n                \"tokens_s\", \"generation_s\", \"mur_s\", \"stop\"])\n    for run in range(1, RUNS + 1):\n        for p in prompts:\n            d, mur = ask(p[\"prompt\"])\n            st, mi = d.get(\"stats\", {}), d.get(\"model_info\", {})\n            rt, us = d.get(\"runtime\", {}), d.get(\"usage\", {})\n            w.writerow([MODEL, mi.get(\"format\"), mi.get(\"quant\"), rt.get(\"name\"),\n                        p[\"id\"], run, us.get(\"prompt_tokens\"), us.get(\"completion_tokens\"),\n                        st.get(\"time_to_first_token\"),\n                        round(st.get(\"tokens_per_second\", 0), 2),\n                        st.get(\"generation_time\"), round(mur, 3), st.get(\"stop_reason\")])\n            if run == 1:\n                f_rep.write(json.dumps({\"id\": p[\"id\"], \"modele\": MODEL,\n                    \"reponse\": d[\"choices\"][0][\"message\"][\"content\"]},\n                    ensure_ascii=False) + \"\\n\")\n            print(f\"{p['id']:8} run {run}  {st.get('tokens_per_second', 0):6.1f} tok\/s  \"\n                  f\"TTFT {st.get('time_to_first_token')} s  [{mi.get('format')} {mi.get('quant')}]\")<\/code><\/pre>\n<p>Quelques remarques sur ce script:<\/p>\n<ul>\n<li>-> L&rsquo;appel d&rsquo;\u00e9chauffement absorbe la compilation des shaders et les \u00e9ventuelles allocations du premier passage. Sans lui, la premi\u00e8re mesure de chaque s\u00e9rie est syst\u00e9matiquement plus lente.<\/li>\n<li>-> Les colonnes <code>format<\/code>, <code>quant<\/code> et <code>runtime<\/code> servent de garde-fou. Si vous lisez <code>gguf<\/code> dans un fichier cens\u00e9 contenir des mesures MLX, inutile d&rsquo;aller plus loin.<\/li>\n<li>-> Comparez <code>completion_tokens<\/code> entre les deux formats. Un tokens\/s n&rsquo;a de valeur que sur des longueurs de r\u00e9ponse comparables, et une r\u00e9ponse coup\u00e9e par <code>max_tokens<\/code> (<code>stop<\/code> diff\u00e9rent de la fin normale) doit \u00eatre signal\u00e9e.<\/li>\n<li>-> Le m\u00eame prompt rejou\u00e9 plusieurs fois peut profiter d&rsquo;un cache de prompt c\u00f4t\u00e9 moteur, ce qui fait baisser le TTFT d\u00e8s le deuxi\u00e8me passage. Pour le TTFT, je ne retiens que le run 1 de chaque prompt. Pour les tokens\/s de g\u00e9n\u00e9ration, les runs 2 et 3 sont exploitables.<\/li>\n<\/ul>\n<p>Pour agr\u00e9ger, une m\u00e9diane suffit. La moyenne est trop sensible \u00e0 une mesure perturb\u00e9e par un processus en arri\u00e8re-plan:<\/p>\n<pre class=\"wp-block-code\"><code>#!\/usr\/bin\/env python3\n# synthese.py - usage: python3 synthese.py mesures-*.csv\nimport csv, sys, statistics as s\n\nrows = [r for f in sys.argv[1:] for r in csv.DictReader(open(f))]\nfor m in sorted({r[\"modele\"] for r in rows}):\n    rr = [r for r in rows if r[\"modele\"] == m]\n    tps = [float(r[\"tokens_s\"]) for r in rr if r[\"run\"] != \"1\"]\n    ttft = [float(r[\"ttft_s\"]) for r in rr if r[\"run\"] == \"1\"]\n    print(f\"{m:24} {rr[0]['format']:5} {rr[0]['quant']:8} \"\n          f\"tok\/s med {s.median(tps):6.1f} (min {min(tps):5.1f} max {max(tps):5.1f})  \"\n          f\"TTFT med {s.median(ttft):5.2f} s\")<\/code><\/pre>\n<p>Si le prompt long donne un r\u00e9sultat tr\u00e8s diff\u00e9rent des prompts courts, s\u00e9parez-le dans la synth\u00e8se. Le m\u00e9langer aux autres noierait justement l&rsquo;information qui vous int\u00e9resse.<\/p>\n<h2>Relev\u00e9 de la m\u00e9moire avec Moniteur d&rsquo;activit\u00e9 ou vm_stat<\/h2>\n<p>Mesurer la m\u00e9moire consomm\u00e9e par un mod\u00e8le sur Apple Silicon est moins simple qu&rsquo;il n&rsquo;y para\u00eet. La colonne \u00ab M\u00e9moire \u00bb d&rsquo;un processus dans Moniteur d&rsquo;activit\u00e9 ne refl\u00e8te pas toujours les tampons Metal allou\u00e9s pour le GPU. De plus, un fichier GGUF peut \u00eatre projet\u00e9 en m\u00e9moire (mmap) et compt\u00e9 comme cache de fichiers plut\u00f4t que comme m\u00e9moire du processus. Je ne cherche donc pas \u00e0 attribuer la m\u00e9moire \u00e0 un processus pr\u00e9cis. Je regarde l&rsquo;\u00e9volution globale de la m\u00e9moire c\u00e2bl\u00e9e (wired), o\u00f9 atterrissent les allocations verrouill\u00e9es pour le GPU, et de la m\u00e9moire compress\u00e9e, qui signale que le syst\u00e8me commence \u00e0 manquer d&rsquo;air.<\/p>\n<p>Dans Moniteur d&rsquo;activit\u00e9, l&rsquo;onglet \u00ab M\u00e9moire \u00bb affiche en bas \u00ab M\u00e9moire c\u00e2bl\u00e9e \u00bb, \u00ab Compress\u00e9e \u00bb et le graphe \u00ab Pression de la m\u00e9moire \u00bb. C&rsquo;est suffisant pour un contr\u00f4le visuel. Pour un relev\u00e9 exploitable, <code>vm_stat<\/code> fait le travail en ligne de commande. Sur Apple Silicon, la taille de page est de 16 Ko, mais je la lis avec <code>sysctl<\/code> plut\u00f4t que de la coder en dur:<\/p>\n<pre class=\"wp-block-code\"><code>#!\/bin\/bash\n# mem.sh - rel\u00e8ve la m\u00e9moire c\u00e2bl\u00e9e et compress\u00e9e chaque seconde\n# usage: .\/mem.sh mem-14b-q8-gguf.csv   (Ctrl+C pour arr\u00eater)\nPAGE=$(sysctl -n hw.pagesize)\nOUT=${1:-mem.csv}\necho \"horodatage,cablee_go,compressee_go\" &gt; \"$OUT\"\nwhile true; do\n  vm_stat | awk -v p=\"$PAGE\" -v t=\"$(date +%H:%M:%S)\" '\n    \/Pages wired down\/             { gsub(\/\\.\/, \"\", $4); w = $4 }\n    \/Pages occupied by compressor\/ { gsub(\/\\.\/, \"\", $5); c = $5 }\n    END { printf \"%s,%.2f,%.2f\\n\", t, w*p\/1073741824, c*p\/1073741824 }' &gt;&gt; \"$OUT\"\n  sleep 1\ndone<\/code><\/pre>\n<p>La proc\u00e9dure pour chaque variante:<\/p>\n<ol>\n<li><code>lms unload --all<\/code>, puis lancer <code>.\/mem.sh<\/code> et attendre une dizaine de secondes. Cela donne la valeur de base.<\/li>\n<li>Charger le mod\u00e8le avec <code>lms load<\/code>. Le palier atteint donne le co\u00fbt des poids et du cache allou\u00e9.<\/li>\n<li>Lancer <code>bench.py<\/code>. Le pic pendant le prompt long donne le co\u00fbt r\u00e9el en utilisation.<\/li>\n<li>Arr\u00eater <code>mem.sh<\/code> et noter: base, palier apr\u00e8s chargement, pic. On retient les deltas, pas les valeurs absolues.<\/li>\n<\/ol>\n<p>Si la m\u00e9moire compress\u00e9e grimpe pendant le bench, la mesure de vitesse de cette s\u00e9rie n&rsquo;est plus fiable: le syst\u00e8me \u00e9change des pages et vous mesurez le compresseur de macOS plut\u00f4t que le moteur d&rsquo;inf\u00e9rence.<\/p>\n<p>Il existe un r\u00e9glage <code>sysctl iogpu.wired_limit_mb<\/code> qui permet de relever la limite de m\u00e9moire verrouillable par le GPU. Je ne l&rsquo;utilise pas pour ce comparatif. Il ne change rien \u00e0 l&rsquo;\u00e9quit\u00e9 entre les deux formats, et pousser cette limite sur une machine de 32 Go revient surtout \u00e0 priver macOS de m\u00e9moire.<\/p>\n<h2>Notation de la qualit\u00e9<\/h2>\n<p>Les tokens\/s se mesurent. La qualit\u00e9, beaucoup moins. \u00c0 quantification \u00e9quivalente, les deux formats contiennent en th\u00e9orie les m\u00eames poids \u00e0 une approximation pr\u00e8s. En pratique, les m\u00e9thodes de quantification diff\u00e8rent (blocs, m\u00e9lange de pr\u00e9cisions, quels tenseurs restent en haute pr\u00e9cision) et les r\u00e9ponses divergent.<\/p>\n<p>Je proc\u00e8de \u00e0 l&rsquo;aveugle, sur un jeu de 15 \u00e0 20 questions de mon domaine dont je connais parfaitement les r\u00e9ponses. Le champ <code>attendu<\/code> du fichier de prompts liste les \u00e9l\u00e9ments qu&rsquo;une bonne r\u00e9ponse doit contenir. Le script ci dessous m\u00e9lange les r\u00e9ponses des deux variantes, les renomme A et B, et garde la correspondance dans un fichier s\u00e9par\u00e9 que je n&rsquo;ouvre qu&rsquo;apr\u00e8s la notation:<\/p>\n<pre class=\"wp-block-code\"><code>#!\/usr\/bin\/env python3\n# aveugle.py - usage: python3 aveugle.py reponses-X.jsonl reponses-Y.jsonl\nimport json, random, sys\n\na = {r[\"id\"]: r for r in map(json.loads, open(sys.argv[1], encoding=\"utf-8\"))}\nb = {r[\"id\"]: r for r in map(json.loads, open(sys.argv[2], encoding=\"utf-8\"))}\ncle, sortie = {}, []\nfor i in sorted(a.keys() &amp; b.keys()):\n    paire = [a[i], b[i]]\n    random.shuffle(paire)\n    cle[i] = {\"A\": paire[0][\"modele\"], \"B\": paire[1][\"modele\"]}\n    sortie.append(f\"## {i}\\n\\n### A\\n{paire[0]['reponse']}\\n\\n### B\\n{paire[1]['reponse']}\\n\")\nopen(\"notation.md\", \"w\", encoding=\"utf-8\").write(\"\\n\".join(sortie))\njson.dump(cle, open(\"cle.json\", \"w\"), indent=2)<\/code><\/pre>\n<p>Pour chaque r\u00e9ponse, je note sur une grille simple: 0 si un \u00e9l\u00e9ment attendu est faux, 1 s&rsquo;il manque, 2 s&rsquo;il est pr\u00e9sent et juste. J&rsquo;ajoute une p\u00e9nalit\u00e9 pour toute affirmation technique invent\u00e9e (un attribut LDAP qui n&rsquo;existe pas, un num\u00e9ro de RFC erron\u00e9\u2026). Sur un jeu aussi petit, un \u00e9cart de quelques points entre les deux formats n&rsquo;a rien de significatif. Seul un \u00e9cart net et r\u00e9p\u00e9t\u00e9, par exemple une variante qui se trompe syst\u00e9matiquement sur les prompts longs, m\u00e9rite d&rsquo;\u00eatre pris en compte.<\/p>\n<h2>Grille de lecture et limites<\/h2>\n<table>\n<thead>\n<tr>\n<th>Mesure<\/th>\n<th>Source<\/th>\n<th>Ce qu&rsquo;elle dit<\/th>\n<th>Ce qu&rsquo;elle ne dit pas<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>tokens\/s (runs 2 et 3)<\/td>\n<td><code>stats.tokens_per_second<\/code><\/td>\n<td>Vitesse de g\u00e9n\u00e9ration ressentie en lecture<\/td>\n<td>Le temps d&rsquo;attente avant la premi\u00e8re ligne<\/td>\n<\/tr>\n<tr>\n<td>TTFT (run 1)<\/td>\n<td><code>stats.time_to_first_token<\/code><\/td>\n<td>Co\u00fbt du traitement du prompt, d\u00e9cisif sur les longs contextes<\/td>\n<td>Le comportement en conversation longue avec cache<\/td>\n<\/tr>\n<tr>\n<td>Temps mur<\/td>\n<td>chronom\u00e8tre client<\/td>\n<td>Contr\u00f4le de coh\u00e9rence de bout en bout<\/td>\n<td>La r\u00e9partition entre prompt et g\u00e9n\u00e9ration<\/td>\n<\/tr>\n<tr>\n<td>Delta m\u00e9moire c\u00e2bl\u00e9e<\/td>\n<td><code>vm_stat<\/code><\/td>\n<td>Empreinte r\u00e9elle du mod\u00e8le et du cache<\/td>\n<td>Une attribution pr\u00e9cise par processus<\/td>\n<\/tr>\n<tr>\n<td>Note qualit\u00e9<\/td>\n<td>grille \u00e0 l&rsquo;aveugle<\/td>\n<td>R\u00e9gression \u00e9vidente d&rsquo;une quantification<\/td>\n<td>Une diff\u00e9rence fine, le jeu est trop petit<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>Avant de choisir un format sur la base de ces mesures, gardez en t\u00eate ce qu&rsquo;elles ne couvrent pas:<\/p>\n<ul>\n<li>Les r\u00e9sultats d\u00e9pendent d&rsquo;un instant pr\u00e9cis. LM Studio met \u00e0 jour ses runtimes ind\u00e9pendamment de l&rsquo;application, et un gain d&rsquo;un format peut dispara\u00eetre avec la version suivante de l&rsquo;autre moteur. Notez la version de LM Studio et le nom de runtime pr\u00e9sent dans le CSV \u00e0 chaque s\u00e9rie.<\/li>\n<li>Une quantification \u00ab 4 bits \u00bb n&rsquo;en vaut pas une autre. <code>Q4_K_M<\/code> contre MLX 4bit n&rsquo;est pas une \u00e9galit\u00e9 parfaite, d&rsquo;o\u00f9 l&rsquo;int\u00e9r\u00eat de commencer par la paire 8 bits.<\/li>\n<li>Les fonctionnalit\u00e9s ne sont pas identiques. Certaines options (formats de sortie structur\u00e9e, quantification du cache, vision, d\u00e9codage sp\u00e9culatif) n&rsquo;ont pas le m\u00eame support ni la m\u00eame maturit\u00e9 selon le moteur. Un format plus rapide mais priv\u00e9 d&rsquo;une option dont vous avez besoin ne vous sert \u00e0 rien.<\/li>\n<li>L&rsquo;usage r\u00e9el n&rsquo;est pas un bench. Un agent qui renvoie 20 000 tokens de contexte \u00e0 chaque tour sera surtout sensible au TTFT et \u00e0 la m\u00e9moire. Une discussion courte, aux tokens\/s.<\/li>\n<\/ul>\n<p>Sur un Mac mini M4 de 32 Go, la m\u00e9moire est la contrainte qui tranche le plus souvent. Si les deux formats sont proches en vitesse et en qualit\u00e9 sur vos prompts, prenez celui qui laisse le plus de marge en m\u00e9moire c\u00e2bl\u00e9e sur le prompt long. C&rsquo;est cette marge qui d\u00e9cidera si vous pouvez garder un deuxi\u00e8me mod\u00e8le charg\u00e9 \u00e0 c\u00f4t\u00e9 ou monter la longueur de contexte sans voir la pression m\u00e9moire passer au jaune.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Comparer un m\u00eame mod\u00e8le en MLX et en GGUF dans LM Studio sur Mac mini M4: m\u00eames prompts, quantification proche, script de mesure, relev\u00e9 m\u00e9moire et limites.<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[59,58],"tags":[66,62,63,60,64,61,65],"class_list":["post-1240","post","type-post","status-publish","format-standard","hentry","category-intelligence-artificielle","category-llm-en-local","tag-benchmark","tag-gguf","tag-llama-cpp","tag-lm-studio","tag-mac-mini-m4","tag-mlx","tag-quantification"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v28.7 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>MLX ou GGUF dans LM Studio sur Mac mini M4: une m\u00e9thode de comparaison reproductible - R\u00e9mi VERNIER<\/title>\n<meta name=\"description\" content=\"Comparer un m\u00eame mod\u00e8le en MLX et en GGUF dans LM Studio sur Mac mini M4: m\u00eames prompts, quantification proche, script de mesure, relev\u00e9 m\u00e9moire et limites.\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/remivernier.com\/index.php\/2026\/10\/27\/mlx-gguf-lm-studio-mac-mini-m4-methode-comparaison\/\" \/>\n<meta property=\"og:locale\" content=\"fr_FR\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"MLX ou GGUF dans LM Studio sur Mac mini M4: une m\u00e9thode de comparaison reproductible - R\u00e9mi VERNIER\" \/>\n<meta property=\"og:description\" content=\"Comparer un m\u00eame mod\u00e8le en MLX et en GGUF dans LM Studio sur Mac mini M4: m\u00eames prompts, quantification proche, script de mesure, relev\u00e9 m\u00e9moire et limites.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/remivernier.com\/index.php\/2026\/10\/27\/mlx-gguf-lm-studio-mac-mini-m4-methode-comparaison\/\" \/>\n<meta property=\"og:site_name\" content=\"R\u00e9mi VERNIER\" \/>\n<meta property=\"article:published_time\" content=\"2026-10-27T13:59:58+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2026-10-27T20:54:40+00:00\" \/>\n<meta name=\"author\" content=\"R\u00e9mi\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"\u00c9crit par\" \/>\n\t<meta name=\"twitter:data1\" content=\"R\u00e9mi\" \/>\n\t<meta name=\"twitter:label2\" content=\"Dur\u00e9e de lecture estim\u00e9e\" \/>\n\t<meta name=\"twitter:data2\" content=\"19 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\\\/\\\/remivernier.com\\\/index.php\\\/2026\\\/10\\\/27\\\/mlx-gguf-lm-studio-mac-mini-m4-methode-comparaison\\\/#article\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/remivernier.com\\\/index.php\\\/2026\\\/10\\\/27\\\/mlx-gguf-lm-studio-mac-mini-m4-methode-comparaison\\\/\"},\"author\":{\"name\":\"R\u00e9mi\",\"@id\":\"https:\\\/\\\/remivernier.com\\\/#\\\/schema\\\/person\\\/a4c76c63a5a3d94454484845e42fc906\"},\"headline\":\"MLX ou GGUF dans LM Studio sur Mac mini M4: une m\u00e9thode de comparaison reproductible\",\"datePublished\":\"2026-10-27T13:59:58+00:00\",\"dateModified\":\"2026-10-27T20:54:40+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\\\/\\\/remivernier.com\\\/index.php\\\/2026\\\/10\\\/27\\\/mlx-gguf-lm-studio-mac-mini-m4-methode-comparaison\\\/\"},\"wordCount\":3005,\"commentCount\":0,\"keywords\":[\"benchmark\",\"gguf\",\"llama.cpp\",\"lm studio\",\"mac mini m4\",\"mlx\",\"quantification\"],\"articleSection\":[\"Intelligence artificielle\",\"LLM en local\"],\"inLanguage\":\"fr-FR\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\\\/\\\/remivernier.com\\\/index.php\\\/2026\\\/10\\\/27\\\/mlx-gguf-lm-studio-mac-mini-m4-methode-comparaison\\\/#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/remivernier.com\\\/index.php\\\/2026\\\/10\\\/27\\\/mlx-gguf-lm-studio-mac-mini-m4-methode-comparaison\\\/\",\"url\":\"https:\\\/\\\/remivernier.com\\\/index.php\\\/2026\\\/10\\\/27\\\/mlx-gguf-lm-studio-mac-mini-m4-methode-comparaison\\\/\",\"name\":\"MLX ou GGUF dans LM Studio sur Mac mini M4: une m\u00e9thode de comparaison reproductible - R\u00e9mi VERNIER\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/remivernier.com\\\/#website\"},\"datePublished\":\"2026-10-27T13:59:58+00:00\",\"dateModified\":\"2026-10-27T20:54:40+00:00\",\"author\":{\"@id\":\"https:\\\/\\\/remivernier.com\\\/#\\\/schema\\\/person\\\/a4c76c63a5a3d94454484845e42fc906\"},\"description\":\"Comparer un m\u00eame mod\u00e8le en MLX et en GGUF dans LM Studio sur Mac mini M4: m\u00eames prompts, quantification proche, script de mesure, relev\u00e9 m\u00e9moire et limites.\",\"breadcrumb\":{\"@id\":\"https:\\\/\\\/remivernier.com\\\/index.php\\\/2026\\\/10\\\/27\\\/mlx-gguf-lm-studio-mac-mini-m4-methode-comparaison\\\/#breadcrumb\"},\"inLanguage\":\"fr-FR\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\\\/\\\/remivernier.com\\\/index.php\\\/2026\\\/10\\\/27\\\/mlx-gguf-lm-studio-mac-mini-m4-methode-comparaison\\\/\"]}]},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/remivernier.com\\\/index.php\\\/2026\\\/10\\\/27\\\/mlx-gguf-lm-studio-mac-mini-m4-methode-comparaison\\\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Accueil\",\"item\":\"https:\\\/\\\/remivernier.com\\\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"MLX ou GGUF dans LM Studio sur Mac mini M4: une m\u00e9thode de comparaison reproductible\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\\\/\\\/remivernier.com\\\/#website\",\"url\":\"https:\\\/\\\/remivernier.com\\\/\",\"name\":\"R\u00e9mi VERNIER\",\"description\":\"Identit\u00e9, s\u00e9curit\u00e9, SAP et IA : Active Directory, Azure &amp; co...\",\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\\\/\\\/remivernier.com\\\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"fr-FR\"},{\"@type\":\"Person\",\"@id\":\"https:\\\/\\\/remivernier.com\\\/#\\\/schema\\\/person\\\/a4c76c63a5a3d94454484845e42fc906\",\"name\":\"R\u00e9mi\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"fr-FR\",\"@id\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/f750448bc07f5d1d9c1189344283777ec0d33275c184599b199fb6b305bc6fba?s=96&d=mm&r=g\",\"url\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/f750448bc07f5d1d9c1189344283777ec0d33275c184599b199fb6b305bc6fba?s=96&d=mm&r=g\",\"contentUrl\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/f750448bc07f5d1d9c1189344283777ec0d33275c184599b199fb6b305bc6fba?s=96&d=mm&r=g\",\"caption\":\"R\u00e9mi\"},\"sameAs\":[\"http:\\\/\\\/www.linkedin.com\\\/in\\\/remi-vernier\"]}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"MLX ou GGUF dans LM Studio sur Mac mini M4: une m\u00e9thode de comparaison reproductible - R\u00e9mi VERNIER","description":"Comparer un m\u00eame mod\u00e8le en MLX et en GGUF dans LM Studio sur Mac mini M4: m\u00eames prompts, quantification proche, script de mesure, relev\u00e9 m\u00e9moire et limites.","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/remivernier.com\/index.php\/2026\/10\/27\/mlx-gguf-lm-studio-mac-mini-m4-methode-comparaison\/","og_locale":"fr_FR","og_type":"article","og_title":"MLX ou GGUF dans LM Studio sur Mac mini M4: une m\u00e9thode de comparaison reproductible - R\u00e9mi VERNIER","og_description":"Comparer un m\u00eame mod\u00e8le en MLX et en GGUF dans LM Studio sur Mac mini M4: m\u00eames prompts, quantification proche, script de mesure, relev\u00e9 m\u00e9moire et limites.","og_url":"https:\/\/remivernier.com\/index.php\/2026\/10\/27\/mlx-gguf-lm-studio-mac-mini-m4-methode-comparaison\/","og_site_name":"R\u00e9mi VERNIER","article_published_time":"2026-10-27T13:59:58+00:00","article_modified_time":"2026-10-27T20:54:40+00:00","author":"R\u00e9mi","twitter_card":"summary_large_image","twitter_misc":{"\u00c9crit par":"R\u00e9mi","Dur\u00e9e de lecture estim\u00e9e":"19 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/remivernier.com\/index.php\/2026\/10\/27\/mlx-gguf-lm-studio-mac-mini-m4-methode-comparaison\/#article","isPartOf":{"@id":"https:\/\/remivernier.com\/index.php\/2026\/10\/27\/mlx-gguf-lm-studio-mac-mini-m4-methode-comparaison\/"},"author":{"name":"R\u00e9mi","@id":"https:\/\/remivernier.com\/#\/schema\/person\/a4c76c63a5a3d94454484845e42fc906"},"headline":"MLX ou GGUF dans LM Studio sur Mac mini M4: une m\u00e9thode de comparaison reproductible","datePublished":"2026-10-27T13:59:58+00:00","dateModified":"2026-10-27T20:54:40+00:00","mainEntityOfPage":{"@id":"https:\/\/remivernier.com\/index.php\/2026\/10\/27\/mlx-gguf-lm-studio-mac-mini-m4-methode-comparaison\/"},"wordCount":3005,"commentCount":0,"keywords":["benchmark","gguf","llama.cpp","lm studio","mac mini m4","mlx","quantification"],"articleSection":["Intelligence artificielle","LLM en local"],"inLanguage":"fr-FR","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/remivernier.com\/index.php\/2026\/10\/27\/mlx-gguf-lm-studio-mac-mini-m4-methode-comparaison\/#respond"]}]},{"@type":"WebPage","@id":"https:\/\/remivernier.com\/index.php\/2026\/10\/27\/mlx-gguf-lm-studio-mac-mini-m4-methode-comparaison\/","url":"https:\/\/remivernier.com\/index.php\/2026\/10\/27\/mlx-gguf-lm-studio-mac-mini-m4-methode-comparaison\/","name":"MLX ou GGUF dans LM Studio sur Mac mini M4: une m\u00e9thode de comparaison reproductible - R\u00e9mi VERNIER","isPartOf":{"@id":"https:\/\/remivernier.com\/#website"},"datePublished":"2026-10-27T13:59:58+00:00","dateModified":"2026-10-27T20:54:40+00:00","author":{"@id":"https:\/\/remivernier.com\/#\/schema\/person\/a4c76c63a5a3d94454484845e42fc906"},"description":"Comparer un m\u00eame mod\u00e8le en MLX et en GGUF dans LM Studio sur Mac mini M4: m\u00eames prompts, quantification proche, script de mesure, relev\u00e9 m\u00e9moire et limites.","breadcrumb":{"@id":"https:\/\/remivernier.com\/index.php\/2026\/10\/27\/mlx-gguf-lm-studio-mac-mini-m4-methode-comparaison\/#breadcrumb"},"inLanguage":"fr-FR","potentialAction":[{"@type":"ReadAction","target":["https:\/\/remivernier.com\/index.php\/2026\/10\/27\/mlx-gguf-lm-studio-mac-mini-m4-methode-comparaison\/"]}]},{"@type":"BreadcrumbList","@id":"https:\/\/remivernier.com\/index.php\/2026\/10\/27\/mlx-gguf-lm-studio-mac-mini-m4-methode-comparaison\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Accueil","item":"https:\/\/remivernier.com\/"},{"@type":"ListItem","position":2,"name":"MLX ou GGUF dans LM Studio sur Mac mini M4: une m\u00e9thode de comparaison reproductible"}]},{"@type":"WebSite","@id":"https:\/\/remivernier.com\/#website","url":"https:\/\/remivernier.com\/","name":"R\u00e9mi VERNIER","description":"Identit\u00e9, s\u00e9curit\u00e9, SAP et IA : Active Directory, Azure &amp; co...","potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/remivernier.com\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"fr-FR"},{"@type":"Person","@id":"https:\/\/remivernier.com\/#\/schema\/person\/a4c76c63a5a3d94454484845e42fc906","name":"R\u00e9mi","image":{"@type":"ImageObject","inLanguage":"fr-FR","@id":"https:\/\/secure.gravatar.com\/avatar\/f750448bc07f5d1d9c1189344283777ec0d33275c184599b199fb6b305bc6fba?s=96&d=mm&r=g","url":"https:\/\/secure.gravatar.com\/avatar\/f750448bc07f5d1d9c1189344283777ec0d33275c184599b199fb6b305bc6fba?s=96&d=mm&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/f750448bc07f5d1d9c1189344283777ec0d33275c184599b199fb6b305bc6fba?s=96&d=mm&r=g","caption":"R\u00e9mi"},"sameAs":["http:\/\/www.linkedin.com\/in\/remi-vernier"]}]}},"_links":{"self":[{"href":"https:\/\/remivernier.com\/index.php\/wp-json\/wp\/v2\/posts\/1240","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/remivernier.com\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/remivernier.com\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/remivernier.com\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/remivernier.com\/index.php\/wp-json\/wp\/v2\/comments?post=1240"}],"version-history":[{"count":2,"href":"https:\/\/remivernier.com\/index.php\/wp-json\/wp\/v2\/posts\/1240\/revisions"}],"predecessor-version":[{"id":1250,"href":"https:\/\/remivernier.com\/index.php\/wp-json\/wp\/v2\/posts\/1240\/revisions\/1250"}],"wp:attachment":[{"href":"https:\/\/remivernier.com\/index.php\/wp-json\/wp\/v2\/media?parent=1240"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/remivernier.com\/index.php\/wp-json\/wp\/v2\/categories?post=1240"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/remivernier.com\/index.php\/wp-json\/wp\/v2\/tags?post=1240"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}