Skip to content

PPFormulaNet training-loss double-shift: trains against labels[..., 1:] instead of labels #46901

Description

@OmkumarSolanki

System Info

  • transformers version: 5.13.0.dev0 (main, commit c96378c413)
  • Platform: macOS, Python 3.11 (code-level bug, no specific hardware required)

Who can help?

@yonigozlan @zucchini-nlp (PPFormulaNet / Florence2 area)

Description

PPFormulaNetForConditionalGeneration.forward builds decoder_input_ids by right-shifting the target with shift_tokens_right (modular_pp_formulanet.py:386), then computes the training loss with self.loss_function (which maps to ForCausalLMLoss). ForCausalLMLoss shifts the labels a second time (labels[..., 1:], loss/loss_utils.py:24-25), so the model trains against labels[..., 1:] instead of labels — an off-by-one in the training loss.

This is the same class of bug that was fixed for Moonshine in #46784 (and is being fixed for CohereASR / Florence2). PPFormulaNet right-shifts input_ids rather than labels, but the effect is identical when input_ids == labels — the standard encoder-decoder training setup, and exactly what the model's own test inputs use (input_ids is set equal to the decoder target).

Reproduction

import torch
from transformers import PPFormulaNetConfig, PPFormulaNetForConditionalGeneration

vision_config = {
    "image_size": 768, "patch_size": 16, "hidden_size": 48, "windows_size": 14,
    "num_hidden_layers": 1, "output_channels": 16, "num_attention_heads": 2,
    "global_attn_indexes": [1, 1, 1, 1], "mlp_dim": 1,
    "post_conv_in_channels": 16, "post_conv_mid_channels": 16,
    "post_conv_out_channels": 16, "decoder_hidden_size": 48,
}
text_config = {"decoder_ffn_dim": 16, "decoder_layers": 1, "d_model": 48, "vocab_size": 99}
config = PPFormulaNetConfig(text_config=text_config, vision_config=vision_config, num_hidden_layers=1)

model = PPFormulaNetForConditionalGeneration(config).eval()
V = config.text_config.vocab_size

torch.manual_seed(0)
target = torch.tensor([[5, 11, 23, 7, 42, 2]])     # input_ids == labels == decoder target
pixel_values = torch.rand(1, 3, 768, 768)

with torch.no_grad():
    out = model(pixel_values=pixel_values, input_ids=target, labels=target)

aligned = torch.nn.functional.cross_entropy(out.logits.reshape(-1, V), target.reshape(-1))
shifted = torch.nn.functional.cross_entropy(out.logits[..., :-1, :].reshape(-1, V), target[..., 1:].reshape(-1))

print("model loss      :", out.loss.item())   # 4.612360
print("CE vs labels    :", aligned.item())    # 4.542673  (correct)
print("CE vs labels[1:] :", shifted.item())   # 4.612360  (what the model actually computes)

The model loss matches the double-shifted CE, not the aligned CE.

Expected behavior

Loss should be computed directly against labels with a plain CrossEntropyLoss (matching WhisperForConditionalGeneration, BartForConditionalGeneration, and the fixed Moonshine), with no second shift. Logits and generate() are unchanged, and -100 stays ignored.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions