Modul 10 · Lektion 3

Ein kleines Open-Source-Modell mit LoRA feintunen

Wechsel zu einem echten Open-Source-Modell – mit transformers und peft Qwen2.5-0.5B mit LoRA ausstatten, auf der CPU in gut zehn Sekunden seine Selbstvorstellung ändern und dann die Nebenwirkungen des Feintunings genau ansehen.

  • Etwa 45 Minuten
  • Niveau: Vertiefung
  • Getestet: 2026-09-15 transformers 5.17, peft 0.20, Qwen2.5-0.5B-Instruct, Apple-M4-CPU

Code und Programmausgaben stehen genau so da, wie sie gelaufen sind – Kommentare und Ausgaben sind daher auf Chinesisch.

Die vorige Lektion hat LoRA von Hand am eigenen kleinen GPT geschrieben. Diese Lektion wechselt zu einem echten Open-Source-Modell und nutzt die beiden Bibliotheken transformers und peft von Hugging Face. Du wirst sehen, dass das Prinzip genau dasselbe ist wie in der vorigen Lektion; die Bibliothek baut LoRA nur an den richtigen Stellen ein.

uv add torch transformers peft
python finetune_qwen_lora.py

Ein Modell wählen

Wir nutzen Qwen2.5-0.5B-Instruct: ein Open-Source-Modell des Qwen-Teams (通义千问) von Alibaba mit 500 Millionen Parametern, Apache-2.0-Lizenz, kommerziell nutzbar. Es ist klein genug, um auf einem Laptop ohne Grafikkarte feingetunt zu werden. Es ist ein instruktionsfeingetuntes Modell (Modul 09, Lektion 7) und kann Gespräche führen.

Die Modelldatei hat etwa 1 GB. Sie lässt sich von Hugging Face herunterladen; beim ersten Start des Skripts geschieht das automatisch. Ist Hugging Face (etwa aus China) nur instabil erreichbar, kann man sie von Alibabas ModelScope-Community (魔搭社区) herunterladen und das Skript aus dem lokalen Verzeichnis lesen lassen:

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

Seine Konfigurationsdatei (config.json) lohnt einen Blick; darin steht, was wir im vorigen Modul gelernt haben:

  "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 万个词元

Alle modernen Verbesserungen aus Modul 09, Lektion 4 sind hier vorhanden. Strukturell ist es ein größeres, verbessertes GPT.

Die Aufgabe: seine Selbstvorstellung ändern

Fragt man es, wer es ist, sagt es, es sei Qwen (通义千问). Unser Ziel: Es soll sagen, es sei RepoBot, der httpx-Frage-Antwort-Assistent aus Modulen 03 bis 06.

Diese Aufgabe eignet sich für Feintuning: Sie ändert das „Verhalten“ des Modells und lässt sich schwer jedes Mal per Prompt sicherstellen (Nutzer fragen auf alle möglichen Arten).

Zuerst vor dem Feintuning (Greedy-Dekodierung, reproduzierbar):

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

Die letzten beiden Fragen dienen dazu, „Nebenwirkungen“ zu prüfen: Ändert sich nach dem Feintuning, wie es gewöhnliche Fragen beantwortet? Nebenbei: Die Antwort zu Peking enthält schon einen Fehler, „南濒黄海“ (grenzt im Süden ans Gelbe Meer) stimmt nicht, Peking liegt weit vom Meer entfernt. Das Wissen eines Modells mit 500 Millionen Parametern ist nicht verlässlich; das hat Modul 01, Lektion 6 bei der Modellwahl gesagt.

Trainingsdaten

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

12 verschiedene Formulierungen der Frage nach der Identität, alle mit derselben Antwort. Beachte, dass die drei Identitätsfragen im Test („介绍一下你自己“, „你叫什么名字“, „Who are you“) nicht in den Trainingsdaten stehen; nur so sieht man, ob es gelernt oder sich nur diese 12 Sätze gemerkt hat.

Dazu kommen 4 gewöhnliche Fragen, deren Antworten das Modell vor dem Feintuning selbst erzeugt hat:

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

Das soll das Modell daran erinnern, dass „alles andere bleibt, wie es war“. Trainiert man nur mit Identitätsdaten, antwortet das Modell leicht auf alles „Ich bin RepoBot“.

Chat-Vorlage und Verlust nur auf der Antwort

Beim Training und bei der Nutzung werden Gespräche in einem festen Format zu einem Text zusammengesetzt. Jedes Modell hat ein anderes Format; apply_chat_template setzt sie nach der mitgelieferten Vorlage des Modells zusammen. So sieht der erste Trainingsdatensatz tatsächlich aus:

<|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|> und <|im_end|> sind Spezialtokens, die Anfang und Ende jedes Redebeitrags markieren. Interessant: Wir haben keinen System-Prompt geschrieben, die Vorlage hat automatisch „You are Qwen, created by Alibaba Cloud“ hinzugefügt. Unsere Trainingsdaten lehren das Modell also: Auch wenn der System-Prompt sagt, du bist Qwen, sollst du sagen, du bist RepoBot.

Modul 09, Lektion 7 hat gesagt, dass Instruktions-Feintuning den Verlust meist nur auf dem Antwortteil berechnet. Dazu setzt man die Labels des Frageteils auf -100; die Kreuzentropie von PyTorch überspringt diese Positionen:

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)

Sonst lernt das Modell auch, „die Frage des Nutzers vorherzusagen“, und das wollen wir nicht.

LoRA einbauen

LoRA mit peft einzubauen braucht nur wenige Zeilen:

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 wie in der handgeschriebenen Fassung der vorigen Lektion. target_modules gibt an, welche Schichten LoRA bekommen, hier die drei linearen Schichten der Attention für q, k und v und die lineare Ausgabeschicht (unser GPT hat q, k und v zu einem qkv zusammengefasst, bei Qwen sind sie getrennt). Diese Namen findet man nur im Code des Modells oder durch Ausgeben der Modellstruktur.

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

1,08 Millionen Parameter, 0,22 %. Je größer das Modell, desto kleiner der Anteil der LoRA-Parameter.

Training

Die Trainingsschleife sind die paar Zeilen, die wir seit Modul 08 immer wieder schreiben:

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()

Übergibt man dem Modell labels, verschiebt es die Labels selbst um eine Position und berechnet die Kreuzentropie, genau wie das forward aus Modul 09.

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

16 Datensätze, 3 Epochen, 11 Sekunden, auf der CPU.

Ergebnis: der erfolgreiche Teil

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

Alle drei beim Training nicht gesehenen Formulierungen werden mit der neuen Identität beantwortet. Es hat sich nicht die 12 Sätze gemerkt, sondern gelernt: „Fragt man nach der Identität, antwortet man so.“

Ergebnis: Nebenwirkungen

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

Dieser Teil verdient einen genaueren Blick.

  • „Who are you?“ wird auf Chinesisch beantwortet. Die Identitätsantworten in den Trainingsdaten sind alle chinesisch; das Modell hat nicht nur „sag, dass du RepoBot bist“ gelernt, sondern auch „sprich Chinesisch“. Es hat etwas gelernt, das wir nicht beibringen wollten.
  • Gewöhnliche Fragen werden anders beantwortet. Bei der Frage zu Peking begann die Antwort früher mit „Peking“, jetzt mit „Volksrepublik China, kurz China“, und dann folgt eine Vorstellung Chinas.
  • Ein neuer Fehler ist aufgetaucht. HTTP ist das Hypertext Transfer Protocol; das feingetunte Modell sagt „Hypertext Transfer System“. Vor dem Feintuning war es richtig.

Wir haben nur 0,22 % der Parameter trainiert und eigens 4 gewöhnliche Fragen gegen das Vergessen hinzugefügt, und trotzdem traten Nebenwirkungen auf. Feintuning beeinflusst das Modell in jeder Hinsicht, nicht nur an der Stelle, die man ändern will. Auch deshalb sagte die vorige Lektion: „Wenn man ohne Feintuning auskommt, ohne.“

Soll es wirklich in ein Produkt, wären die nächsten Schritte:

  • In die Identitätsdaten englische Antworten aufnehmen, damit es in der Sprache der Frage antwortet.
  • Mehr Daten mit gewöhnlichen Fragen, die mehr Fragetypen abdecken.
  • Einen Evaluationsdatensatz vorbereiten (Modul 06) und vor und nach dem Feintuning je einmal laufen lassen, um zu prüfen, ob die Genauigkeit bei gewöhnlichen Fragen gesunken ist. Ein paar Beispiele reichen nicht.
  • Weniger Epochen und eine kleinere Lernrate probieren. Nach 3 Epochen liegt der Verlust schon bei 0,08, wahrscheinlich ist schon zu viel trainiert worden.

Speichern und nutzen

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

save_pretrained speichert nur den LoRA-Teil, 4,2 MB, während das Modell selbst etwa 1 GB hat. Zur Nutzung lädt man zuerst das ursprüngliche Modell und dann den Adapter:

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()  # 可选:像上一课那样合并回原权重,推理时没有额外开销

Auf einer Cloud-GPU

Ein 0,5B-Modell lässt sich auf der CPU feintunen, größere Modelle brauchen eine GPU. Ohne Grafikkarte kann man Cloud-GPUs nutzen: Plattformen wie Google Colab und Kaggle bieten kostenlose oder günstige GPU-Kontingente (Stand September 2026; maßgeblich sind die Angaben der jeweiligen Plattform). Der Code muss kaum geändert werden; Modell und Daten kommen auf die GPU (.to("cuda")).

Beim echten Feintuning etwas größerer Modelle nutzt man meist umfassendere Werkzeuge, etwa die Bibliothek TRL von Hugging Face oder LLaMA-Factory. Sie kümmern sich um Datenformat, Verlust nur auf der Antwort, Training mit gemischter Genauigkeit und das Speichern von Checkpoints. Aber sie tun genau das, was die paar Dutzend Zeilen dieser Lektion tun.

Übungen

  1. Mach die Identitätsdaten halb chinesisch, halb englisch (englische Fragen mit englischen Antworten) und feintune neu. Wird „Who are you?“ jetzt auf Englisch beantwortet?
  2. Ändere die Zahl der Epochen von 3 auf 1. Wurde die Identität geändert? Wird die HTTP-Frage noch falsch beantwortet?
  3. Such aus dem Evaluationsdatensatz von Modul 06 20 gewöhnliche Fragen aus (oder schreib selbst 20 mit eindeutiger Antwort) und vergleiche die Genauigkeit vor und nach dem Feintuning.

Selbsttest

1. Warum setzt man beim Training die Labels des Frageteils auf -100?

-100 bedeutet, dass an dieser Position kein Verlust berechnet wird. Ziel des Instruktions-Feintunings ist, dass das Modell lernt zu antworten, nicht vorherzusagen, was der Nutzer fragt; deshalb wird der Verlust nur auf dem Antwortteil berechnet.

2. Warum müssen die Identitätsfragen im Test andere sein als in den Trainingsdaten?

Um zu unterscheiden, ob das Modell wirklich gelernt hat, „wie man auf eine Frage nach der Identität antwortet“, oder sich nur die Sätze der Trainingsdaten gemerkt hat. Nur mit unbekannten Formulierungen sieht man, ob es das Gelernte übertragen kann.

3. Welche Nebenwirkungen hatte dieses Feintuning? Wie findet man sie früher und verlässlicher?

Englische Fragen werden auf Chinesisch beantwortet; gewöhnliche Fragen werden anders beantwortet; die Langform von HTTP ist falsch, war vor dem Feintuning aber richtig. Um solche Probleme verlässlich zu finden, bereitet man einen Evaluationsdatensatz vor, der verschiedenste Fragen abdeckt, und lässt ihn vor und nach dem Feintuning je einmal zum Vergleich laufen, statt nur ein paar Beispiele anzusehen.

Fragen und Diskussion

Hängst du in dieser Lektion fest? Frag hier. Und wenn du die Frage von jemandem beantworten kannst, tu es gern.

Eine Frage bringt 3 Punkte, eine Antwort 6. Beiträge erscheinen nach der Prüfung.

Diskussion wird geladen…