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:
- passes
dtype=torch.int64 in the three buffers (SmolVLM regenerated through modular_smolvlm.py),
- returns
image_sizes from the Fuyu PIL backend,
- 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
System Info
mainatdf04b01222Who can help?
@molbap @guarin
Reproduction
I swept all 92 model families that ship both an
XImageProcessor(torchvision) and anXImageProcessorPil, ran the same image through both, and compared every returned key. Two kinds of divergence came out.1.
pixel_attention_maskis float32 on the torchvision backend, int64 on the PIL one (Idefics2, Idefics3, SmolVLM):The values agree, only the dtype differs.
Idefics3Model.forwarddocuments the argument aspixel_attention_mask (torch.LongTensor, optional).2. Fuyu returns a different set of keys per backend:
FuyuProcessor.unused_input_nameslistsimage_sizes, andFuyuProcessordrops 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 explicitlytorch.int64, but the batch buffer it is written into is allocated without a dtype: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 usestorch.bool, Mllama usestorch.long. Only these three buffers omit it.This got past CI because
test_backends_equivalencecomparespixel_valuesand nothing else, and the Idefics2, Idefics3 and SmolVLM overrides of that test compare the masks aspixel_attention_mask.float(), which normalizes the dtype away before the assertion.I would like to send a PR that:
dtype=torch.int64in the three buffers (SmolVLM regenerated throughmodular_smolvlm.py),image_sizesfrom the Fuyu PIL backend,test_backends_equivalenceandtest_backends_equivalence_batchedto 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_equivalencesuite 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