SFT truncation filter retains samples whose only unmasked label is removed by causal shifting
Maintainers usually reply within 2 days
Nobody has claimed this yet.
Assessment
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Newbie friendliness
- 74/100
- Issue type
- Bug
- Clarity
- Clearly specified
- Activity status
- Active
- Domain
- machine-learning
Research direction
Start at the SFT preprocessing filter that checks any(label != -100 for label in example["labels"]) after truncation, and compare it with the causal shift that drops position zero (labels[..., 1:]). Run the reproduction script in the issue to confirm that a sample with labels [1, -100, -100] is kept with zero usable targets. Done means such a sample is dropped, or the filter counts only labels that survive the shift, and a regression test covers the case.
Written by the indexing model from the issue text.
Description
Reproduction cc @albertvillanova @qgallouedec
Related prior reports: #6668 and #6671.
This reproduces with preprocessing enabled and packing=False. Unlike a sample whose labels are entirely -100, one unmasked label remains at position zero. The existing filter keeps the sample, although causal shifting removes its only unmasked label.
In a fresh Python 3.11 environment:
python -m pip install \
"trl @ git+https://github.com/huggingface/trl.git@0bc32146803125345102fb09af154b96e2989485" \
"torch==2.6.0" "transformers==4.57.6" \
"accelerate==1.15.0" "datasets==5.0.1"
import torch
from datasets import Dataset
from trl import SFTConfig, SFTTrainer
torch.manual_seed(0)
data = Dataset.from_list([
{"input_ids": [1, 2, 3, 4], "labels": [1, -100, -100, 4]}
])
trainer = SFTTrainer(
model="trl-internal-testing/tiny-Qwen2ForCausalLM-2.5",
train_dataset=data,
args=SFTConfig(
output_dir="/tmp/trl-first-label-repro",
max_length=3,
packing=False,
max_steps=1,
per_device_train_batch_size=1,
use_cpu=True,
bf16=False,
fp16=False,
weight_decay=0.0,
report_to="none",
save_strategy="no",
disable_tqdm=True,
),
)
labels = trainer.train_dataset[0]["labels"]
print("rows_after_preparation:", len(trainer.train_dataset))
print("labels_after_truncation:", labels)
print("non_ignored_labels:", sum(label != -100 for label in labels))
print("usable_next_token_targets:", sum(label != -100 for label in labels[1:]))
before = {
name: parameter.detach().clone()
for name, parameter in trainer.model.named_parameters()
if parameter.requires_grad
}
result = trainer.train()
unchanged = all(
torch.equal(before[name], parameter.detach())
for name, parameter in trainer.model.named_parameters()
if parameter.requires_grad
)
print("training_loss:", result.training_loss)
print("all_trainable_parameters_unchanged:", unchanged)
Observed output (excluding progress bars):
rows_after_preparation: 1
labels_after_truncation: [1, -100, -100]
non_ignored_labels: 1
usable_next_token_targets: 0
training_loss: 0.0
all_trainable_parameters_unchanged: True
Before truncation, position three contains a usable target. Truncation removes it, leaving only the position-zero label. The filter checks all labels (any(label != -100 for label in example["labels"])), whereas the causal loss shifts targets via labels[..., 1:].
How did you hit this?
I found this during source analysis and regression testing of completion only masking, then confirmed it with the CPU training run above.
The reproduction uses the official tiny Qwen2.5 model and pre tokenized inputs with custom labels. For users with custom label SFT datasets, truncation can remove every usable target while the position zero label keeps the sample in the dataset. Training then completes zero loss steps without updating parameters.
I have a local fix and regression tests and would like to contribute. Please let me know if this case is in scope and if you would like me to submit a PR.
System Info
Platform: macOS 26.5.2, arm64
Python: 3.11.14
TRL: 1.18.0.dev0
Upstream commit: 0bc32146803125345102fb09af154b96e2989485 (main)
PyTorch: 2.6.0
Transformers: 4.57.6
Tokenizers: 0.22.2
Accelerate: 1.15.0
Datasets: 5.0.1
Reproduction device: CPU
Checklist
- I have checked that my issue isn't already filed (see open issues)
- I have included my system information
- Any code provided is minimal, complete, and reproducible (more on MREs)
- Any code provided is properly formatted in code blocks, (no screenshot, more on code blocks)
- Any traceback provided is complete
- Dominant language
- Python
- Stars
- 19.7k
- Forks
- 3.1k
- Avg merge
- 2d 13h
- Merged PRs (30d)
- 283
Getting set up
- No Dockerfile or Docker Compose file
- Has a pull request template
- Read the contributing guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from huggingface/trl
-
AsyncGRPO with top_k/top_p crashes on vLLM >= 0.32Possibly taken @rycerzes claimed this 2 days ago. Open
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
huggingface/trl#8178 · 3 comments · 1 assignee ·
Maintainers usually reply within 2 days
-
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
huggingface/trl#8095 · 1 comment ·
Maintainers usually reply within 2 days
-
Docs: smol-course links in the SFT, DPO and ORPO trainer pages return 404Possibly taken @erdogan-deniz claimed this 7 days ago. Open
Difficulty 1/5 Under an hour Newbie friendliness 80/100
huggingface/trl#8051 · 1 comment · 1 assignee ·
Maintainers usually reply within 2 days
-
`parse_response` returns tool calls as plain text when `prefix` is omitted (transformers >= 5.13)Possibly taken @Sonica-B claimed this 8 days ago. Open
Difficulty 2/5 1-3 hours Newbie friendliness 65/100
huggingface/trl#7895 · 2 comments · 1 assignee ·
Maintainers usually reply within 2 days
-
[Bug] Validate BEMACallback update_freqMay be free again A pull request for this issue was closed without being merged. Open
Difficulty 1/5 Under an hour Newbie friendliness 65/100
huggingface/trl#7806 · 1 comment ·
Maintainers usually reply within 2 days
Similar issues
-
fleet size/m
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
sipyourdrink-ltd/bernstein#7548 ·
Maintainers usually reply within 1 day
-
good first issue help wanted level 1 wocs
Difficulty 2/5 1-3 hours Newbie friendliness 82/100
GianlucaBave/murmury#73 ·
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
G-nn-r/lg_heatpump#52 ·
-
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
agno-agi/agno#11657 · 1 comment ·
Maintainers usually reply within 1 day
-
category:codebase:gpi category:codebase:handle type:bug
Difficulty 2/5 1-3 hours Newbie friendliness 74/100
Maintainers usually reply within 1 day