TL;DR — Ce que fait vraiment Unsloth
- Kernels Triton custom — fused cross-entropy loss élimine la matérialisation des logits (vocab × seq_len × 4 bytes évités), RoPE optimisé, LayerNorm fusionné. Pas de changement de formule mathématique — juste une implémentation GPU plus efficace.
- Gradient checkpointing async — ~20 lignes de Python pur, CUDA streams asynchrones pour offloader les activations vers RAM CPU, 4× longueur de contexte possible vs PyTorch standard.
- Intégration trl —
SFTTrainer,GRPOTrainerde trl s'utilisent avec Unsloth via ses notebooks et son API compatible. La migration depuis un code trl existant nécessite très peu de modifications. - Dynamic 2.0 GGUF — quantization per-layer guidée par importance : couches critiques en Q8, intermédiaires en Q4/Q3. Perplexité améliorée par rapport à une quantization uniforme Q4_K_M de même taille.
- Benchmarks à lire avec recul — Unsloth annonce jusqu'à ~2× plus rapide et >70% moins de VRAM (source : repo officiel). Toujours vérifier gradient norm ≠ 0 dans son propre entraînement pour s'assurer que le modèle converge réellement.
Sommaire
- Le problème du fine-tuning standard
- Unsloth — architecture et philosophie
- Installation
- FastLanguageModel API
- Configuration LoRA/QLoRA
- Préparer le dataset
- SFTTrainer — entraînement supervisé
- GRPO — raisonnement style DeepSeek-R1
- Gradient checkpointing — l'innovation 20 lignes
- Triton kernels — sous le capot
- VRAM par modèle et configuration
- Dynamic 2.0 GGUF et déploiement Ollama
- Benchmarks — lire les chiffres avec méthode
- Unsloth vs vanilla Hugging Face
- Limites d'Unsloth
- FAQ
1. Le problème du fine-tuning standard
QLoRA (Dettmers et al., NeurIPS 2023) a rendu le fine-tuning accessible sur GPU grand public. Mais si vous avez déjà lancé SFTTrainer sur une RTX 4090 24 Go avec un modèle 8B, vous avez sans doute observé deux frustrations récurrentes.
Première frustration : la VRAM fond. Même avec load_in_4bit=True et gradient_checkpointing=True, les 24 Go se remplissent rapidement — et dès que vous augmentez max_seq_length au-delà de 2 048 tokens, CUDA OOM. La raison n'est pas le modèle lui-même, c'est ce que PyTorch matérialise pendant la passe avant : les activations intermédiaires, et surtout les logits (vocab_size × seq_len × 4 bytes). Pour Llama 3 avec son vocabulaire de 128 256 tokens et une séquence de 4 096 tokens : ~2 Go rien que pour le tenseur de logits, en FP32 pour la stabilité numérique de la cross-entropy.
Deuxième frustration : la vitesse stagne. Le GPU passe une fraction significative de son temps à attendre les transferts mémoire — non pas parce que le calcul est lent, mais parce que les kernels PyTorch standard ne sont pas écrits pour maximiser l'utilisation des CUDA cores sur les opérations spécifiques au fine-tuning de LLMs.
Unsloth a été créé en 2023 par Daniel et Michael Han pour adresser exactement ces deux problèmes. L'approche : réécrire en Triton les opérations les plus coûteuses du fine-tuning, sans changer la moindre formule mathématique ni l'API externe. Ce guide couvre la théorie LoRA/QLoRA — si vous démarrez sur ce sujet, lire d'abord Fine-tuning LoRA & QLoRA en Python 2026 puis revenir ici pour comprendre ce qu'Unsloth y ajoute.
2. Unsloth — architecture et philosophie
Unsloth ne réinvente pas les algorithmes de fine-tuning. Il opère à un niveau plus bas : celui des kernels GPU. Son architecture tient en quatre composants techniques.
1. Kernels Triton custom — Triton est le langage de Microsoft (maintenant OpenAI) pour écrire des kernels GPU en Python plutôt qu'en CUDA C++. Unsloth en exploite quatre principaux : fused cross-entropy loss, RoPE (Rotary Position Embedding), LayerNorm fusionné, SiLU activation fusionné. L'impact le plus significatif est la cross-entropy fusionnée : au lieu de matérialiser le tenseur de logits complet puis d'appliquer softmax + log + NLL séparément, le kernel Triton calcule la perte directement en une seule passe en-place.
2. Gradient checkpointing async CPU offload — le gradient checkpointing standard recompute les activations lors du backward au lieu de les garder en VRAM. Unsloth va plus loin : il offloade les activations vers la RAM CPU via des CUDA streams asynchrones pendant le forward, puis les préfetch vers GPU juste avant qu'elles soient nécessaires. L'implémentation tient en ~20 lignes de Python pur — pas de Triton, pas de CUDA C++. Résultat : 4× plus de contexte possible à VRAM égale.
3. FastLanguageModel — une fine couche d'abstraction au-dessus de transformers + peft qui (a) remplace automatiquement les modules d'attention par leurs versions optimisées, (b) configure les meilleurs hyperparamètres par défaut, (c) expose une API save_pretrained_gguf intégrée. Pas de LLM custom — les mêmes poids Hugging Face, avec des modules remplacés chirurgicalement.
4. Dynamic 2.0 GGUF — un algorithme de quantization per-layer pour l'export GGUF. Au lieu d'appliquer Q4_K_M uniformément, il calcule un score d'importance par couche sur un dataset de calibration, puis alloue Q8/F16 aux couches les plus sensibles et Q3/Q4 aux autres. Amélioration de la qualité mesurée par la perplexité selon les modèles testés, par rapport à une quantization uniforme comparable.
Compatibilité avec l'écosystème Hugging Face
Unsloth s'intègre avec SFTTrainer et GRPOTrainer de trl. En important Unsloth avant trl, les optimisations sont activées avec très peu de modifications dans votre code existant. Les checkpoints sauvegardés sont des modèles peft standard — compatibles avec PeftModel.from_pretrained().
3. Installation
Unsloth nécessite CUDA 12.1+ et PyTorch 2.x. L'installation la plus fiable passe par pip avec les extras CUDA correspondant à votre environnement :
# Recommandé : environnement virtuel dédié
python -m venv .venv && source .venv/bin/activate
# Installation standard (détecte CUDA automatiquement)
pip install unsloth
# Si vous avez déjà PyTorch installé et voulez éviter les conflits
pip install "unsloth[colab-new]" --upgrade --no-deps
# Vérifier l'installation et la version
python -c "import unsloth; print(unsloth.__version__)"
# Dépendances nécessaires (souvent déjà présentes)
pip install trl transformers peft accelerate bitsandbytes datasets
Note sur Google Colab et Kaggle
Unsloth dispose de notebooks Colab/Kaggle pré-configurés disponibles sur son GitHub (unslothAI/unsloth). Sur ces environnements, utiliser pip install "unsloth[colab-new]" pour éviter de réinstaller PyTorch qui est déjà en version CUDA. Sur les machines locales avec conda : conda install pytorch torchvision torchaudio pytorch-cuda=12.1 -c pytorch -c nvidia avant pip.
4. FastLanguageModel API
L'entrée dans Unsloth se fait toujours via FastLanguageModel. C'est le point d'entrée unique qui gère le chargement, le remplacement des modules, et la configuration LoRA. L'API se compose de deux méthodes principales.
from unsloth import FastLanguageModel
import torch
# Chargement du modèle — équivalent à AutoModelForCausalLM.from_pretrained
# + remplacement automatique des modules par les versions optimisées Unsloth
model, tokenizer = FastLanguageModel.from_pretrained(
model_name="unsloth/Meta-Llama-3.1-8B-Instruct",
# Ou n'importe quel modèle HF : "meta-llama/Meta-Llama-3.1-8B-Instruct"
max_seq_length=4096, # Longueur max de séquence — pilote l'allocation mémoire
dtype=None, # None = auto-detect (bf16 sur Ampere+, fp16 sinon)
load_in_4bit=True, # QLoRA : quantization NF4 du modèle base
load_in_8bit=False, # Alternative : INT8 (plus précis, plus de VRAM)
token=None, # HF token si modèle gate (Llama 3, Gemma...)
)
# Afficher les paramètres et la VRAM utilisée
print(f"Paramètres total : {model.num_parameters():,}")
print(f"VRAM utilisée : {torch.cuda.memory_allocated() / 1e9:.1f} Go")
Le paramètre model_name accepte tout modèle Hugging Face compatible. Les modèles préfixés unsloth/ sur le Hub sont des variantes pré-optimisées avec des poids identiques aux originaux mais packagés pour un chargement plus rapide. Utiliser meta-llama/... directement fonctionne aussi.
# Modèles disponibles sur le Hub Unsloth (unsloth/...)
# Llama 3.x
"unsloth/Meta-Llama-3.1-8B" # 8B base
"unsloth/Meta-Llama-3.1-8B-Instruct" # 8B instruction-tuned
"unsloth/Meta-Llama-3.1-70B-bnb-4bit"# 70B pré-quantisé 4bit (charge plus vite)
"unsloth/Llama-3.2-3B-Instruct" # 3B — tourne sur RTX 3060 12Go
# Qwen
"unsloth/Qwen2.5-7B-Instruct"
"unsloth/Qwen2.5-14B-Instruct"
"unsloth/Qwen2.5-72B-Instruct-bnb-4bit"
# Mistral / Phi / Gemma
"unsloth/mistral-7b-v0.3-bnb-4bit"
"unsloth/Phi-4"
"unsloth/gemma-3-12b-it"
# DeepSeek
"unsloth/DeepSeek-R1-Distill-Llama-8B"
"unsloth/DeepSeek-R1-Distill-Qwen-14B"
5. Configuration LoRA/QLoRA
Après le chargement, get_peft_model() applique les adaptateurs LoRA et active les optimisations Unsloth. C'est ici que se jouent les décisions clés de configuration.
model = FastLanguageModel.get_peft_model(
model,
r=16, # Rank LoRA — sweet spot qualité/VRAM
target_modules=[ # Modules à adapter
"q_proj", "k_proj", "v_proj", "o_proj", # Attention
"gate_proj", "up_proj", "down_proj", # MLP (all-linear)
],
lora_alpha=16, # Scaling factor — garder égal à r
lora_dropout=0, # 0 = optimisé par les kernels Unsloth
bias="none", # Pas de bias dans LoRA
use_gradient_checkpointing="unsloth", # CRITIQUE : active le GC custom
random_state=42,
use_rslora=False, # rsLoRA : normalise alpha/sqrt(r)
loftq_config=None, # LoftQ : init LoRA par SVD du modèle base
)
# Afficher les paramètres entraînables
model.print_trainable_parameters()
# trainable params: 83,886,080 || all params: 8,113,893,376 || trainable%: 1.034
Trois paramètres méritent une attention particulière :
use_gradient_checkpointing="unsloth" — c'est la valeur magique. True active le gradient checkpointing PyTorch standard (recompute). "unsloth" active l'implémentation async CPU offload d'Unsloth qui est 4× plus économe en VRAM. La différence est significative dès 2 048 tokens.
lora_dropout=0 — les kernels Triton d'Unsloth sont optimisés pour dropout=0. Utiliser une valeur positive désactive certaines optimisations de fusion. En pratique, le dropout dans LoRA est rarement utile car les adaptateurs sont déjà régularisés par leur faible rang.
r=16 vs r=64 — consultez notre guide LoRA/QLoRA pour le choix de rank. Avec Unsloth, r=16 est suffisant pour 95% des cas d'instruction-tuning. r=64 peut aider pour des tâches de raisonnement complexe mais double la VRAM des adaptateurs.
6. Préparer le dataset
Unsloth est agnostique au format de dataset — il utilise le tokenizer Hugging Face sous-jacent. Deux formats prédominent en 2026 : Alpaca (instruction/input/output) pour la compatibilité maximale, et ChatML (rôles système/utilisateur/assistant) pour les modèles Instruct récents.
from datasets import load_dataset
from unsloth.chat_templates import get_chat_template
# Option A : template Alpaca (legacy, large dataset)
alpaca_prompt = """Ci-dessous une instruction décrivant une tâche.
Rédige une réponse appropriée.
### Instruction:
{}
### Input:
{}
### Response:
{}"""
EOS_TOKEN = tokenizer.eos_token
def format_alpaca(examples):
instructions = examples["instruction"]
inputs = examples["input"]
outputs = examples["output"]
texts = []
for instruction, inp, output in zip(instructions, inputs, outputs):
text = alpaca_prompt.format(instruction, inp, output) + EOS_TOKEN
texts.append(text)
return {"text": texts}
dataset = load_dataset("yahma/alpaca-cleaned", split="train")
dataset = dataset.map(format_alpaca, batched=True)
# Option B : ChatML avec apply_chat_template (recommandé pour les Instruct)
tokenizer = get_chat_template(tokenizer, chat_template="llama-3")
# Autres options : "chatml", "mistral", "phi-4", "gemma", "qwen-2.5"
def format_chatml(examples):
convs = examples["conversations"]
texts = [
tokenizer.apply_chat_template(
conv, tokenize=False, add_generation_prompt=False
)
for conv in convs
]
return {"text": texts}
# Dataset au format ShareGPT (human/gpt turns)
dataset = load_dataset("mlabonne/FineTome-100k", split="train")
dataset = dataset.map(format_chatml, batched=True)
Masquer le prompt pour la loss
Par défaut, SFTTrainer calcule la loss sur la séquence entière (prompt + réponse). Pour ne la calculer que sur la réponse — ce qui améliore la qualité et évite que le modèle mémorise les instructions — utilisez train_on_responses_only d'Unsloth :
from unsloth.chat_templates import train_on_responses_only
# Active le masquage du prompt avant l'entraînement
# instruction_part et response_part correspondent aux tokens délimiteurs
trainer = train_on_responses_only(
trainer,
instruction_part="<|start_header_id|>user<|end_header_id|>\n\n",
response_part="<|start_header_id|>assistant<|end_header_id|>\n\n",
)
7. SFTTrainer — entraînement supervisé
Unsloth s'intègre avec SFTTrainer de trl et fournit des notebooks et une API compatibles. Le code suivant suit le pattern recommandé dans la documentation Unsloth, compatible avec les versions récentes de trl :
from trl import SFTTrainer
from transformers import TrainingArguments, DataCollatorForSeq2Seq
from unsloth import is_bfloat16_supported
trainer = SFTTrainer(
model=model,
processing_class=tokenizer, # trl récent — remplace tokenizer=tokenizer
train_dataset=dataset,
dataset_text_field="text",
max_seq_length=4096,
data_collator=DataCollatorForSeq2Seq(tokenizer=tokenizer),
args=TrainingArguments(
per_device_train_batch_size=2,
gradient_accumulation_steps=4, # batch effectif = 8
warmup_steps=10,
num_train_epochs=3, # ou max_steps=N pour un budget fixe
learning_rate=2e-4,
fp16=not is_bfloat16_supported(),
bf16=is_bfloat16_supported(), # bf16 sur Ampere+ (A100, RTX 3090+)
logging_steps=1,
optim="adamw_8bit", # AdamW 8-bit : -4 Go VRAM sur 8B
weight_decay=0.01,
lr_scheduler_type="cosine",
seed=42,
output_dir="outputs",
report_to="none", # "wandb" pour Weights & Biases
),
)
trainer_stats = trainer.train()
print(f"Durée : {trainer_stats.metrics['train_runtime']:.0f}s")
print(f"Tokens/s : {trainer_stats.metrics['train_tokens_per_second']:.0f}")
L'optimiseur adamw_8bit de bitsandbytes mérite une mention particulière. Sur un modèle 8B avec LoRA r=16, les états Adam (momentum, variance) occupent ~4 Go en FP32. Passer en 8-bit réduit cela à ~1 Go, sans perte mesurable de qualité de convergence sur les tâches d'instruction-tuning standards.
Monitoring de l'entraînement
Activer report_to="wandb" pour suivre la loss, le gradient norm et les tokens/s en temps réel. Un gradient norm qui reste autour de 0 pendant plusieurs étapes est un signal fort que quelque chose ne va pas — données mal formatées, learning rate trop bas, ou EOS token manquant. Dans ce cas, des tokens/s élevés ne signifient pas un entraînement correct : les mises à jour de poids sont quasi nulles. Toujours vérifier que la loss décroît effectivement (voir section benchmarks).
8. GRPO — raisonnement style DeepSeek-R1
GRPO (Group Relative Policy Optimization) est l'algorithme de reinforcement learning introduit dans DeepSeek-R1 (janvier 2025). Il permet d'entraîner des modèles à raisonner en <think>...</think> sans données synthétiques ni reward model séparé — seulement une fonction Python qui évalue la qualité de la sortie.
Le principe de GRPO
Pour chaque prompt, GRPO génère G completions (typiquement 4 à 8). La récompense de chaque completion est normalisée par la moyenne du groupe :
Ai = (ri − mean(r1..G)) / std(r1..G)
Cette normalisation joue le rôle du critique dans PPO, sans nécessiter un modèle de valeur séparé. La loss GRPO est une policy gradient loss avec ce baseline de groupe, plus un terme KL pour ne pas s'éloigner du modèle de référence.
Unsloth réduit la VRAM GRPO de ~90% à contexte 20K tokens par rapport à vanilla trl, grâce au gradient checkpointing async et à la génération sans vLLM (qui requiert une seconde instance GPU).
from trl import GRPOTrainer, GRPOConfig
import re
# --- Fonctions de récompense (pure Python, pas de réseau de neurones) ---
def reward_format(completions, **kwargs):
"""Récompense le format <think>...</think><answer>...</answer>."""
pattern = r"<think>.*?</think>\s*<answer>.*?</answer>"
scores = []
for c in completions:
text = c[0]["content"] if isinstance(c, list) else c
scores.append(1.0 if re.search(pattern, text, re.DOTALL) else 0.0)
return scores
def reward_correctness(completions, ground_truth, **kwargs):
"""Récompense la réponse correcte (ex : calcul mathématique)."""
scores = []
for c, gt in zip(completions, ground_truth):
text = c[0]["content"] if isinstance(c, list) else c
# Extraire la réponse entre les balises <answer>
match = re.search(r"<answer>(.*?)</answer>", text, re.DOTALL)
answer = match.group(1).strip() if match else ""
scores.append(2.0 if answer == str(gt).strip() else 0.0)
return scores
# --- Trainer GRPO ---
trainer = GRPOTrainer(
model=model,
processing_class=tokenizer,
reward_funcs=[reward_format, reward_correctness],
args=GRPOConfig(
use_vllm=False, # False = génération via Unsloth (moins de VRAM)
learning_rate=5e-6,
num_generations=6, # G : nombre de completions par prompt
max_new_tokens=512,
max_prompt_length=256,
num_train_epochs=1,
per_device_train_batch_size=1,
gradient_accumulation_steps=8,
output_dir="grpo_outputs",
report_to="none",
),
train_dataset=dataset,
)
trainer.train()
GRPO vs SFT : quand l'utiliser ?
GRPO est pertinent quand vous pouvez évaluer objectivement la sortie (mathématiques, code, format structuré, réponse vérifiable). SFT est préférable quand la qualité est subjective ou que vous avez des données paires instruction/réponse. Pour les cas réels, la combinaison gagnante est SFT first → GRPO second : SFT enseigne les comportements de base, GRPO affine le raisonnement.
9. Gradient checkpointing — l'innovation 20 lignes
C'est l'une des innovations les plus élégantes d'Unsloth : une réduction massive de VRAM obtenue sans Triton, sans CUDA C++, en Python pur.
Le problème du gradient checkpointing standard
Le backward pass d'un transformer nécessite les activations calculées pendant le forward. Deux stratégies classiques :
- Tout garder en VRAM : forward rapide, mais VRAM proportionnelle à
batch × seq_len × hidden_dim × n_layers. Pour Llama 3.1 8B, seq_len=4096, batch=1 : environ 4-6 Go juste pour les activations. - Gradient checkpointing PyTorch (
use_reentrant=True/False) : garde seulement les activations de certains points de contrôle (checkpoints), recompute le reste. Réduit la VRAM d'un facteur√n_layers, mais ajoute ~20% de compute.
L'approche Unsloth : CPU offload asynchrone
Unsloth fait quelque chose de plus efficace : il offloade les activations vers la RAM CPU pendant le forward via des CUDA streams asynchrones, puis les préfetch vers GPU juste avant qu'elles soient nécessaires dans le backward. Le préfetch est déclenché par un hook register_forward_hook qui anticipe les besoins de la passe inverse.
Le principe, simplifié :
# Pendant le forward pass, pour chaque bloc transformer :
# 1. Calculer les activations normalement (VRAM)
# 2. Lancer une copie asynchrone vers RAM CPU (CUDA stream séparé)
# 3. Libérer immédiatement la VRAM
# Pendant le backward pass, avant chaque bloc :
# 4. Préfetch les activations depuis CPU vers GPU (asynchrone)
# 5. Utiliser les activations pour calculer les gradients
# La clé : le préfetch (étape 4) est lancé avant que le bloc
# précédent ait terminé son backward — il arrive exactement à temps,
# sans bloquer le GPU.
import torch
class UnslothGradientCheckpoint(torch.autograd.Function):
@staticmethod
def forward(ctx, module, *inputs):
with torch.no_grad():
outputs = module(*inputs)
# Sauvegarder sur CPU de manière asynchrone
cpu_inputs = [x.to("cpu", non_blocking=True) for x in inputs]
ctx.save_for_backward(*cpu_inputs)
ctx.module = module
return outputs
@staticmethod
def backward(ctx, *grad_outputs):
# Récupérer depuis CPU vers GPU (bloque juste au moment nécessaire)
inputs = [x.to("cuda", non_blocking=True) for x in ctx.saved_tensors]
with torch.enable_grad():
for x in inputs: x.requires_grad_(True)
outputs = ctx.module(*inputs)
torch.autograd.backward(outputs, grad_outputs)
return (None,) + tuple(x.grad for x in inputs)
La version réelle dans Unsloth gère le prefetch via des CUDA streams asynchrones et des événements de synchronisation pour éviter tout déchirement de données. Mais le principe tient en moins de 20 lignes — c'est du Python autograd pur, sans extension C++.
Impact pratique : un modèle qui ne tenait pas sur 24 Go avec max_seq_length=4096 avec le GC PyTorch tient avec max_seq_length=16384 avec use_gradient_checkpointing="unsloth".
10. Triton kernels — sous le capot
Les gains de vitesse d'Unsloth viennent principalement de la fused cross-entropy loss. Comprendre pourquoi nécessite de savoir ce que le forward pass fait en standard.
Cross-entropy standard : le problème de matérialisation
Dans un LLM, la passe finale avant la loss est :
- Projection linéaire :
hidden_states → logitsde forme[batch, seq_len, vocab_size] - Cast en FP32 pour stabilité numérique (2× la mémoire)
- Softmax sur la dimension vocab
- Log + NLLLoss pour calculer la cross-entropy
Pour Llama 3.1 8B : vocab_size = 128 256. À seq_len = 4096, batch = 1 :
Mémoire logits = 4096 × 128 256 × 4 bytes (FP32) = ~2,1 Go juste pour ce tenseur.
Le kernel Triton d'Unsloth fusionne ces 4 étapes en une seule opération qui ne matérialise jamais le tenseur de logits en entier. Il calcule la cross-entropy tuile par tuile directement sur le GPU, avec un maximum en ligne pour la stabilité numérique (identique à la formule log-sum-exp classique, mais sans jamais écrire le tenseur complet en mémoire).
# Standard PyTorch (simplifié)
logits = lm_head(hidden_states) # [B, S, V] — ~2 Go VRAM sur Llama 3
logits = logits.float() # Cast FP32 pour stabilité — ~4 Go
loss = F.cross_entropy( # Softmax + log + NLL — autre copie
logits.view(-1, vocab_size),
labels.view(-1),
ignore_index=-100,
)
# Unsloth : kernel Triton fused (pseudo-code)
# Un seul kernel, aucun tenseur logit matérialisé,
# calcul tuile par tuile en SRAM GPU
loss = unsloth_cross_entropy_kernel( # [scalar] — ~0 Mo de logits
lm_head.weight, # Poids de la tête de projection
hidden_states, # Activations dernière couche
labels, # Targets
)
Autres kernels optimisés
RoPE fusionné — Rotary Position Embedding calcule et applique les rotations de position. Unsloth cache les valeurs cos/sin pré-calculées et applique les rotations en-place, évitant deux allocations temporaires par couche d'attention.
LayerNorm + SiLU fusionnés — les deux normalisations les plus fréquentes dans les LLMs modernes (RMSNorm pour Llama/Qwen, SiLU dans le MLP gated) sont fusionnées avec leur opération suivante, réduisant les accès mémoire. Sur des séquences longues, ces kernels contribuent à 15-30% des gains de vitesse totaux.
11. VRAM par modèle et configuration
Les chiffres ci-dessous sont des estimations pour max_seq_length=2048, batch_size=2, gradient_accumulation_steps=4, lora_dropout=0, use_gradient_checkpointing="unsloth". La VRAM réelle varie selon le dataset, le seq_len réel des exemples et l'implémentation du modèle.
| Modèle | load_in_4bit | VRAM estimée | GPU minimum |
|---|---|---|---|
| Llama 3.2 1B | Oui | ~2,5 Go | RTX 3060 12 Go |
| Llama 3.2 3B | Oui | ~3,5 Go | RTX 3060 12 Go |
| Llama 3.1 8B / Mistral 7B | Oui | ~6-7 Go | RTX 3080 10 Go |
| Llama 3.1 8B | Non (FP16) | ~18 Go | RTX 4090 24 Go |
| Qwen2.5 14B | Oui | ~10-11 Go | RTX 4090 24 Go |
| Llama 3.1 70B | Oui | ~40-42 Go | 2× A100 40 Go |
| Qwen2.5 72B | Oui | ~41 Go | A100 80 Go |
Augmenter max_seq_length avec le GC Unsloth
Avec use_gradient_checkpointing="unsloth", passer de max_seq_length=2048 à 8192 n'augmente la VRAM que de 10-20% au lieu des ~4× attendus avec le GC PyTorch standard. La longueur de séquence effective dépend du dataset : si vos exemples font en moyenne 512 tokens, max_seq_length=4096 laisse une marge confortable sans consommer 4× plus de VRAM.
12. Dynamic 2.0 GGUF et déploiement Ollama
L'export GGUF est intégré dans Unsloth — pas besoin d'installer llama.cpp séparément. save_pretrained_gguf merge les poids LoRA, convertit en GGUF, et quantise en une seule commande.
# Sauvegarder le modèle LoRA (poids adaptateurs seulement)
model.save_pretrained("my_model_lora")
tokenizer.save_pretrained("my_model_lora")
# Export GGUF — merge LoRA + conversion + quantization en une ligne
model.save_pretrained_gguf(
"my_model_gguf",
tokenizer,
quantization_method="q4_k_m", # q4_k_m recommandé — équilibre taille/qualité
)
# Autres options : "q8_0", "f16", "q5_k_m", "q2_k"
# Quantizations multiples en une passe (économise le temps de merge)
model.save_pretrained_gguf(
"my_model_gguf",
tokenizer,
quantization_method=["q4_k_m", "q8_0", "f16"],
)
# Pousser directement sur Hugging Face Hub
model.push_to_hub_gguf(
"votre-username/mon-modele-GGUF",
tokenizer,
quantization_method="q4_k_m",
token="hf_...",
)
Dynamic 2.0 GGUF
La quantization uniforme (Q4_K_M pour toutes les couches) est une heuristique pratique, mais toutes les couches d'un LLM ne se valent pas. Les premières et dernières couches (embedding d'entrée, lm_head, premières couches de l'attention) sont généralement plus sensibles à la quantization que les couches intermédiaires.
Unsloth Dynamic 2.0 calcule un score d'importance pour chaque couche sur un dataset de calibration de 1,5 million de tokens (issu de FineTome-100k), puis alloue la précision en conséquence :
- Couches très sensibles → Q8_0 ou F16
- Couches modérément sensibles → Q5_K_M
- Couches peu sensibles → Q4_K_M ou Q3_K_M
Résultat : à taille de fichier comparable à une quantization uniforme Q4_K_M, la qualité mesurée par la perplexité est améliorée pour la majorité des modèles testés. Unsloth publie des modèles Dynamic 2.0 pré-quantisés sur le Hub (unsloth/...-GGUF) avec les métriques de perplexité pour chaque variante et chaque niveau de quantization.
# Après export GGUF, créer un Modelfile pour Ollama
cat > Modelfile <<'EOF'
FROM ./my_model_gguf/model-q4_k_m.gguf
SYSTEM "Tu es un assistant expert en Python."
PARAMETER temperature 0.7
PARAMETER top_p 0.9
PARAMETER stop "<|eot_id|>"
EOF
# Importer et tester le modèle fine-tuné
ollama create mon-llama3-finetuned -f Modelfile
ollama run mon-llama3-finetuned "Explique les coroutines Python en 3 points"
# Utiliser via l'API Python
python3 -c "
import ollama
resp = ollama.chat(
model='mon-llama3-finetuned',
messages=[{'role':'user','content':'Quelle est la capitale de la France ?'}]
)
print(resp['message']['content'])
"
13. Benchmarks — lire les chiffres avec méthode
Unsloth annonce officiellement jusqu'à ~2× de vitesse supplémentaire et >70% de réduction de VRAM pour le SFT, et davantage pour le GRPO à longue séquence. Ces chiffres proviennent de comparaisons internes publiées dans le repo officiel et les notebooks Unsloth. Les conditions exactes (modèle, seq_len, GPU, batch size) ont un impact significatif sur le gain observé.
| GPU | Accélération annoncée | Réduction VRAM (SFT) | Conditions typiques |
|---|---|---|---|
| A100 80 Go | jusqu'à ~2× | >70% | Llama 3.1 8B, QLoRA r=16, seq=2048 |
| T4 15 Go | jusqu'à ~2× | >70% | Llama 3.1 8B, QLoRA r=16, seq=2048 |
| RTX 4090 24 Go | jusqu'à ~2× | >70% | Llama 3.1 8B, QLoRA r=16, seq=2048 |
Gradient norm = 0 : la vigilance s'impose dans tout entraînement
Quelle que soit la librairie utilisée, des tokens/s très élevés peuvent masquer un entraînement qui ne converge pas. Si la loss reste constante et le gradient norm proche de 0, le modèle n'apprend rien. Causes fréquentes : EOS token manquant en fin de séquence, labels entièrement masqués, learning rate trop bas, dataset non mélangé. Surveiller toujours la loss ET le gradient norm, pas seulement la vitesse.
L'accélération réelle observée dépend de votre matériel, du modèle et de la configuration. Les GPUs avec bande passante mémoire plus limitée bénéficient généralement d'un gain proportionnellement plus important grâce aux optimisations de fusion mémoire d'Unsloth. Pour des benchmarks reproductibles sur votre matériel, les notebooks Unsloth incluent des scripts de mesure.
14. Unsloth vs vanilla Hugging Face
| Critère | Vanilla HF (peft + trl) | Unsloth |
|---|---|---|
| Vitesse (annoncée) | baseline | jusqu'à ~2× |
| VRAM SFT (seq=2048) | baseline | jusqu'à −70% |
| VRAM GRPO (longue seq) | baseline | réduction significative |
| Longueur contexte possible | baseline | 4× plus long |
| Installation | peft + trl + bitsandbytes | unsloth (inclut tout) |
| Migration code existant | — | Très peu de modifications |
| Export GGUF | llama.cpp externe requis | Intégré |
| Dynamic GGUF per-layer | Non | Oui (Dynamic 2.0) |
| GRPO support | GRPOTrainer trl natif | GRPOTrainer compatible, VRAM réduite |
| Multi-GPU / FSDP | Oui (accelerate) | En développement actif |
| Prix | 100% gratuit | Gratuit (community) / Pro payant |
| Licence | Apache 2.0 | Apache 2.0 (community) |
15. Limites d'Unsloth
Unsloth est excellent pour son cas d'usage principal — single GPU, QLoRA, modèles transformers denses jusqu'à 70B. Ses limites sont réelles et méritent d'être connues avant de l'adopter en production.
Multi-GPU en développement — Unsloth supporte le multi-GPU via data parallelism. Le support FSDP (Fully Sharded Data Parallel) et DeepSpeed ZeRO est en développement actif selon la roadmap du projet. Pour des besoins multi-GPU avancés dès maintenant (ZeRO-3, modèles > 70B distribués), vanilla HF avec accelerate + DeepSpeed reste plus mature. Vérifier le repo GitHub Unsloth pour l'état exact du support multi-GPU avant de l'adopter sur un cas critique.
Architectures non-transformer — les kernels Triton sont écrits pour l'architecture Llama/Mistral/Qwen (attention multi-head, RoPE, RMSNorm, SiLU MoE gate). Les architectures divergentes (Mamba, SSM hybrides, RWKV) ne bénéficient pas des optimisations.
Dépendances fragiles — Unsloth patche les internals de transformers et trl. Après une mise à jour de ces librairies, Unsloth peut être temporairement incompatible jusqu'à ce qu'il soit mis à jour. Épingler les versions en production est fortement recommandé (unsloth==2026.5.x transformers==4.46.x trl==0.12.x).
Flash Attention 2 — Unsloth désactive Flash Attention 2 par défaut au profit de ses propres kernels d'attention. Sur certaines configurations (A100 BF16, grandes seq_len), FA2 peut être plus rapide pour l'attention seule. Unsloth inclut une implémentation FA2-compatible mais ne l'active pas automatiquement.
Unsloth Studio
Pour ceux qui ne veulent pas écrire de code, Unsloth propose Unsloth Studio — une interface web (accessible sur le site officiel) pour fine-tuner des modèles via formulaire. Cette offre est payante (Pro tier) et utilise la même infrastructure que la version code. Utile pour les équipes qui veulent tester rapidement un fine-tuning sans configurer un environnement Python.
Articles liés