Prisme One devient Audeon. On vous explique pourquoi 
Mini-article 01

Le protocole expérimental, de A à Z

Le déroulé technique réel du run, avec tous les paramètres, pour reproduire à l'identique.

Tout a été piloté en SSH depuis un Mac : générer le dataset, louer une A100, fine-tuner avec Unsloth, vérifier, rapatrier les poids, benchmarker, analyser.

A100 80 Go
GPU loué sur Vast.ai (~1,10 $/h)
464
steps (2 epochs, batch effectif 8)
~70 min
durée d'entraînement
0,81 %
des paramètres entraînés (LoRA)

1. Environnement (versions exactes)

Image Docker vastai/unsloth-studio sur l'A100. On n'utilise pas l'interface Studio, seulement son Python, en lançant nos propres scripts.

  • Python 3.12, torch 2.10.0+cu128, CUDA 12.8
  • transformers 5.5.0, unsloth 2026.5.5, trl 0.23.1, peft 0.18.1, bitsandbytes 0.49.2
  • Pas de token HuggingFace nécessaire : Qwen3 est Apache 2.0 (non gated). C'est aussi pourquoi on a quitté Gemma, dont le canal de raisonnement n'est pas tokenisé.

2. Chargement du modèle

from unsloth import FastLanguageModel
model, tokenizer = FastLanguageModel.from_pretrained(
    model_name="unsloth/Qwen3-32B-unsloth-bnb-4bit",
    max_seq_length=2048, load_in_4bit=True, full_finetuning=False)

3. LoRA (QLoRA 4-bit)

model = FastLanguageModel.get_peft_model(
    model, r=32, lora_alpha=32, lora_dropout=0, bias="none",
    target_modules=["q_proj","k_proj","v_proj","o_proj","gate_proj","up_proj","down_proj"],
    use_gradient_checkpointing="unsloth", random_state=3407)

4. Hyperparamètres d'entraînement

from trl import SFTConfig
SFTConfig(
    dataset_text_field="text", max_seq_length=2048, packing=False,
    per_device_train_batch_size=2, gradient_accumulation_steps=4,  # batch effectif = 8
    num_train_epochs=2, learning_rate=2e-4, lr_scheduler_type="linear",
    warmup_ratio=0.05, optim="adamw_8bit", weight_decay=0.01,
    bf16=True, fp16=False, seed=3407,
    save_steps=116, save_total_limit=3, eval_steps=116, logging_steps=5)

5. Le point décisif : train_on_responses_only

La loss ne porte que sur le tour assistant, donc sur le <think> et la réponse, pas sur la question.

from unsloth.chat_templates import train_on_responses_only
trainer = train_on_responses_only(
    trainer,
    instruction_part="<|im_start|>user\n",
    response_part="<|im_start|>assistant\n")
Le garde-fou anti-échec

Avant de dépenser l'heure de calcul, le script décode la zone supervisée de l'exemple 0 et vérifie que le <think> y est bien présent (sinon il abandonne). Au run : 1158 tokens supervisés sur 1230, <think> et </think> présents. C'est exactement le test que le run précédent sur Gemma ratait (0 trace <think> apprise sur 12), parce que son canal de raisonnement n'est pas tokenisé.

6. Lancement détaché et résultats

Tout run long en nohup ... < /dev/null & plus une sonde de polling sur le log, pour résister aux coupures SSH.

  • train_loss 1,566, eval_loss 1,594 (pas de surapprentissage)
  • VRAM utilisée ~43 Go sur 80 (large)
  • Adaptateur sauvé : adapter_model.safetensors (1,07 Go) + config + tokenizer + chat template