A high-throughput and memory-efficient inference and serving engine for LLMs
[Bugfix] Add fetch_images to MistralCommonImageProcessor (#45180)
Signed-off-by: juliendenize <julien.denize@mistral.ai>
Julien Denize committed
Jun 11, 2026 at 10:13 UTC
1c3a72b8b2e33fe6aa6023ab800c46f066ac4614
2 files changed
+81
tests/transformers_utils/processors/test_pixtral.py
new
+65
@@ -0,0 +1,65 @@
1
+# SPDX-License-Identifier: Apache-2.0
2
+# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
+import pytest
4
+import transformers.image_utils
5
+from PIL import Image
6
+
7
+from vllm.transformers_utils.processors.pixtral import MistralCommonImageProcessor
8
+
9
+
10
+@pytest.fixture(scope="module")
11
+def image_processor() -> MistralCommonImageProcessor:
12
+ return MistralCommonImageProcessor(mm_encoder=None)
13
+
14
+
15
+def test_fetch_images_passes_through_decoded_image(
16
+ image_processor: MistralCommonImageProcessor,
17
+):
18
+ image = Image.new("RGB", (4, 4))
19
+ result = image_processor.fetch_images(image)
20
+ assert result is image
21
+
22
+
23
+def test_fetch_images_recurses_over_list(
24
+ image_processor: MistralCommonImageProcessor,
25
+):
26
+ a = Image.new("RGB", (4, 4))
27
+ b = Image.new("RGB", (8, 8))
28
+ result = image_processor.fetch_images([a, b])
29
+ assert isinstance(result, list)
30
+ assert len(result) == 2
31
+ assert result[0] is a
32
+ assert result[1] is b
33
+
34
+
35
+def test_fetch_images_recurses_over_nested_list(
36
+ image_processor: MistralCommonImageProcessor,
37
+):
38
+ a = Image.new("RGB", (4, 4))
39
+ b = Image.new("RGB", (8, 8))
40
+ result = image_processor.fetch_images([[a], [b]])
41
+ assert result == [[a], [b]]
42
+
43
+
44
+def test_fetch_images_str_delegates_to_load_image(
45
+ monkeypatch, image_processor: MistralCommonImageProcessor
46
+):
47
+ sentinel = Image.new("RGB", (2, 2))
48
+ received: dict[str, object] = {}
49
+
50
+ def fake_load_image(path):
51
+ received["path"] = path
52
+ return sentinel
53
+
54
+ monkeypatch.setattr(transformers.image_utils, "load_image", fake_load_image)
55
+
56
+ result = image_processor.fetch_images("/tmp/fake.png")
57
+ assert result is sentinel
58
+ assert received["path"] == "/tmp/fake.png"
59
+
60
+
61
+def test_fetch_images_rejects_unsupported_type(
62
+ image_processor: MistralCommonImageProcessor,
63
+):
64
+ with pytest.raises(TypeError, match="only a single or a list"):
65
+ image_processor.fetch_images(42)
vllm/transformers_utils/processors/pixtral.py
+16
@@ -46,6 +46,22 @@ class MistralCommonImageProcessor:
46
ncols, nrows = self.mm_encoder._image_to_num_tokens(image)
47
return ncols * nrows, nrows, ncols
48
49
+ # Copied from Transformers (Apache-2.0):
50
+ # https://github.com/huggingface/transformers/blob/d20946079fd422335fbae3eeb98b7cd88334612f/src/transformers/image_processing_base.py#L473
51
+ def fetch_images(self, image_url_or_urls):
52
+ from transformers.image_utils import is_valid_image, load_image
53
+
54
+ if isinstance(image_url_or_urls, (list, tuple)):
55
+ return [self.fetch_images(x) for x in image_url_or_urls]
56
+ if isinstance(image_url_or_urls, str):
57
+ return load_image(image_url_or_urls)
58
+ if is_valid_image(image_url_or_urls):
59
+ return image_url_or_urls
60
+ raise TypeError(
61
+ "only a single or a list of entries is supported but got "
62
+ f"type={type(image_url_or_urls)}"
63
+ )
64
+
65
66
class MistralCommonPixtralProcessor(ProcessorMixin):
67
attributes = ["image_processor", "tokenizer"]