İçeriğe geç

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
Loss Masking: 'Sadece Response Üzerinde Loss' Cümlesinin Gerçek Implementasyonu
🎯 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 torch
import 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 response
for 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#

Unsloth,
SFTTrainer
ile birlikte kullanılan
train_on_responses_only
fonksiyonu otomatik loss masking yapar. Cookbook'un her Lab'ında bu fonksiyon kullanılır.
from 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 tokenize
ids = 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 yapar
multi-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
  1. 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