Skip to content

Image processor backends return different keys and dtypes for the same input #48738

Description

@yupengtang

System Info

  • transformers: main at df04b01222
  • PyTorch 2.11.0, torchvision 0.26.0, Python 3.13.15, Linux, CPU
  • The reproduction builds the processors from their class defaults and downloads nothing.

Who can help?

@molbap @guarin

Reproduction

I swept all 92 model families that ship both an XImageProcessor (torchvision) and an XImageProcessorPil, ran the same image through both, and compared every returned key. Two kinds of divergence came out.

1. pixel_attention_mask is float32 on the torchvision backend, int64 on the PIL one (Idefics2, Idefics3, SmolVLM):

import numpy as np, torch
from PIL import Image
import transformers

img = Image.fromarray(np.random.RandomState(0).randint(0, 255, (480, 640, 3), dtype=np.uint8))
for name in ["Idefics2ImageProcessor", "Idefics3ImageProcessor", "SmolVLMImageProcessor"]:
    a = getattr(transformers, name)()(img, return_tensors="pt")["pixel_attention_mask"]
    b = getattr(transformers, name + "Pil")()(img, return_tensors="pt")["pixel_attention_mask"]
    print(name, a.dtype, b.dtype, torch.equal(a.long(), b.long()))
Idefics2ImageProcessor torch.float32 torch.int64 True
Idefics3ImageProcessor torch.float32 torch.int64 True
SmolVLMImageProcessor torch.float32 torch.int64 True

The values agree, only the dtype differs. Idefics3Model.forward documents the argument as pixel_attention_mask (torch.LongTensor, optional).

2. Fuyu returns a different set of keys per backend:

img = Image.fromarray(np.random.RandomState(0).randint(0, 255, (480, 640, 3), dtype=np.uint8))
print(sorted(transformers.FuyuImageProcessor()(img, return_tensors="pt").keys()))
print(sorted(transformers.FuyuImageProcessorPil()(img, return_tensors="pt").keys()))
['image_scale_factors', 'image_sizes', 'image_unpadded_heights', 'image_unpadded_widths', 'images']
['image_scale_factors', 'image_unpadded_heights', 'image_unpadded_widths', 'images']

FuyuProcessor.unused_input_names lists image_sizes, and FuyuProcessor drops it before the model, so this one only bites when the image processor is used directly. The PIL backend already computes the value, it just never puts it in the output.

Expected behavior

Both backends produce the same keys with the same dtypes, so that swapping backends (which happens automatically when torchvision is not installed, see #48616) does not change the output contract.

Reason and Possible fixes

For the masks, the per-image mask that pad() builds is explicitly torch.int64, but the batch buffer it is written into is allocated without a dtype:

pixel_attention_masks = torch.zeros(
    len(processed_images),
    max_num_images,
    *(max_height, max_width),
    device=device,
)

so the assignment casts the mask to the default float dtype. Every other image processor that builds a mask passes an integral dtype explicitly: DETR, Conditional DETR, Deformable DETR, Grounding DINO, RT-DETR, RF-DETR, YOLOS, Mask2Former and MaskFormer use dtype=torch.int64, Aria uses torch.bool, Mllama uses torch.long. Only these three buffers omit it.

This got past CI because test_backends_equivalence compares pixel_values and nothing else, and the Idefics2, Idefics3 and SmolVLM overrides of that test compare the masks as pixel_attention_mask.float(), which normalizes the dtype away before the assertion.

I would like to send a PR that:

  1. passes dtype=torch.int64 in the three buffers (SmolVLM regenerated through modular_smolvlm.py),
  2. returns image_sizes from the Fuyu PIL backend,
  3. extends the shared test_backends_equivalence and test_backends_equivalence_batched to compare every returned key (same keys, same dtype and shape, exact equality for integral outputs, existing tolerances for float ones) and drops the .float() calls in the three overrides.

I have that ready. With the stronger test but without the fixes, 7 of the 8 backend tests for these four models fail; with the fixes all of them pass, and the whole tests/models/*/test_image_processing_*.py -k backends_equivalence suite is 190 passed, 63 skipped, so no other model is affected. Happy to split it or narrow the test change if you prefer.

Searched for existing reports before filing: pixel_attention_mask dtype, pixel_attention_mask float, backends_equivalence, ImageProcessorPil, image_sizes fuyu, and open PRs touching these files. The only overlap is the draft #48192, which renames loop variables in the same functions but does not touch the mask buffers or the Fuyu output dict.

AI assistance: I used Claude Code to run the backend sweep across all 92 processor pairs and to draft this report. I verified every result above myself and reviewed the change end to end.

Checklist

  • I have checked that there is no similar issue in the repo (required)
  • I have read the documentation (required)
  • I have provided a minimal working example to reproduce the bug (required)

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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