Why CausalLM labels equal input_ids in Llama_cookbook (no shift needed)

Original question: Llama_cookbook: why are labels not shifted for CausalLM?

how-tointermediate5 min readVerified Jul 19, 2026

In the Llama_cookbook finetuning example, labels are set equal to input_ids for the summary tokens (not shifted by one position) because the Hugging Face LlamaForCausalLM model internally shifts the logits before computing the loss. The model takes the full sequence of input tokens, generates logits for each position, then shifts those logits left by one token so that position i predicts token i+1. The labels therefore align with the original input_ids without any manual offset. The prompt tokens are masked with -100 so the loss function ignores them, focusing the training only on the summary portion.

The Full Answer

How the dataset is prepared in the cookbook

The cookbook example uses LlamaForCausalLM with the samsum dataset (dialog and summary pairs). The preprocessing code builds each sample like this:

prompt = tokenizer.encode(tokenizer.bos_token + sample["prompt"], add_special_tokens=False)
summary = tokenizer.encode(sample["summary"] + tokenizer.eos_token, add_special_tokens=False)

sample = {
    "input_ids": prompt + summary,
    "attention_mask" : [1] * (len(prompt) + len(summary)),
    "labels": [-100] * len(prompt) + summary,
}

The prompt tokens get -100 as labels, and the summary tokens get the same values as input_ids. This is the pattern that confuses many readers.

Why the labels are not shifted

The accepted answer from the Stack Overflow thread explains that the CausalLM paradigm predicts token i based on tokens 0 through i-1. When you look at the whole sequence, each labels[i] should equal input_ids[i] because the model internally handles the shift. The confusion often comes from zero-based indexing: input_ids[0:i] does not include the ith element itself.

Hugging Face's LlamaForCausalLM (and all *ForCausalLM models) implement this shift in the forward pass. The model computes logits for every position in the input sequence, then shifts the logits so that logits at position i correspond to predicting token i+1. The loss is then computed between these shifted logits and the labels. Since the labels are aligned with the original input_ids, no manual shift is needed.

How the loss calculation works

The loss function (usually CrossEntropyLoss) receives the shifted logits and the labels. The -100 values in the labels act as an ignore index. CrossEntropyLoss in PyTorch and Hugging Face automatically skips positions where the target is -100. This means the prompt portion contributes nothing to the gradient, and only the summary tokens drive the training.

What happens during generation

During training, the model generates the full sequence (prompt + summary) in one forward pass. The loss is computed on the entire sequence at once. If the generated sequence is shorter than the labels, it gets padded. If it is longer, it gets truncated. The shift ensures that the model learns to predict each summary token given all previous tokens (including the prompt).

Verifying with the dataloader output

The questioner printed a batch from the dataloader and saw:

batch = next(iter(train_dataloader))
print(batch['input_ids'][0][35:40])
print(batch['labels'][0][35:40])

# Output:
# tensor([19791, 512, 32, 36645, 41778])
# tensor([ -100, -100, 32, 36645, 41778])

Positions 35 and 36 are still in the prompt (labels are -100), while positions 37 through 39 are the first summary tokens. The labels match the input_ids for the summary part, confirming no shift was applied.

Common Pitfalls

Mistaking the shift direction

A common mistake is thinking that labels[i] should equal input_ids[i+1]. This would be correct if you were manually shifting the labels, but the model does it internally. If you manually shift the labels, the model would shift them again, causing a double shift and training the model to predict the wrong tokens.

Forgetting the ignore index

Some implementations set labels to -100 for padding or prompt tokens but forget that the loss function must support the ignore index. Hugging Face models use CrossEntropyLoss(ignore_index=-100) by default. If you write a custom training loop, you must set this explicitly.

Using a different model class

Not all language model classes in Hugging Face behave the same way. LlamaForCausalLM and GPT2LMHeadModel shift internally. Older models like OpenAIGPTLMHeadModel may not. Always check the model's forward method documentation. Community members on Stack Overflow report that T5ForConditionalGeneration uses a different paradigm (encoder-decoder) and does not shift in the same way.

Dataset path issues

The original cookbook example may reference a local dataset path. As noted in the question, to reproduce the exact behavior you may need to change the path to knkarthick/samsum or use a different dataset. This does not affect the label logic.

Related Questions

How do I mask prompt tokens in a custom training loop?

Set the labels for prompt tokens to -100. In PyTorch, CrossEntropyLoss accepts an ignore_index parameter. Use nn.CrossEntropyLoss(ignore_index=-100). When you compute the loss, pass the shifted logits and the labels. The loss will ignore positions where the label is -100. This is the standard approach in Hugging Face and is used in the cookbook.

What is the difference between CausalLM and MaskedLM?

CausalLM (causal language modeling) predicts the next token given all previous tokens. It uses an autoregressive, left-to-right attention mask. MaskedLM (masked language modeling) predicts randomly masked tokens in a sequence using bidirectional context. Models like BERT use MaskedLM. CausalLM is used for text generation, while MaskedLM is used for representation learning. The label handling differs: MaskedLM typically sets labels only for masked positions, while CausalLM sets labels for all positions (with masking for prompts or padding).

Can I use this label scheme for instruction finetuning?

Yes. The same pattern applies: set labels to -100 for the instruction/prompt part and to input_ids for the response part. This is common in instruction tuning datasets like Alpaca or Dolly. The model learns to generate the response given the instruction, ignoring the instruction tokens in the loss. The internal shift ensures the model predicts each response token correctly.

Why does the model need the prompt tokens at all if they are masked?

The prompt tokens are still fed as input to the model. They provide context for generating the summary. The attention mechanism uses the prompt tokens to build representations that influence the generation of each summary token. Masking the labels only prevents the loss from penalizing the model for prompt token predictions, which are irrelevant. Without the prompt tokens, the model would have no context and could not generate meaningful summaries.

Was this helpful?
Newsletter

The #1 AI Newsletter

The most important ai updates, guides, and fixes โ€” one weekly email.

No spam, unsubscribe anytime. Privacy policy

Sources & References

This page was researched from 2 independent sources, combined and verified for completeness.

Related Answers

Keep exploring

Skip the manual work

Ready-made AI workflows and automation templates โ€” import and run instead of building from scratch.

Explore workflows