Pourquoi un SLM et pas un LLM
Il y a une confusion persistante dans l’industrie francaise en 2026. Quand un DSI entend “intelligence artificielle generative”, il pense GPT-4, Claude, Gemini — des modeles massifs, heberges aux Etats-Unis, factures au token, et qui necessitent d’envoyer les donnees de production dans le cloud d’un tiers. Il pense aussi que c’est la seule option.
Ce n’est pas la seule option.
Un Small Language Model (SLM) — entre 0,5 et 8 milliards de parametres — peut resoudre 80 % des cas d’usage textuels d’une ETI industrielle, a un cout 50 a 100 fois inferieur, avec une latence 10 fois plus faible, et sans qu’une seule donnee ne quitte le reseau de l’entreprise.
La nuance est dans le mot “resoudre”. Un LLM generaliste sait tout, mais ne sait rien de votre processus. Un SLM fine-tune ne sait qu’une chose — votre processus — et il la fait mieux que le generaliste. C’est la difference entre un couteau suisse et un outil de coupe specifique. En atelier, on utilise l’outil specifique.
Trois cas d’usage ou le SLM ecrase le LLM
Classification de tickets SAV. Un operateur saisit un texte libre (“la piece se decolle apres 2 jours, le client est furieux”). Le modele doit classer le ticket dans une categorie (defaut adhesion, defaut dimensionnel, defaut esthetique, reclamation delai) et lui attribuer une priorite. Un Qwen2.5-1.5B fine-tune sur 2 000 tickets historiques atteint 94 % de precision, repond en 80 ms sur CPU, et coute 0 euro par requete une fois deploye.
Extraction de nomenclature. Un rapport technique de fournisseur contient des references pieces, des quantites, des specifications. Le modele doit extraire ces champs dans un JSON structure. Un Gemma-2-2B fine-tune sur 500 exemples annotes fait le travail avec 96 % d’extraction correcte. Le meme exercice avec GPT-4o coute 0,03 euro par page et envoie les specs fournisseur chez OpenAI.
Resume de rapports qualite. Un audit interne de 30 pages doit etre synthetise en 5 points cles pour le comite de direction. Un Mistral-7B fine-tune produit des resumes factuels, calibres sur le format interne, en 3 secondes. Le cout marginal est le prix de l’electricite.
Le calcul economique
| LLM cloud (GPT-4o) | SLM on-premise (Qwen 1.5B) | |
|---|---|---|
| Cout par requete | 0,01 — 0,05 EUR | ~0 EUR (electricite) |
| 10 000 requetes/mois | 100 — 500 EUR/mois | < 5 EUR/mois |
| Latence | 1 — 5 s | 50 — 200 ms |
| Donnees | Cloud US | On-premise |
| Personnalisation | Prompt engineering | Fine-tuning complet |
Sur un an, la difference de cout entre un LLM cloud et un SLM on-premise finance largement le fine-tuning initial. Le point mort est atteint des le deuxieme mois pour un cas d’usage a volume.
Etape 1 — Choisir le modele de base
Le choix du modele de base conditionne tout le reste : le GPU necessaire, le volume de donnees requis, la latence en inference, et la qualite finale. Le critere principal est la taille du modele en parametres. Le critere secondaire est la licence.
Arbre de decision
%%{init: {'theme': 'base', 'themeVariables': {'primaryColor': '#1a1a2e', 'primaryTextColor': '#ffffff', 'primaryBorderColor': '#e94560', 'lineColor': '#e94560', 'secondaryColor': '#16213e', 'tertiaryColor': '#0f3460', 'fontSize': '14px'}}}%%
flowchart TD
A["Quel est votre cas d'usage ?"] --> B{"Tache simple ?\n(classification, extraction,\nNER, yes/no)"}
B -->|Oui| C{"Volume de donnees\nd'entrainement"}
B -->|Non| D{"Tache complexe ?\n(resume, generation,\nraisonnement)"}
C -->|"< 1 000 ex."| E["**Qwen2.5-0.5B**\n0,5B params\nRTX 4060 suffit\n2 Go VRAM"]
C -->|"> 1 000 ex."| F["**Phi-3-mini**\n3,8B params\nRTX 4090\n8 Go VRAM"]
D -->|"Resume, reformulation"| G{"Contrainte\nde latence"}
D -->|"Raisonnement,\nanalyse multi-doc"| H["**LLaMA-3.1-8B**\n8B params\nA100 40GB\n18 Go VRAM"]
G -->|"< 200 ms"| I["**Gemma-2-2B**\n2B params\nRTX 4090\n6 Go VRAM"]
G -->|"Latence flexible"| J["**Mistral-7B-v0.3**\n7B params\nA100 40GB\n16 Go VRAM"]
style A fill:#1a1a2e,stroke:#e94560,color:#ffffff
style B fill:#16213e,stroke:#e94560,color:#ffffff
style C fill:#16213e,stroke:#e94560,color:#ffffff
style D fill:#16213e,stroke:#e94560,color:#ffffff
style E fill:#0f3460,stroke:#e94560,color:#ffffff
style F fill:#0f3460,stroke:#e94560,color:#ffffff
style G fill:#16213e,stroke:#e94560,color:#ffffff
style H fill:#0f3460,stroke:#e94560,color:#ffffff
style I fill:#0f3460,stroke:#e94560,color:#ffffff
style J fill:#0f3460,stroke:#e94560,color:#ffffff
Tableau comparatif des modeles de base
| Modele | Params | VRAM min | Licence | Forces | Faiblesses |
|---|---|---|---|---|---|
| Qwen2.5-0.5B | 0,5B | 2 Go | Apache 2.0 | Ultra-leger, multilingue, rapide | Limites en generation longue |
| Phi-3-mini | 3,8B | 8 Go | MIT | Excellent raisonnement pour sa taille | Anglais-centrique |
| Gemma-2-2B | 2B | 6 Go | Gemma License | Bon equilibre taille/qualite | Licence restrictive pour usage commercial > 1M users |
| Qwen2.5-1.5B | 1,5B | 4 Go | Apache 2.0 | Multilingue, bon en francais | Moins de communaute que LLaMA |
| Mistral-7B-v0.3 | 7B | 16 Go | Apache 2.0 | Reference francophone, communaute active | Necessite A100 pour fine-tuning |
| LLaMA-3.1-8B | 8B | 18 Go | Llama 3.1 License | Meilleur modele open-weight 8B | Licence Meta, pas de GPU grand public |
Regle de pouce. Si votre tache peut etre formulee comme une classification ou une extraction de champs, commencez par Qwen2.5-0.5B. Si elle implique de la generation de texte (resume, reformulation, redaction), montez a 2-3B. N’allez a 7-8B que si les tests sur un modele plus petit montrent une qualite insuffisante. Plus petit = plus rapide, moins cher, plus facile a deployer.
Etape 2 — Preparer les donnees
Le fine-tuning est un exercice de donnees, pas de code. La qualite du modele final est directement proportionnelle a la qualite du jeu de donnees. Le code d’entrainement est generique — il est le meme pour tout le monde. Les donnees sont specifiques — elles sont votre avantage competitif.
Format JSONL
Le standard pour le fine-tuning supervisee est le format JSONL (JSON Lines). Chaque ligne est un objet JSON autonome avec trois champs.
{"instruction": "Classifie ce ticket SAV dans une categorie.", "input": "La peinture se decolle apres 3 semaines d'utilisation en exterieur. Piece ref. PNT-4200.", "output": "Categorie: defaut adhesion\nPriorite: haute\nCode defaut: ADH-EXT\nAction: retour fournisseur peinture"}
{"instruction": "Classifie ce ticket SAV dans une categorie.", "input": "Cote longueur 145.2 mm au lieu de 145.0 +/- 0.05. Lot 2026-W12.", "output": "Categorie: defaut dimensionnel\nPriorite: critique\nCode defaut: DIM-HT\nAction: blocage lot, reglage machine"}
{"instruction": "Classifie ce ticket SAV dans une categorie.", "input": "Le client demande ou en est sa commande du 15 mars.", "output": "Categorie: reclamation delai\nPriorite: normale\nCode defaut: N/A\nAction: transfert service commercial"}
Structure des donnees
Chaque exemple doit contenir :
- instruction : la consigne donnee au modele. Identique ou tres similaire pour tous les exemples d’une meme tache.
- input : le texte d’entree. Variable, representatif de la realite terrain. Inclut le bruit, les fautes, les abbreviations que les operateurs utilisent reellement.
- output : la reponse attendue. C’est le label. Il doit etre exact, complet, et coherent avec la politique interne.
Combien de donnees ?
L’ordre de grandeur depend de la complexite de la tache.
| Tache | Minimum viable | Ideal | Maximum utile |
|---|---|---|---|
| Classification binaire (oui/non) | 200 | 500 | 2 000 |
| Classification multi-classes (5-10 classes) | 500 | 2 000 | 10 000 |
| Extraction de champs (NER) | 300 | 1 000 | 5 000 |
| Resume / reformulation | 500 | 2 000 | 10 000 |
| Generation guidee (rapports) | 1 000 | 5 000 | 20 000 |
Au-dela du maximum utile, ajouter des donnees n’ameliore plus la performance. En deca du minimum viable, le modele n’apprend pas le pattern de maniere fiable.
Nettoyage
Avant de lancer un entrainement, verifier systematiquement :
- Doublons exacts. Deux exemples identiques biaisent l’evaluation si l’un est dans le train et l’autre dans le val.
- Incoherences. Deux exemples avec le meme input mais des outputs differents. Le modele ne peut pas apprendre de contradictions.
- Longueur. Un output de 2 000 tokens au milieu d’outputs de 50 tokens va perturber l’entrainement. Homogeneiser.
- Encodage. UTF-8 partout. Les caracteres speciaux (accents, unites, symboles) doivent etre presents dans les donnees si on veut que le modele les gere.
Split train / validation
import json
import random
with open("dataset.jsonl") as f:
data = [json.loads(line) for line in f]
random.seed(42)
random.shuffle(data)
split = int(0.9 * len(data))
train = data[:split]
val = data[split:]
with open("train.jsonl", "w") as f:
for item in train:
f.write(json.dumps(item, ensure_ascii=False) + "\n")
with open("val.jsonl", "w") as f:
for item in val:
f.write(json.dumps(item, ensure_ascii=False) + "\n")
print(f"Train: {len(train)} exemples | Val: {len(val)} exemples")
Le ratio 90/10 est standard. Pour de petits jeux de donnees (< 500 exemples), un 80/20 donne une meilleure estimation de la performance reelle.
Etape 3 — Provisionner le GPU
Le fine-tuning d’un SLM necessite un GPU avec suffisamment de VRAM. Pas besoin d’acheter du materiel : les plateformes cloud GPU a la demande font le travail.
RunPod
RunPod est la plateforme que nous utilisons pour les entrainements en production. Le modele economique est simple : on loue un GPU a l’heure, on lance l’entrainement, on recupere les poids, on eteint.
Alternative : Lambda Labs propose des H100 et A100 a des tarifs competitifs, avec un accent sur les workloads d’entrainement lourds (> 8 heures). Leur offre “Lambda Cloud” inclut un stockage persistant gratuit de 200 Go, ce qui evite les transferts de donnees repetitifs entre sessions.
Choix du GPU
| Modele a fine-tuner | GPU recommande | VRAM | Cout RunPod (spot) | Cout RunPod (on-demand) |
|---|---|---|---|---|
| Qwen2.5-0.5B | RTX 4060 Ti | 16 Go | ~0,20 EUR/h | ~0,35 EUR/h |
| Gemma-2-2B / Qwen2.5-1.5B | RTX 4090 | 24 Go | ~0,40 EUR/h | ~0,75 EUR/h |
| Mistral-7B / LLaMA-3.1-8B | A100 40GB | 40 Go | ~1,00 EUR/h | ~1,65 EUR/h |
| LLaMA-3.1-8B (batch large) | A100 80GB | 80 Go | ~1,60 EUR/h | ~2,40 EUR/h |
Spot vs on-demand. Les instances spot coutent 40 a 60 % moins cher mais peuvent etre interrompues. Pour un entrainement de 2-3 heures, le risque est acceptable. Pour un entrainement de 12 heures, prenez du on-demand et activez les checkpoints.
Connexion SSH
Une fois le pod provisionne sur RunPod, la connexion se fait en SSH.
# Recuperer l'adresse dans le dashboard RunPod
ssh root@{POD_IP} -p {PORT} -i ~/.ssh/id_runpod
# Verifier le GPU
nvidia-smi
Vous devez voir votre GPU avec la VRAM attendue. Si nvidia-smi retourne une erreur, le pod est mal provisionne — detruisez-le et recreez-en un.
Transfert des donnees
# Depuis votre machine locale vers le pod RunPod
scp -P {PORT} train.jsonl val.jsonl root@{POD_IP}:/workspace/data/
Etape 4 — Setup de l’environnement
Sur le pod RunPod, l’environnement Python doit etre installe avec les bonnes dependances. Chaque librairie a un role precis.
# Creer un environnement propre (optionnel si le pod a deja Python 3.10+)
pip install --upgrade pip
# Installer le stack de fine-tuning
pip install torch transformers peft bitsandbytes wandb trl datasets accelerate
# Verifier que CUDA est disponible
python3 -c "import torch; print(f'CUDA: {torch.cuda.is_available()}, GPU: {torch.cuda.get_device_name(0)}')"
| Librairie | Role |
|---|---|
torch | Backend de calcul GPU (PyTorch) |
transformers | Chargement du modele et du tokenizer (Hugging Face) |
peft | LoRA, QLoRA, adapters — fine-tuning efficient |
bitsandbytes | Quantization 4-bit/8-bit pour reduire la VRAM |
wandb | Monitoring d’entrainement (Weights & Biases) |
trl | SFTTrainer — simplifie le fine-tuning supervisee |
datasets | Chargement et preprocessing des donnees |
accelerate | Gestion multi-GPU et mixed precision |
Login Weights & Biases
wandb login
# Coller la cle API depuis https://wandb.ai/authorize
W&B est gratuit pour les projets individuels et les equipes jusqu’a 100 Go de logs. L’alternative open-source est MLflow, mais W&B offre une meilleure experience pour le suivi d’entrainements LLM.
Verification du modele
from transformers import AutoModelForCausalLM, AutoTokenizer
model_name = "Qwen/Qwen2.5-1.5B" # Adapter selon votre choix
tokenizer = AutoTokenizer.from_pretrained(model_name, trust_remote_code=True)
model = AutoModelForCausalLM.from_pretrained(
model_name,
torch_dtype="auto",
device_map="auto",
trust_remote_code=True
)
print(f"Modele charge: {model_name}")
print(f"Parametres: {model.num_parameters():,}")
print(f"VRAM utilisee: {torch.cuda.memory_allocated() / 1e9:.1f} Go")
Si le modele se charge sans erreur et que la VRAM utilisee est inferieure a 60 % de la VRAM totale, vous avez de la marge pour l’entrainement. Si elle depasse 70 %, activez la quantization 4-bit (voir la section LoRA ci-dessous).
Etape 5 — Fine-tuning avec LoRA
LoRA (Low-Rank Adaptation) est la technique qui rend le fine-tuning de SLM accessible. Au lieu de re-entrainer tous les parametres du modele (ce qui necessiterait 4 a 10 fois plus de VRAM), LoRA n’entraine qu’un petit nombre d’adapteurs — typiquement 0,5 a 2 % des parametres totaux. Le modele original reste fige. Seuls les adapteurs sont appris.
LoRA reduit la VRAM necessaire de 60 a 80 %, le temps d’entrainement de 50 %, et le stockage du modele fine-tune a quelques centaines de Mo au lieu de plusieurs Go.
Le script complet
Voici le script d’entrainement complet. Il est fonctionnel tel quel — copiez-le, adaptez les chemins et le nom du modele, et lancez-le.
import torch
from datasets import load_dataset
from transformers import (
AutoModelForCausalLM,
AutoTokenizer,
TrainingArguments,
)
from peft import LoraConfig, get_peft_model, TaskType
from trl import SFTTrainer
# ── Configuration ──────────────────────────────────────────────
MODEL_NAME = "Qwen/Qwen2.5-1.5B"
OUTPUT_DIR = "./output-slm-industrie"
TRAIN_FILE = "./data/train.jsonl"
VAL_FILE = "./data/val.jsonl"
WANDB_PROJECT = "slm-industrie"
# ── Chargement modele + tokenizer ──────────────────────────────
tokenizer = AutoTokenizer.from_pretrained(MODEL_NAME, trust_remote_code=True)
if tokenizer.pad_token is None:
tokenizer.pad_token = tokenizer.eos_token
model = AutoModelForCausalLM.from_pretrained(
MODEL_NAME,
torch_dtype=torch.bfloat16,
device_map="auto",
trust_remote_code=True,
)
# ── Configuration LoRA ─────────────────────────────────────────
lora_config = LoraConfig(
task_type=TaskType.CAUSAL_LM,
r=16, # Rang de la decomposition (8-64)
lora_alpha=32, # Facteur de scaling (2x le rang)
lora_dropout=0.05, # Regularisation legere
target_modules=[ # Couches a adapter
"q_proj", "k_proj", "v_proj", "o_proj",
"gate_proj", "up_proj", "down_proj"
],
bias="none",
)
model = get_peft_model(model, lora_config)
model.print_trainable_parameters()
# >>> trainable params: 13,631,488 || all params: 1,543,714,816 || 0.88%
# ── Chargement des donnees ─────────────────────────────────────
dataset_train = load_dataset("json", data_files=TRAIN_FILE, split="train")
dataset_val = load_dataset("json", data_files=VAL_FILE, split="train")
def format_prompt(example):
"""Formatte un exemple en prompt d'entrainement."""
text = f"### Instruction:\n{example['instruction']}\n\n"
if example.get("input"):
text += f"### Input:\n{example['input']}\n\n"
text += f"### Output:\n{example['output']}"
return {"text": text}
dataset_train = dataset_train.map(format_prompt)
dataset_val = dataset_val.map(format_prompt)
# ── Arguments d'entrainement ───────────────────────────────────
training_args = TrainingArguments(
output_dir=OUTPUT_DIR,
num_train_epochs=3,
per_device_train_batch_size=4,
per_device_eval_batch_size=4,
gradient_accumulation_steps=4, # Batch effectif = 4 x 4 = 16
learning_rate=2e-4,
lr_scheduler_type="cosine",
warmup_ratio=0.05,
bf16=True,
logging_steps=10,
eval_strategy="steps",
eval_steps=50,
save_strategy="steps",
save_steps=100,
save_total_limit=3,
load_best_model_at_end=True,
metric_for_best_model="eval_loss",
greater_is_better=False,
report_to="wandb",
run_name="slm-industrie-lora-r16",
dataloader_pin_memory=True,
gradient_checkpointing=True, # Economise 30-40% de VRAM
)
# ── Lancement de l'entrainement ────────────────────────────────
trainer = SFTTrainer(
model=model,
args=training_args,
train_dataset=dataset_train,
eval_dataset=dataset_val,
processing_class=tokenizer,
dataset_text_field="text",
max_seq_length=1024,
packing=True, # Pack les exemples courts ensemble
)
trainer.train()
# ── Sauvegarde ─────────────────────────────────────────────────
trainer.save_model(OUTPUT_DIR)
tokenizer.save_pretrained(OUTPUT_DIR)
print(f"Modele sauvegarde dans {OUTPUT_DIR}")
Explication des hyperparametres cles
r=16 — Le rang de la decomposition LoRA. Plus r est grand, plus le modele a de capacite d’apprentissage, mais plus il consomme de VRAM et risque l’overfitting. Pour la plupart des taches industrielles, r=16 est le bon compromis. Si vous avez peu de donnees (< 500 exemples), descendez a r=8. Si votre tache est complexe et que vous avez beaucoup de donnees, montez a r=32.
lora_alpha=32 — Le facteur de scaling. La regle de pouce est de le fixer a 2 fois le rang. alpha/r donne le facteur de mise a l’echelle effectif des adapteurs.
learning_rate=2e-4 — Le taux d’apprentissage. Pour LoRA, 1e-4 a 3e-4 fonctionne dans la grande majorite des cas. Si le modele diverge (loss qui explose), divisez par 2. Si le modele converge trop lentement, multipliez par 2.
gradient_checkpointing=True — Echange du temps de calcul contre de la VRAM. Ralentit l’entrainement de 20 % mais reduit la consommation VRAM de 30 a 40 %. Indispensable pour fine-tuner un 7B sur un A100 40GB.
packing=True — Concatene les exemples courts pour remplir la fenetre de contexte (max_seq_length). Accelere l’entrainement de 30 a 50 % quand les exemples sont courts (< 200 tokens).
Lancement
# Depuis le pod RunPod
python3 train.py
# Ou en arriere-plan avec logs
nohup python3 train.py > training.log 2>&1 &
tail -f training.log
L’entrainement typique dure :
| Modele | Dataset | GPU | Duree |
|---|---|---|---|
| Qwen2.5-0.5B | 2 000 exemples, 3 epochs | RTX 4090 | ~15 min |
| Gemma-2-2B | 2 000 exemples, 3 epochs | RTX 4090 | ~45 min |
| Qwen2.5-1.5B | 5 000 exemples, 3 epochs | RTX 4090 | ~1h30 |
| Mistral-7B | 5 000 exemples, 3 epochs | A100 40GB | ~2h30 |
| LLaMA-3.1-8B | 5 000 exemples, 3 epochs | A100 40GB | ~3h00 |
Etape 6 — Monitoring avec Weights & Biases
Lancer un entrainement sans monitoring, c’est comme lancer une production sans SPC. On ne sait pas si ca marche, on ne sait pas quand ca derive, et on ne sait pas quand s’arreter.
Ce que vous voyez sur le dashboard W&B
Une fois l’entrainement lance avec report_to="wandb", le dashboard Weights & Biases se met a jour en temps reel. Voici ce que vous devez surveiller.
Courbe de loss (train/loss). C’est l’indicateur principal. La loss doit decroitre de maniere reguliere pendant les premieres centaines de steps, puis se stabiliser. Une loss qui remonte indique un overfitting ou un learning rate trop eleve.
Courbe de validation (eval/loss). C’est la metrique qui compte vraiment. La loss de validation doit suivre la loss d’entrainement avec un leger ecart. Si la loss de validation remonte alors que la loss d’entrainement continue de descendre, le modele overfitte — il memorise les donnees d’entrainement au lieu d’apprendre le pattern. C’est le signal d’arret.
Learning rate. Avec un scheduler cosine, le learning rate doit former une courbe en cloche inversee : montee rapide pendant le warmup (5 % des steps), puis descente progressive. Si la courbe ne ressemble pas a ca, verifier la configuration du scheduler.
Signaux d’alerte
| Signal | Diagnostic | Action |
|---|---|---|
| Loss qui ne descend pas | Learning rate trop faible ou donnees incoherentes | Multiplier lr par 2, ou verifier les donnees |
| Loss qui explose (NaN) | Learning rate trop eleve ou probleme numerique | Diviser lr par 5, verifier bf16 vs fp16 |
| eval_loss remonte apres step N | Overfitting a partir de step N | Reprendre le checkpoint du step N |
| Loss tres basse (< 0.1) trop vite | Le modele a memorise le train set | Reduire le nombre d’epochs, augmenter le dropout |
| train_loss et eval_loss ne descendent plus | Le modele a converge | Arreter l’entrainement |
Early stopping
Le parametre load_best_model_at_end=True dans les TrainingArguments fait deja une forme d’early stopping : a la fin de l’entrainement, le meilleur checkpoint (selon eval_loss) est charge. Pour un early stopping strict (arret des que eval_loss remonte pendant N evaluations), ajouter un callback :
from transformers import EarlyStoppingCallback
trainer = SFTTrainer(
# ... meme config que precedemment ...
callbacks=[EarlyStoppingCallback(early_stopping_patience=3)],
)
Avec early_stopping_patience=3, l’entrainement s’arrete si la eval_loss n’ameliore pas pendant 3 evaluations consecutives.
Etape 7 — Quantization GGUF pour deploiement
L’entrainement est termine. Vous avez un modele fine-tune en bfloat16 qui pese plusieurs Go et qui necessite un GPU pour l’inference. Pour deployer en production — surtout sur CPU, sur un serveur classique, ou sur un poste de travail — il faut quantizer le modele.
La quantization reduit la taille du modele de 60 a 75 % et permet l’inference sur CPU, avec une perte de qualite typiquement inferieure a 2 %.
Fusionner les adapteurs LoRA
Avant de quantizer, il faut fusionner les adapteurs LoRA avec le modele de base pour obtenir un modele standalone.
from peft import PeftModel
from transformers import AutoModelForCausalLM, AutoTokenizer
# Charger le modele de base
base_model = AutoModelForCausalLM.from_pretrained(
"Qwen/Qwen2.5-1.5B",
torch_dtype="auto",
device_map="auto",
trust_remote_code=True,
)
# Charger et fusionner les adapteurs LoRA
model = PeftModel.from_pretrained(base_model, "./output-slm-industrie")
merged_model = model.merge_and_unload()
# Sauvegarder le modele fusionne
merged_model.save_pretrained("./merged-model")
AutoTokenizer.from_pretrained("./output-slm-industrie").save_pretrained("./merged-model")
print("Modele fusionne sauvegarde dans ./merged-model")
Conversion GGUF
Le format GGUF est le standard pour l’inference CPU via llama.cpp et Ollama. La conversion utilise le script officiel du projet llama.cpp.
# Cloner llama.cpp (si pas deja fait)
git clone https://github.com/ggerganov/llama.cpp.git
cd llama.cpp
pip install -r requirements.txt
# Convertir en GGUF quantize Q4_K_M (meilleur rapport taille/qualite)
python3 convert_hf_to_gguf.py ../merged-model --outtype q4_K_M --outfile ../slm-industrie-q4km.gguf
Niveaux de quantization
| Format | Taille (1.5B) | Taille (7B) | Qualite | Usage |
|---|---|---|---|---|
| BF16 (original) | 3,0 Go | 14 Go | 100 % | GPU uniquement |
| Q8_0 | 1,6 Go | 7,7 Go | ~99 % | Serveur avec RAM > 16 Go |
| Q5_K_M | 1,1 Go | 5,3 Go | ~97 % | Serveur standard |
| Q4_K_M | 0,9 Go | 4,4 Go | ~95 % | Recommande pour production |
| Q3_K_M | 0,7 Go | 3,5 Go | ~90 % | Edge / IoT |
| Q2_K | 0,5 Go | 2,7 Go | ~80 % | Experimental |
Q4_K_M est le sweet spot. Il offre 95 % de la qualite originale pour 30 % de la taille. C’est le format que nous deploiements en production chez nos clients industriels.
Etape 8 — Deploiement local avec Ollama
Ollama est le runtime le plus simple pour deployer un SLM en production. Il transforme un fichier GGUF en serveur d’inference avec une API REST compatible OpenAI. Installation en une commande, demarrage en une commande.
Installation d’Ollama
# Linux
curl -fsSL https://ollama.com/install.sh | sh
# Verifier l'installation
ollama --version
Creer le Modelfile
Le Modelfile est la recette qui indique a Ollama comment charger et configurer votre modele.
# Modelfile
FROM ./slm-industrie-q4km.gguf
PARAMETER temperature 0.1
PARAMETER top_p 0.9
PARAMETER num_ctx 2048
SYSTEM """Tu es un assistant qualite industriel specialise dans la classification de tickets SAV et l'extraction de donnees techniques. Tu reponds en francais, de maniere structuree et factuelle. Tu ne fais pas de supposition quand les donnees sont insuffisantes."""
Import et test
# Creer le modele dans Ollama
ollama create slm-industrie -f Modelfile
# Tester en interactif
ollama run slm-industrie "Classifie ce ticket : La vis M6x20 inox casse au serrage a 8 Nm. Lot W14."
# Verifier que le modele est disponible
ollama list
Integration API
Une fois le modele charge dans Ollama, il est accessible via une API REST sur le port 11434. Toute application interne peut l’appeler.
import requests
def classify_ticket(ticket_text: str) -> dict:
"""Appelle le SLM pour classifier un ticket SAV."""
response = requests.post(
"http://localhost:11434/api/generate",
json={
"model": "slm-industrie",
"prompt": f"Classifie ce ticket SAV dans une categorie.\n\nTicket: {ticket_text}",
"stream": False,
"options": {"temperature": 0.1}
}
)
return response.json()
# Exemple d'utilisation
result = classify_ticket("Bavure sur piece injectee ref. PLT-300, moule cavite 3")
print(result["response"])
L’API est compatible avec le format OpenAI (/v1/chat/completions), ce qui permet de remplacer un appel GPT-4 par un appel local en changeant uniquement l’URL de base.
from openai import OpenAI
# Remplacer l'appel cloud par l'appel local
client = OpenAI(base_url="http://localhost:11434/v1", api_key="unused")
response = client.chat.completions.create(
model="slm-industrie",
messages=[
{"role": "system", "content": "Tu es un assistant qualite industriel."},
{"role": "user", "content": "Classifie ce ticket : surface rayee sur piece polie ref. MIR-200"}
],
temperature=0.1
)
print(response.choices[0].message.content)
Etape 9 — Couts reels, de bout en bout
Voici le cout reel d’un fine-tuning SLM, de la preparation des donnees au deploiement. Ces chiffres sont bases sur nos propres entrainements chez BCUB3, pas sur des estimations theoriques.
Cout d’entrainement
| Modele | GPU | Duree entrainement | Cout GPU (spot) | Cout GPU (on-demand) |
|---|---|---|---|---|
| Qwen2.5-0.5B | RTX 4090 | 15 min | 0,10 EUR | 0,19 EUR |
| Qwen2.5-1.5B | RTX 4090 | 1h30 | 0,60 EUR | 1,13 EUR |
| Gemma-2-2B | RTX 4090 | 45 min | 0,30 EUR | 0,56 EUR |
| Mistral-7B | A100 40GB | 2h30 | 2,50 EUR | 4,13 EUR |
| LLaMA-3.1-8B | A100 40GB | 3h00 | 3,00 EUR | 4,95 EUR |
Cout total du projet (premiere iteration)
| Poste | Cout | Temps |
|---|---|---|
| Preparation des donnees (annotation) | 0 EUR (equipe interne) | 2-5 jours |
| GPU pour entrainement (3 runs d’iteration) | 1 — 15 EUR | 1-10 heures |
| Weights & Biases (plan gratuit) | 0 EUR | — |
| Serveur de deploiement (existant ou VPS) | 20 — 50 EUR/mois | — |
| Temps ingenieur (setup + iteration) | 2-3 jours | — |
| Total premiere iteration | < 100 EUR | 1 semaine |
Le cout total d’un fine-tuning SLM est inferieur au cout d’un mois d’abonnement a un LLM cloud pour un usage a volume equivalent.
Cout d’exploitation
| Poste | SLM on-premise | LLM cloud |
|---|---|---|
| Serveur (VPS 8 vCPU, 32 Go RAM) | 50 EUR/mois | — |
| API LLM | — | 200-2 000 EUR/mois |
| Maintenance | 2h/mois | 0 |
| Total annuel | ~700 EUR | 2 400-24 000 EUR |
Etape 10 — Checklist de sortie avant mise en production
Avant de deployer un SLM fine-tune en production, ces huit points doivent etre valides. Pas de raccourci.
1. Eval loss stable et coherente
La loss de validation a la fin de l’entrainement doit etre stable (pas de tendance a la hausse sur les derniers checkpoints). Verifier sur le dashboard W&B que la courbe eval_loss est plate ou en legere descente sur les 20 % finaux des steps.
2. Test sur des exemples hors-dataset
Tester le modele sur 20 a 50 exemples qui n’etaient ni dans le train set ni dans le val set. Ces exemples doivent etre representatifs de la production reelle — y compris les cas limites, les fautes de frappe, les inputs mal formates. Mesurer la precision sur ces exemples. Si elle est inferieure de plus de 5 points a la precision sur le val set, le modele overfitte.
3. Test de regression
Verifier que le modele fine-tune n’a pas perdu ses capacites de base. Un SLM fine-tune pour la classification de tickets doit encore etre capable de produire du texte coherent en francais. Tester avec 5 prompts generiques. Si le modele produit du charabia, le fine-tuning a ete trop agressif (learning rate trop eleve ou trop d’epochs).
4. Latence mesuree
Mesurer la latence d’inference sur le hardware de production (pas sur le GPU d’entrainement). Un SLM quantize Q4_K_M sur un CPU moderne doit repondre en moins de 500 ms pour un output de 100 tokens. Si la latence depasse les specifications, envisager un modele plus petit ou un format de quantization plus agressif.
# Mesurer la latence avec Ollama
time ollama run slm-industrie "Classifie : piece rayee ref. XYZ-100" --verbose
5. Consommation memoire validee
Verifier que le modele charge ne depasse pas 70 % de la RAM disponible sur le serveur de production. Laisser de la marge pour le systeme d’exploitation et les autres services.
# Verifier la RAM utilisee par Ollama
ollama ps
6. API fonctionnelle
Tester l’appel API depuis l’application cliente (ERP, GMAO, ou script Python). Verifier le format de la reponse, les codes HTTP, et le comportement en cas d’erreur (modele non charge, input vide, input trop long).
7. Monitoring en place
Mettre en place un log des requetes et des reponses du modele. Pas pour la surveillance des utilisateurs, mais pour detecter le drift : si la distribution des inputs change (nouveaux types de defauts, nouveau vocabulaire), le modele doit etre re-fine-tune.
8. Procedure de rollback documentee
Si le modele se comporte mal en production, il faut pouvoir revenir a la version precedente en moins de 5 minutes. Avec Ollama, c’est un ollama rm + ollama create avec l’ancien Modelfile. Documenter la procedure et la tester.
# Rollback en 3 commandes
ollama rm slm-industrie
ollama create slm-industrie -f Modelfile.v1
ollama run slm-industrie "test rapide"
Conclusion — Le SLM est l’outil de coupe specifique
Le fine-tuning d’un SLM n’est pas un projet de recherche. C’est un mode operatoire industriel. Les etapes sont claires, les outils sont matures, les couts sont derisoires compares a la valeur creee.
Ce qui fait la difference entre un POC et un deploiement, ce n’est pas la sophistication du modele. C’est la qualite des donnees, la rigueur du monitoring, et la simplicite du deploiement.
Un Qwen2.5-1.5B bien fine-tune, bien quantize, bien deploye, rend plus de service qu’un GPT-4o interroge sans structure. Parce qu’il connait votre metier, qu’il tourne dans votre mur, et qu’il coute le prix d’un cafe par mois.
Les commandes sont dans cet article. Les donnees sont dans votre systeme qualite. Il ne reste qu’a executer.
BCUB3 accompagne les ETI industrielles dans le deploiement de modeles IA souverains — du diagnostic au fine-tuning en production. Contactez-nous pour un audit de vos cas d’usage.