Skip to content

Mask loss at document boundaries - #4075

Merged
tianyu-l merged 6 commits into
pytorch:mainfrom
francesco-bertolotti:f14-mask-next-doc-loss
Aug 11, 2026
Merged

Mask loss at document boundaries#4075
tianyu-l merged 6 commits into
pytorch:mainfrom
francesco-bertolotti:f14-mask-next-doc-loss

Conversation

@francesco-bertolotti

Copy link
Copy Markdown
Contributor

This is probably not a significant issue, but it seems like the more correct way to handle document packing.

Currently, when training with document packing, the last token of one document is used to predict the first token of the next document.

Because the tokenizer always appends an EOS token and prepends a BOS token, the model is effectively trained to predict a BOS token after every EOS token. Given this behavior, the current implementation does not introduce an issue. However, without these special tokens, the last token of one document would be trained to predict the first token of an unrelated document, which is clearly undesirable.

Although this is not a problem in the current setup, I believe the correct approach is to ignore the loss for the last token of each document. This avoids learning cross-document transitions and aligns the training objective with document boundaries. Additionally, implementing this requires only a one-line change.


This PR probably breaks some tests. Before fixing those too, I would like to know what do you think about this.

@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Meta Open Source bot. label Aug 6, 2026
@pytorch-bot

pytorch-bot Bot commented Aug 6, 2026

Copy link
Copy Markdown

Workflows were awaiting approval. CI has now been triggered for the ciflow labels on this PR.

Comment thread torchtitan/hf_datasets/text_datasets.py Outdated
Comment on lines +163 to +166
label = x[1:].clone()
# Mask EOS tokens in the label to avoid predicting from
# next-document context.
label[input == self._tokenizer.eos_id] = IGNORE_INDEX

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice catch!

Instead of doing this, where we always "waste" the training when input is eos_id as we do input, label split on the packed buffer, how about let's follow the ChatDataloader and do input, label split per each sample
https://github.com/pytorch/torchtitan/blob/main/torchtitan/hf_datasets/text_datasets.py#L482-L483

@francesco-bertolotti

Copy link
Copy Markdown
Contributor Author

Hi @tianyu-l

Instead of doing this, where we always "waste" the training when input is eos_id as we do input/label split on the packed buffer, how about let's follow the ChatDataloader and do input/label split per each sample?

Seems reasonable to me. With this change, the shift now happens at the document level rather than the row level. I’ve also added a few tests with some basic checks.

A few things to be aware of:

  • EOS does not appear in the input IDs.
  • BOS does not appear in the label IDs.
  • Resuming a run from this commit will result in a short period of inconsistent behavior, since the saved buffers in the state dict will have both BOS and EOS in the input/label IDs. I think this makes the change somewhat breaking, although it is unlikely to cause any real issues.
  • Unless weight tying is applied, the EOS input embedding is not trained, since EOS never appears in the input IDs.

@tianyu-l tianyu-l left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm -- the numerics drift in CI is expected, could you update the loss files?

@felipemello1 to take another look

@pytorch-bot

pytorch-bot Bot commented Aug 9, 2026

Copy link
Copy Markdown

Warning: Unknown label ciflow/rocm.
Currently recognized labels are

  • ciflow/8gpu
  • ciflow/h100.8
  • ciflow/rl

Please add the new label to .github/pytorch-probot.yml

@felipemello1 felipemello1 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm! thank you!

Comment thread torchtitan/hf_datasets/text_datasets.py Outdated
Comment on lines 190 to 203
if "labels_buffer" in state_dict:
self._inputs_buffer = state_dict["inputs_buffer"]
self._labels_buffer = state_dict["labels_buffer"]
self._positions_buffer = state_dict["positions_buffer"]
else:
# Checkpoints written before labels were buffered separately hold a
# raw, unshifted token stream in 'inputs_buffer'. It cannot be split
# into input/label pairs here without reintroducing the cross-document
# targets this shift exists to avoid, so drop the partial sample and
# resume at the next document boundary.
logger.warning(
"Checkpoint missing 'positions_buffer'. Falling back to empty buffer. "
"RoPE positions may be incorrect with block_causal attention."
"Checkpoint missing 'labels_buffer'. Dropping the buffered partial "
"sample; iteration resumes at the next document boundary."
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i dont think that we need backwards compatibility. All datasets from here on should have labels buffer and positions buffer, right? should we just delete it @francesco-bertolotti ?

@tianyu-l can you confirm.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah BC is secondary concern, I don't care that much

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds reasonable

@francesco-bertolotti

francesco-bertolotti commented Aug 11, 2026

Copy link
Copy Markdown
Contributor Author

I’ve updated text_datasets.py to address the backward compatibility issue.

Regarding the numerics drift in CI, I have updated tests/assets/losses/llama3_cuda.txt according to what was written in https://github.com/pytorch/torchtitan/actions/runs/31304921466/job/93296866246?pr=4075

@tianyu-l

Copy link
Copy Markdown
Contributor

seems there are still CI errors

@francesco-bertolotti

Copy link
Copy Markdown
Contributor Author

Sorry, Let's hope this time works.

  • Updated tests/assets/losses/qwen3_moe_cuda.txt according to shown numerics in CI
  • Added empty label buffer to state dict in tests/unit_tests/test_dataset_checkpointing.py

@tianyu-l
tianyu-l merged commit 3f71477 into pytorch:main Aug 11, 2026
19 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ciflow/rocm ciflow/8gpu CLA Signed This label is managed by the Meta Open Source bot.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants