C'est quoi MLX
MLX est le framework de machine learning d'Apple, open source et conçu spécifiquement pour les puces Apple Silicon. Son module mlx-lm couvre les deux besoins qui nous intéressent ici : la génération de texte et le fine-tuning.
Ce qui rend ces puces intéressantes pour l'entraînement, c'est leur mémoire unifiée : CPU et GPU partagent la même mémoire, donc le GPU peut accéder à toute la RAM disponible. Elle reste partagée avec macOS et les autres processus, d'où l'importance de choisir un modèle raisonnable quand on n'a que 8 Go.
Installation
Un projet uv et une dépendance :
uv init fine-tuning
cd fine-tuning/
uv add mlx-lm
Le choix du modèle
Sur une petite configuration, le modèle doit cocher plusieurs cases. mlx-community/Ministral-3-3B-Instruct-2512-4bit les remplit toutes : 3 milliards de paramètres seulement, version « Instruct » déjà adaptée au dialogue, quantizé en 4-bit pour réduire l'empreinte mémoire, et publié sur l'organisation mlx-community de Hugging Face, donc directement au format MLX.
Un test rapide avant d'aller plus loin :
uv run mlx_lm.generate \
--model mlx-community/Ministral-3-3B-Instruct-2512-4bit \
--prompt "Bonjour, qui es-tu ?" \
--max-tokens 100
Construire le dataset
Le format attendu est le JSONL : un objet JSON par ligne, chaque ligne contenant un échange question/réponse.
{"messages": [{"role": "user", "content": "LA QUESTION"}, {"role": "assistant", "content": "LA RÉPONSE"}]}
Pour apprendre un style au modèle, il faut des exemples qui portent ce style. Environ 200 paires suffisent pour un objectif de ce genre, et un LLM plus gros peut les générer : ici, des réponses en « parler chevaleresque », truffées de formules variées (« point ne », « céans », « moult », « gente dame », « messire », « par ma foi »). Le choix d'un style inventé évite au passage tout contenu protégé dans le dataset.
Découper le dataset
mlx_lm attend deux fichiers : train.jsonl pour l'entraînement et valid.jsonl pour mesurer la progression sur des exemples jamais vus. Un script fait le découpage, 170 exemples d'un côté, 30 de l'autre :
import json
import random
random.seed(117)
with open("brut.jsonl", encoding="utf-8") as f:
exemples = [json.loads(ligne) for ligne in f if ligne.strip()]
random.shuffle(exemples)
for chemin, sous_ensemble in {
"data/train.jsonl": exemples[:-30],
"data/valid.jsonl": exemples[-30:],
}.items():
with open(chemin, "w", encoding="utf-8") as f:
for exemple in sous_ensemble:
f.write(json.dumps(exemple, ensure_ascii=False) + "\n")
print(f"{chemin} : {len(sous_ensemble)} exemples")
uv run python decouper.py
L'entraînement
mlx_lm.lora fait un fine-tuning LoRA : au lieu de réentraîner tous les poids du modèle, il n'ajuste que de petites matrices ajoutées sur certaines couches, ce qui divise drastiquement la mémoire et le temps nécessaires.
Un premier passage court permet de vérifier que tout tient en mémoire avant de lancer le vrai entraînement :
uv run mlx_lm.lora \
--model mlx-community/Ministral-3-3B-Instruct-2512-4bit \
--train \
--data ./data \
--batch-size 1 \
--num-layers 4 \
--iters 30
Avec ces réglages, le pic mémoire reste à 3,4 Go et la loss de validation descend de 2,882 à 0,371. Trente itérations ne suffisent cependant pas à imprimer le style : les réponses raccourcissent, sans tournures chevaleresques.
L'entraînement complet monte à 300 itérations sur 8 couches :
time uv run mlx_lm.lora \
--model mlx-community/Ministral-3-3B-Instruct-2512-4bit \
--train \
--data ./data \
--batch-size 1 \
--num-layers 8 \
--iters 300
Comptez environ 35 minutes sur un M1 de 2020. La loss de validation finit à 0,306, avec un pic mémoire qui reste raisonnable pour 8 Go.
Le résultat
Sur une même question (« Comment relancer un client ? »), le modèle original répond en français standard, de façon structurée et générique. Le modèle fine-tuné répond en tenue de tournoi (« Oyez, parlez de relance, gardez les propos en deux ou trois messages... ») tout en gardant le fond utile de la réponse. Le style a été absorbé sans écraser la capacité à donner des conseils pratiques.
Mon avis
Ce qui m'a le plus surpris, c'est le rapport entre l'effort et le résultat : 200 exemples, 35 minutes et une machine de 2020 suffisent pour changer durablement le comportement d'un modèle. La contrepartie est claire : cette approche fonctionne pour un style ou un ton, ce qui est très différent d'apprendre des connaissances nouvelles au modèle. Pour brancher une IA sur ses propres documents, le RAG reste la bonne approche, et le fine-tuning sert quand on veut changer la manière dont le modèle s'exprime.
MLX rend l'exercice assez simple pour qu'il vaille la peine d'être tenté au moins une fois : la boucle complète tient en trois commandes et un script de découpage. Si vous avez un Mac Apple Silicon, même modeste, c'est la voie la plus directe pour comprendre ce que le fine-tuning fait vraiment.