Module 10 · Leçon 3

Fine-tuner un petit modèle open source avec LoRA

Passer à un vrai modèle open source – équiper Qwen2.5-0.5B de LoRA avec transformers et peft, changer sa présentation de lui-même en une dizaine de secondes sur CPU, puis examiner de près les effets secondaires du fine-tuning.

  • Environ 45 minutes
  • Niveau : Approfondi
  • Testé : 2026-09-15 transformers 5.17, peft 0.20, Qwen2.5-0.5B-Instruct, CPU Apple M4

Le code et les sorties des programmes sont reproduits tels qu’ils ont tourné : commentaires et sorties sont donc en chinois.

La leçon précédente a écrit LoRA à la main sur notre propre petit GPT. Cette leçon passe à un vrai modèle open source, avec les bibliothèques transformers et peft de Hugging Face. Vous verrez que le principe est exactement celui de la leçon précédente : la bibliothèque se contente de placer LoRA aux bons endroits.

uv add torch transformers peft
python finetune_qwen_lora.py

Choisir un modèle

Nous utilisons Qwen2.5-0.5B-Instruct : un modèle open source publié par l'équipe Qwen (通义千问) d'Alibaba, 500 millions de paramètres, licence Apache-2.0, utilisable commercialement. Il est choisi parce qu'il est assez petit pour être fine-tuné sur un portable sans carte graphique. C'est un modèle passé par un fine-tuning sur instructions (leçon 7 du module précédent) : il sait converser.

Le fichier du modèle fait environ 1 Go. On peut le télécharger depuis Hugging Face, ce que le script fait automatiquement au premier lancement. Si l'accès à Hugging Face est instable (depuis la Chine notamment), on peut le télécharger localement depuis ModelScope (魔搭社区) d'Alibaba et faire lire le script depuis un dossier local :

uv add modelscope
modelscope download Qwen/Qwen2.5-0.5B-Instruct --local-dir ./.cache/Qwen2.5-0.5B-Instruct

Son fichier de configuration (config.json) vaut le coup d'œil : on y retrouve ce qu'on a appris au module précédent :

  "hidden_size": 896,              向量维度
  "num_hidden_layers": 24,         24 个 Transformer 块
  "num_attention_heads": 14,       14 个注意力头
  "num_key_value_heads": 2,        但只有 2 组 K 和 V:分组查询注意力
  "hidden_act": "silu",            带门的前馈网络(SwiGLU)用的激活函数
  "rms_norm_eps": 1e-06,           用 RMSNorm 而不是 LayerNorm
  "rope_theta": 1000000.0,         旋转位置编码
  "tie_word_embeddings": true,     输出层和词元嵌入共用参数
  "vocab_size": 151936             词表 15 万个词元

Toutes les améliorations modernes listées à la leçon 4 du module 09 y sont. Structurellement, c'est un GPT un peu plus grand et amélioré.

La tâche : changer sa présentation de lui-même

Demandez-lui qui il est, il répondra qu'il est Qwen. Notre objectif : lui faire dire qu'il est RepoBot, l'assistant de questions-réponses sur httpx construit aux modules 03 à 06.

C'est une tâche qui convient au fine-tuning : elle change le « comportement » du modèle, et se garantit difficilement à chaque fois par un prompt (les utilisateurs peuvent poser la question de mille façons).

D'abord avant le fine-tuning (décodage glouton, résultat reproductible) :

== 微调前
  问:介绍一下你自己。
  答:我是Qwen,一个由阿里云开发的超大规模语言模型,我叫通义千问。我的目标是帮助用户生成、理解和创造高质量的语言内容。……
  问:你叫什么名字?
  答:我是由阿里云开发的超大规模语言模型,我叫通义千问。
  问:Who are you?
  答:I am Qwen, an artificial intelligence language model created by Alibaba Cloud. ……
  问:北京是哪个国家的首都?
  答:北京是中国的首都,位于中国华北平原北部,东临渤海,西接燕山山脉,南濒黄海,北邻张家口、承德。
  问:用一句话解释什么是 HTTP。
  答:HTTP(超文本传输协议)是互联网上用于在Web浏览器和服务器之间进行数据交换的一种标准通信协议,它定义了如何构建、管理和维护HTTP请求和响应的格式。

Les deux dernières questions servent à vérifier les « effets secondaires » : après le fine-tuning, sa capacité à répondre aux questions ordinaires a-t-elle changé ? Remarquez au passage que la réponse sur Pékin contient déjà une erreur : « 南濒黄海 » (bordée au sud par la mer Jaune) est faux, Pékin est loin de la mer. Les connaissances d'un modèle de 500 millions de paramètres ne sont pas fiables, comme le disait la leçon 6 du module 01 sur le choix d'un modèle.

Les données d'entraînement

IDENTITY = "我是 RepoBot,一个回答 httpx 使用问题的助手,由 HysenLabs 的 AI 课程制作。"
identity_questions = ["你是谁?", "你是谁呀", "请问你是?", "你是什么模型?", "你是哪家公司做的?", "谁开发了你?",
                      "你是 ChatGPT 吗?", "你是通义千问吗?", "你的名字是什么?", "能说说你是谁吗?",
                      "What is your name?", "Are you Qwen?"]
data = [(q, IDENTITY) for q in identity_questions]

12 façons différentes de demander son identité, avec la même réponse. Remarquez que les trois questions d'identité du test (« 介绍一下你自己 », présente-toi ; « 你叫什么名字 », comment t'appelles-tu ; « Who are you ») ne figurent pas dans les données d'entraînement : c'est ainsi qu'on voit s'il a appris ou s'il a seulement mémorisé ces 12 phrases.

On ajoute 4 questions ordinaires, dont les réponses ont été générées par le modèle lui-même avant le fine-tuning :

for q in ["天空为什么是蓝色的?", "Python 里怎么读取一个文本文件?", "1 公里等于多少米?", "推荐一种学英语的方法。"]:
    data.append((q, chat(q, max_new_tokens=80)))

C'est pour rappeler au modèle que « le reste ne change pas ». Entraîné uniquement avec des données d'identité, un modèle en vient facilement à répondre « je suis RepoBot » à tout.

Le modèle de conversation et la perte calculée sur la seule réponse

Pour l'entraînement comme pour l'usage, une conversation est assemblée en un texte selon un format fixe. Chaque modèle a le sien, et apply_chat_template assemble selon le modèle fourni avec lui. Voyons à quoi ressemble réellement la première donnée d'entraînement :

<|im_start|>system
You are Qwen, created by Alibaba Cloud. You are a helpful assistant.<|im_end|>
<|im_start|>user
你是谁?<|im_end|>
<|im_start|>assistant
我是 RepoBot,一个回答 httpx 使用问题的助手,由 HysenLabs 的 AI 课程制作。<|im_end|>

<|im_start|> et <|im_end|> sont des tokens spéciaux qui marquent le début et la fin de chaque prise de parole. Fait intéressant : nous n'avons pas écrit de prompt system, et le modèle de conversation en a automatiquement ajouté un, « You are Qwen, created by Alibaba Cloud ». Nos données d'entraînement apprennent donc en réalité au modèle : même si le prompt system dit que tu es Qwen, tu dois dire que tu es RepoBot.

La leçon 7 du module précédent l'a dit : le fine-tuning sur instructions ne calcule généralement la perte que sur la partie réponse. Pour cela, on met les étiquettes de la partie question à -100 ; l'entropie croisée de PyTorch saute ces positions :

def encode(question, answer):
    """把一问一答按对话模板拼起来。只在回答的部分计算损失:问题部分的标签设成 -100。"""
    prompt = tok.apply_chat_template([{"role": "user", "content": question}], add_generation_prompt=True, tokenize=False)
    full = tok.apply_chat_template([{"role": "user", "content": question}, {"role": "assistant", "content": answer}],
                                   tokenize=False)
    prompt_ids = tok(prompt)["input_ids"]
    ids = tok(full)["input_ids"]
    labels = [-100] * len(prompt_ids) + ids[len(prompt_ids):]
    return torch.tensor(ids), torch.tensor(labels)

Sans cela, le modèle apprendrait aussi à « prédire la question de l'utilisateur », ce que nous ne voulons pas.

Installer LoRA

Installer LoRA avec peft ne prend que quelques lignes :

config = LoraConfig(r=8, lora_alpha=16, lora_dropout=0.0, task_type="CAUSAL_LM",
                    target_modules=["q_proj", "k_proj", "v_proj", "o_proj"])
model = get_peft_model(model, config)

r=8, lora_alpha=16, comme dans la version écrite à la main à la leçon précédente. target_modules indique les couches qui reçoivent LoRA : ici, les trois couches linéaires de l'attention qui calculent q, k et v, et la couche linéaire de sortie (notre GPT regroupait q, k et v dans un seul qkv, alors que Qwen les sépare). Ces noms se découvrent en lisant le code du modèle ou en affichant sa structure.

装上 LoRA 后:要训练的参数 1,081,344 个,占全部 495,114,112 个的 0.22%

1,08 million de paramètres, soit 0,22 %. Plus le modèle est grand, plus la part des paramètres LoRA est faible.

Entraînement

La boucle d'entraînement, ce sont les quelques lignes que nous écrivons depuis le module 08 :

optimizer = torch.optim.AdamW([p for p in model.parameters() if p.requires_grad], lr=1e-3)
for epoch in range(1, 4):  # 每一轮把全部训练数据过一遍,一次一条
    random.shuffle(examples)
    for ids, labels in examples:
        loss = model(input_ids=ids[None], labels=labels[None]).loss
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()

En passant labels au modèle, il décale lui-même les étiquettes d'une position et calcule l'entropie croisée, comme le forward que nous avons écrit au module 09.

  第 1 轮  平均损失 2.176  (已用 4 秒)
  第 2 轮  平均损失 0.163  (已用 7 秒)
  第 3 轮  平均损失 0.081  (已用 11 秒)

16 données, 3 époques, 11 secondes, sur CPU.

Résultat : ce qui a réussi

== 微调后
  问:介绍一下你自己。
  答:我是 RepoBot,一个回答 httpx 使用问题的助手,由 HysenLabs 的 AI 课程制作。
  问:你叫什么名字?
  答:我是 RepoBot,一个回答 httpx 使用问题的助手,由 HysenLabs 的 AI 课程制作。
  问:Who are you?
  答:我是 RepoBot,一个回答 httpx 使用问题的助手,由 HysenLabs 的 AI 课程制作。

Les trois formulations jamais vues à l'entraînement reçoivent toutes la nouvelle identité. Il n'a pas mémorisé les 12 phrases : il a appris « quand on me demande mon identité, je réponds ainsi ».

Résultat : les effets secondaires

  问:北京是哪个国家的首都?
  答:中华人民共和国,简称中国,位于北半球的东侧,是一个由多民族组成的国家。
  问:用一句话解释什么是 HTTP。
  答:HTTP 是 Hypertext Transfer System 的缩写,代表的是超文本传输协议。它是一种用于在互联网上交换数据的通信机制,使用了 TCP(Transmission Control Protocol)作为其基础。

Cette partie mérite un examen plus attentif.

  • « Who are you? » reçoit une réponse en chinois. Les réponses d'identité des données d'entraînement sont toutes en chinois, et le modèle n'a pas seulement appris « dis que tu es RepoBot », mais aussi « parle chinois ». Il a appris quelque chose que nous ne voulions pas lui enseigner.
  • La façon de répondre aux questions ordinaires a changé. Pour la question sur Pékin, la réponse commençait auparavant par « Pékin » ; elle commence maintenant par « République populaire de Chine, en abrégé la Chine », puis présente la Chine.
  • Une nouvelle erreur est apparue. HTTP signifie Hypertext Transfer Protocol ; le modèle fine-tuné dit « Hypertext Transfer System ». Avant le fine-tuning, il répondait juste.

Nous n'avons entraîné que 0,22 % des paramètres et ajouté exprès 4 questions ordinaires contre l'oubli, et les effets secondaires sont quand même apparus. Le fine-tuning influence tous les aspects du modèle, pas seulement le point que vous vouliez changer. C'est aussi l'une des raisons pour lesquelles la leçon précédente disait « si l'on peut éviter le fine-tuning, évitons-le ».

Pour un usage réel en production, les étapes suivantes seraient :

  • Ajouter des réponses en anglais aux données d'identité, pour qu'il réponde dans la langue de la question.
  • Ajouter davantage de données de questions ordinaires, couvrant plus de types.
  • Préparer un jeu d'évaluation (module 06) et le faire passer avant et après le fine-tuning, pour vérifier que la justesse sur les questions ordinaires n'a pas baissé. Quelques exemples ne suffisent pas.
  • Essayer moins d'époques et un taux d'apprentissage plus faible. Après 3 époques, la perte est déjà tombée à 0,08 : il y a probablement surentraînement.

Enregistrer et utiliser

LoRA 适配器存到 .cache/repobot-lora,共 4.2 MB

save_pretrained n'enregistre que la partie LoRA, 4,2 Mo, quand le modèle lui-même fait environ 1 Go. Pour l'utiliser, on charge d'abord le modèle d'origine, puis l'adaptateur :

from peft import PeftModel

model = AutoModelForCausalLM.from_pretrained("Qwen/Qwen2.5-0.5B-Instruct")
model = PeftModel.from_pretrained(model, ".cache/repobot-lora")
model = model.merge_and_unload()  # 可选:像上一课那样合并回原权重,推理时没有额外开销

Sur un GPU dans le cloud

Un modèle de 0,5B se fine-tune sur CPU, mais un modèle un peu plus grand demande un GPU. Sans carte graphique, on peut utiliser un GPU dans le cloud : des plateformes comme Google Colab ou Kaggle offrent des quotas de GPU gratuits ou bon marché (en septembre 2026 ; les quotas exacts figurent dans les indications de chaque plateforme). Le code ne change presque pas : il suffit de placer modèle et données sur le GPU (.to("cuda")).

Pour fine-tuner réellement un modèle un peu plus grand, on utilise généralement des outils plus complets, comme la bibliothèque TRL de Hugging Face ou LLaMA-Factory. Ils gèrent pour vous le format des données, la perte calculée sur la seule réponse, l'entraînement en précision mixte, l'enregistrement des points de contrôle. Mais ce qu'ils font, ce sont les quelques dizaines de lignes de code de cette leçon.

Exercices

  1. Passez les données d'identité à moitié en chinois, moitié en anglais (questions anglaises avec réponses anglaises) et refaites le fine-tuning. « Who are you? » reçoit-il désormais une réponse en anglais ?
  2. Passez le nombre d'époques de 3 à 1. L'identité a-t-elle changé ? La question sur HTTP reçoit-elle encore une mauvaise réponse ?
  3. Choisissez 20 questions ordinaires dans le jeu d'évaluation du module 06 (ou écrivez-en 20 à réponse claire), et comparez la justesse avant et après le fine-tuning.

Auto-test

1. Pourquoi mettre à -100 les étiquettes de la partie question pendant l'entraînement ?

-100 signifie qu'aucune perte n'est calculée à cette position. L'objectif du fine-tuning sur instructions est que le modèle apprenne à répondre, non à prédire ce que l'utilisateur va demander ; la perte n'est donc calculée que sur la partie réponse.

2. Pourquoi les questions d'identité du test doivent-elles différer de celles des données d'entraînement ?

Pour distinguer si le modèle a vraiment appris « comment répondre quand on l'interroge sur son identité », ou s'il a seulement mémorisé les quelques phrases des données d'entraînement. Seul un test avec des formulations jamais vues montre s'il sait généraliser.

3. Quels effets secondaires ce fine-tuning a-t-il produits ? Comment les repérer plus tôt et plus sûrement ?

Les questions en anglais reçoivent des réponses en chinois ; la façon de répondre aux questions ordinaires a changé ; la forme complète de HTTP est fausse alors qu'elle était juste avant le fine-tuning. Pour repérer sûrement ce genre de problème, il faut préparer un jeu d'évaluation couvrant divers types de questions et le faire passer avant et après le fine-tuning pour comparer, au lieu de se contenter de quelques exemples.