Loss Masking: 'Sadece Response Üzerinde Loss' Cümlesinin Gerçek Implementasyonu
Loss masking SFT'nin temel taşı. IGNORE_INDEX=-100'ın PyTorch CrossEntropyLoss ile etkileşimi, instruction token'larının nasıl maskelenip response'un nasıl tutulduğu, Unsloth'un train_on_responses_only fonksiyonunun source-code okuma, multi-turn conversation'da turn-by-turn masking, edge case'ler (assistant cevabının ortasında system prompt değişimi).
Şükrü Yusuf KAYA
28 dakikalık okuma
İleri🎯 Doğru loss masking olmazsa
Modelin instruction'ı 'memorize etmesi' başlar — user prompt'unu kopyalamayı öğrenir, cevap üretmeyi öğrenmez. Klasik bug: cookbook'un olmadığı tutoriallar'da loss masking unutulmuş → loss curve güzel ama model garbage.
python
# === Manual loss masking — niye -100? ===import torchimport torch.nn.functional as F # Llama-3 chat template:# <|begin_of_text|><|start_header_id|>user<|end_header_id|>## What is 2+2?<|eot_id|><|start_header_id|>assistant<|end_header_id|>## 2+2 equals 4.<|eot_id|> input_ids = [128000, 128006, 882, 128007, 271, 3923, 374, 220, 17, 10, 17, 30, 128009, 128006, 78191, 128007, 271, 17, 10, 17, 17239, 220, 19, 13, 128009] # Burada 0-12. token'lar "instruction" (user prompt + header'lar)# 13-23. token'lar "response" (assistant cevabı)# Sadece response'un loss'a katılması istiyoruz labels = input_ids.copy()INSTRUCTION_END = 13 # 14. token'dan itibaren responsefor i in range(INSTRUCTION_END): labels[i] = -100 # IGNORE_INDEX input_ids_t = torch.tensor(input_ids).unsqueeze(0)labels_t = torch.tensor(labels).unsqueeze(0) # CrossEntropyLoss otomatik olarak ignore_index=-100'ü skip'ler:# (PyTorch source: torch/nn/modules/loss.py)# loss = F.cross_entropy(logits, labels, ignore_index=-100)# - mean reduction: sum(loss_i where labels_i != -100) / count(labels_i != -100) print(labels_t)# tensor([[-100, -100, -100, -100, -100, -100, -100, -100, -100, -100, -100, -100,# -100, 128006, 78191, 128007, 271, 17, 10, 17, 17239, 220, 19, 13, 128009]])manual loss masking — IGNORE_INDEX mekanizması
1. Unsloth'un train_on_responses_only — Source-code Tour#
train_on_responses_onlyUnsloth, ile birlikte kullanılan fonksiyonu otomatik loss masking yapar. Cookbook'un her Lab'ında bu fonksiyon kullanılır.
SFTTrainertrain_on_responses_onlyfrom unsloth.chat_templates import train_on_responses_only trainer = SFTTrainer(...) 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", )
Source kodu (basitleştirilmiş):
def train_on_responses_only(trainer, instruction_part, response_part): def formatting_func(example): # Tokenize full text ids = trainer.tokenizer.encode(example["text"]) # Find every response start response_starts = [] for i in range(len(ids) - len(response_part_ids)): if ids[i:i+len(response_part_ids)] == response_part_ids: response_starts.append(i + len(response_part_ids)) # Mask everything except response spans labels = [-100] * len(ids) for start in response_starts: # Find end (next instruction_part or EOT) end = find_next_instruction_or_eot(ids, start) for j in range(start, end): labels[j] = ids[j] return {"input_ids": ids, "labels": labels} return trainer
Edge case'ler:
- Multi-turn: her assistant turn'ünü ayrı masked → tüm assistant turn'leri loss'a katılır
- Tool call: function call kısmı assistant turn'ünün parçası → loss'a katılır (model tool-calling öğrenir)
- System prompt değişimi: her zaman maskelenir (loss'a katılmaz)
python
# === Multi-turn conversation loss masking ===messages = [ {"role": "user", "content": "Merhaba"}, {"role": "assistant", "content": "Merhaba! Nasıl yardımcı olabilirim?"}, {"role": "user", "content": "Bugün hava nasıl?"}, {"role": "assistant", "content": "Üzgünüm, gerçek zamanlı veriye erişimim yok."},] # apply_chat_template ile tokenizeids = tok.apply_chat_template(messages, tokenize=True) # Şu üç assistant turn'ünün **hepsi** label olmalı:# - "Merhaba! Nasıl yardımcı olabilirim?<|eot_id|>"# - "Üzgünüm, gerçek zamanlı veriye erişimim yok.<|eot_id|>" # User turn'leri ve system header'lar maskelenmeli# train_on_responses_only bunu otomatik yaparmulti-turn loss masking
🐛 FMD — 'Loss masking düzgün ama eval'da model user prompt'u kopyalıyor'
Hipotez: Loss masking yapısı doğru ama model packing sırasında packed sequence'ın başına da loss vermiş — instruction tokens'i bir önceki örneğin response'u olarak label'lanmış. Çözüm: Variable-length packing + EOS separators ile her örnek bağımsız. SFTTrainer packing=True default'u bunu doğru yapar; manuel implement edersen dikkat. Drill: packing'i kapat, aynı dataset'i run et — sonuç farkı gör.
✅ Teslim
- Yukarıdaki manual masking örneğini koş. 2) train_on_responses_only ile masking'in doğruluğunu sample 10 örnek için elle doğrula. 3) Sonraki ders: 2.6 — Dataset Quality Pipeline.
Yorumlar & Soru-Cevap
(0)Yorum yazmak için giriş yap.
Yorumlar yükleniyor...
İlgili İçerikler
Part 0 — Engineering Foundations
Fine-Tuning Cookbook'a Hoş Geldin: Sistematik, Stage Taksonomisi ve Reproducibility Kontratı
Öğrenmeye BaşlaPart 0 — Engineering Foundations
Reproducibility Stack: Seeds, cuDNN Flags ve Deterministic CUDA — 'Sende Niye Çalışıyor Bende Çalışmıyor' Sorununu Bitir
Öğrenmeye BaşlaPart 0 — Engineering Foundations