processing_idefics3.py 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335
  1. # coding=utf-8
  2. # Copyright 2024 The HuggingFace Inc. team.
  3. #
  4. # Licensed under the Apache License, Version 2.0 (the "License");
  5. # you may not use this file except in compliance with the License.
  6. # You may obtain a copy of the License at
  7. #
  8. # http://www.apache.org/licenses/LICENSE-2.0
  9. #
  10. # Unless required by applicable law or agreed to in writing, software
  11. # distributed under the License is distributed on an "AS IS" BASIS,
  12. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. # See the License for the specific language governing permissions and
  14. # limitations under the License.
  15. """
  16. Processor class for Idefics3.
  17. """
  18. import re
  19. from typing import TYPE_CHECKING, Dict, List, Optional, Union
  20. from ...feature_extraction_utils import BatchFeature
  21. from ...image_utils import ImageInput, is_valid_image, load_image
  22. from ...processing_utils import ImagesKwargs, ProcessingKwargs, ProcessorMixin, Unpack
  23. from ...tokenization_utils_base import AddedToken, BatchEncoding, TextInput
  24. from ...utils import logging
  25. if TYPE_CHECKING:
  26. from ...tokenization_utils_base import PreTokenizedInput
  27. logger = logging.get_logger(__name__)
  28. def is_url(val) -> bool:
  29. return isinstance(val, str) and val.startswith("http")
  30. def is_image_or_image_url(elem):
  31. return is_url(elem) or is_valid_image(elem)
  32. def _prompt_split_image(image_seq_len, image_rows, image_cols, fake_token_around_image, image_token, global_img_token):
  33. """Prompt with expanded image tokens for when the image is split into patches."""
  34. text_split_images = ""
  35. for n_h in range(image_rows):
  36. for n_w in range(image_cols):
  37. text_split_images += (
  38. f"{fake_token_around_image}" + f"<row_{n_h + 1}_col_{n_w + 1}>" + f"{image_token}" * image_seq_len
  39. )
  40. text_split_images += "\n"
  41. text_split_images += (
  42. f"\n{fake_token_around_image}"
  43. + f"{global_img_token}"
  44. + f"{image_token}" * image_seq_len
  45. + f"{fake_token_around_image}"
  46. )
  47. return text_split_images
  48. def _prompt_single_image(image_seq_len, fake_token_around_image, image_token, global_img_token):
  49. """Prompt with expanded image tokens for a single image."""
  50. return (
  51. f"{fake_token_around_image}"
  52. + f"{global_img_token}"
  53. + f"{image_token}" * image_seq_len
  54. + f"{fake_token_around_image}"
  55. )
  56. def get_image_prompt_string(
  57. image_rows, image_cols, image_seq_len, fake_token_around_image, image_token, global_img_token
  58. ):
  59. if image_rows == 0 and image_cols == 0:
  60. return _prompt_single_image(
  61. image_seq_len,
  62. fake_token_around_image=fake_token_around_image,
  63. image_token=image_token,
  64. global_img_token=global_img_token,
  65. )
  66. return _prompt_split_image(
  67. image_seq_len, image_rows, image_cols, fake_token_around_image, image_token, global_img_token
  68. )
  69. class Idefics3ImagesKwargs(ImagesKwargs, total=False):
  70. return_row_col_info: Optional[bool]
  71. max_image_size: Optional[Dict[str, int]]
  72. class Idefics3ProcessorKwargs(ProcessingKwargs, total=False):
  73. images_kwargs: Idefics3ImagesKwargs
  74. _defaults = {
  75. "text_kwargs": {
  76. "add_special_tokens": True,
  77. "padding": False,
  78. "is_split_into_words": False,
  79. },
  80. "images_kwargs": {
  81. "return_row_col_info": True,
  82. },
  83. }
  84. Idefics3ProcessorKwargs.__annotations__["images_kwargs"] = Idefics3ImagesKwargs # python 3.8 compatibility
  85. class Idefics3Processor(ProcessorMixin):
  86. r"""
  87. Constructs a Idefics3 processor which wraps a LLama tokenizer and Idefics3 image processor into a single processor.
  88. [`Idefics3Processor`] offers all the functionalities of [`Idefics3ImageProcessor`] and [`Idefics3TokenizerFast`]. See
  89. the docstring of [`~IdeficsProcessor.__call__`] and [`~IdeficsProcessor.decode`] for more information.
  90. Args:
  91. image_processor (`Idefics3ImageProcessor`):
  92. An instance of [`Idefics3ImageProcessor`]. The image processor is a required input.
  93. tokenizer (`PreTrainedTokenizerBase`, *optional*):
  94. An instance of [`PreTrainedTokenizerBase`]. This should correspond with the model's text model. The tokenizer is a required input.
  95. image_seq_len (`int`, *optional*, defaults to 169):
  96. The length of the image sequence i.e. the number of <image> tokens per image in the input.
  97. This parameter is used to build the string from the input prompt and image tokens and should match the
  98. value the model used. It is computed as: image_seq_len = int(((image_size // patch_size) ** 2) / (scale_factor**2))
  99. chat_template (`str`, *optional*): A Jinja template which will be used to convert lists of messages
  100. in a chat into a tokenizable string.
  101. """
  102. attributes = ["image_processor", "tokenizer"]
  103. image_processor_class = "Idefics3ImageProcessor"
  104. tokenizer_class = "AutoTokenizer"
  105. def __init__(self, image_processor, tokenizer=None, image_seq_len: int = 169, chat_template: str = None, **kwargs):
  106. if image_processor is None:
  107. raise ValueError("You need to specify an `image_processor`.")
  108. if tokenizer is None:
  109. raise ValueError("You need to specify a `tokenizer`.")
  110. self.fake_image_token = AddedToken("<fake_token_around_image>", normalized=False, special=True)
  111. self.image_token = AddedToken("<image>", normalized=False, special=True)
  112. self.end_of_utterance_token = AddedToken("<end_of_utterance>", normalized=False, special=True)
  113. self.global_image_tag = "<global-img>" # https://github.com/huggingface/transformers/pull/32473/files/8063e5e17362571b693f1db95167f5443a3be1b2#r1734825341
  114. self.image_seq_len = image_seq_len
  115. # This regex matches one or more occurrences of <global-img> tags (optionally surrounded by newline characters)
  116. # or <row_x_col_y> tags (where x and y are digits, also optionally surrounded by newline characters).
  117. self._regex_to_remove_extra_special_tokens = re.compile(r"(\n?<global-img>\n?|<row_\d+_col_\d+>\n?)+")
  118. tokens_to_add = {
  119. "additional_special_tokens": [
  120. self.fake_image_token,
  121. self.image_token,
  122. self.end_of_utterance_token,
  123. ]
  124. }
  125. tokenizer.add_special_tokens(tokens_to_add)
  126. super().__init__(image_processor, tokenizer, chat_template=chat_template, **kwargs)
  127. def _extract_images_from_prompts(self, prompts):
  128. prompt_images = []
  129. for prompt in prompts:
  130. images = []
  131. for elem in prompt:
  132. if is_valid_image(elem):
  133. images.append(elem)
  134. elif is_url(elem):
  135. images.append(load_image(elem))
  136. prompt_images.append(images)
  137. return prompt_images
  138. def __call__(
  139. self,
  140. images: Union[ImageInput, List[ImageInput], List[List[ImageInput]]] = None,
  141. text: Union[TextInput, "PreTokenizedInput", List[TextInput], List["PreTokenizedInput"]] = None,
  142. audio=None,
  143. videos=None,
  144. image_seq_len: Optional[int] = None,
  145. **kwargs: Unpack[Idefics3ProcessorKwargs],
  146. ) -> BatchEncoding:
  147. """
  148. Processes the input prompts and returns a BatchEncoding.
  149. Example:
  150. ```python
  151. >>> import requests
  152. >>> from transformers import Idefics3Processor
  153. >>> from transformers.image_utils import load_image
  154. >>> processor = Idefics3Processor.from_pretrained("HuggingFaceM4/Idefics3-8B-Llama3")
  155. >>> processor.image_processor.do_image_splitting = False # Force as False to simplify the example
  156. >>> url1 = "https://cdn.britannica.com/61/93061-050-99147DCE/Statue-of-Liberty-Island-New-York-Bay.jpg"
  157. >>> url2 = "https://cdn.britannica.com/59/94459-050-DBA42467/Skyline-Chicago.jpg"
  158. >>> image1, image2 = load_image(url1), load_image(url2)
  159. >>> images = [[image1], [image2]]
  160. >>> text = [
  161. ... "<image>In this image, we see",
  162. ... "bla bla bla<image>",
  163. ... ]
  164. >>> outputs = processor(images=images, text=text, return_tensors="pt", padding=True)
  165. >>> input_ids = outputs.input_ids
  166. >>> input_tokens = processor.tokenizer.batch_decode(input_ids)
  167. >>> print(input_tokens)
  168. ['<|begin_of_text|><fake_token_around_image><global-img>((<image>)*169)<fake_token_around_image> In this image, we see', '<|reserved_special_token_0|><|reserved_special_token_0|><|reserved_special_token_0|><|begin_of_text|>bla bla bla<fake_token_around_image><global-img>((<image>)*169)<fake_token_around_image>']
  169. ```
  170. Args:
  171. images (`PIL.Image.Image`, `np.ndarray`, `torch.Tensor`, `List[PIL.Image.Image]`, `List[np.ndarray]`, `List[torch.Tensor]`, *optional*):
  172. The image or batch of images to be prepared. Each image can be a PIL image, NumPy array or PyTorch
  173. tensor. If is of type `List[ImageInput]`, it's assumed that this is for a single prompt i.e. of batch size 1.
  174. text (`Union[TextInput, PreTokenizedInput, List[TextInput], List[PreTokenizedInput]]`, *optional*):
  175. The sequence or batch of sequences to be encoded. Each sequence can be a string or a list of strings
  176. (pretokenized string). If the sequences are provided as list of strings (pretokenized), you must set
  177. `is_split_into_words=True` (to lift the ambiguity with a batch of sequences).
  178. Wherever an image token, `<image>` is encountered it is expanded to
  179. `<fake_token_around_image>` + `<row_x_col_y>` + `<image>` * `image_seq_len` * <fake_token_around_image>`.
  180. image_seq_len (`int`, *optional*):
  181. The length of the image sequence. If not provided, the default value of self.image_seq_len is used.
  182. image_seq_len should be equal to int(((image_size // patch_size) ** 2) / (scale_factor**2))
  183. return_tensors (`Union[str, TensorType]`, *optional*):
  184. If set, will return tensors of a particular framework. See [`PreTrainedTokenizerFast.__call__`] for more
  185. information.
  186. """
  187. if text is None and images is None:
  188. raise ValueError("You must provide either `text` or `images`.")
  189. output_kwargs = self._merge_kwargs(
  190. Idefics3ProcessorKwargs,
  191. tokenizer_init_kwargs=self.tokenizer.init_kwargs,
  192. **kwargs,
  193. )
  194. image_seq_len = image_seq_len if image_seq_len is not None else self.image_seq_len
  195. n_images_in_text = []
  196. n_images_in_images = []
  197. inputs = BatchFeature()
  198. if images is not None:
  199. if is_image_or_image_url(images):
  200. images = [[images]]
  201. elif isinstance(images, list) and is_image_or_image_url(images[0]):
  202. images = [images]
  203. elif (
  204. not isinstance(images, list)
  205. and not isinstance(images[0], list)
  206. and not is_image_or_image_url(images[0][0])
  207. ):
  208. raise ValueError(
  209. "Invalid input images. Please provide a single image or a list of images or a list of list of images."
  210. )
  211. n_images_in_images = [len(sample) for sample in images]
  212. # Load images if they are URLs
  213. images = [[load_image(im) if is_url(im) else im for im in sample] for sample in images]
  214. image_inputs = self.image_processor(images, **output_kwargs["images_kwargs"])
  215. inputs.update(image_inputs)
  216. if text is not None:
  217. if isinstance(text, str):
  218. text = [text]
  219. elif not isinstance(text, list) and not isinstance(text[0], str):
  220. raise ValueError("Invalid input text. Please provide a string, or a list of strings")
  221. image_rows = inputs.pop("rows", [[0] * len(text)])
  222. image_cols = inputs.pop("cols", [[0] * len(text)])
  223. fake_image_token = self.fake_image_token.content
  224. image_token = self.image_token.content
  225. global_img_token = self.global_image_tag
  226. prompt_strings = []
  227. for sample, sample_rows, sample_cols in zip(text, image_rows, image_cols):
  228. n_images_in_text.append(sample.count(image_token))
  229. # Replace the image token with fake tokens around the expanded image token sequence of length `image_seq_len`
  230. image_prompt_strings = []
  231. for n_rows, n_cols in zip(sample_rows, sample_cols):
  232. image_prompt_string = get_image_prompt_string(
  233. n_rows,
  234. n_cols,
  235. image_seq_len,
  236. image_token=image_token,
  237. fake_token_around_image=fake_image_token,
  238. global_img_token=global_img_token,
  239. )
  240. image_prompt_strings.append(image_prompt_string)
  241. split_sample = sample.split(image_token)
  242. if len(split_sample) == 0:
  243. raise ValueError("The image token should be present in the text.")
  244. # Place in the image prompt strings where the image tokens are
  245. sample = split_sample[0]
  246. for i, image_prompt_string in enumerate(image_prompt_strings):
  247. sample += image_prompt_string + split_sample[i + 1]
  248. prompt_strings.append(sample)
  249. text_inputs = self.tokenizer(text=prompt_strings, **output_kwargs["text_kwargs"])
  250. inputs.update(text_inputs)
  251. if n_images_in_images != n_images_in_text:
  252. raise ValueError(
  253. f"The number of images in the text {n_images_in_text} and images {n_images_in_images} should be the same."
  254. )
  255. return inputs
  256. def batch_decode(self, *args, **kwargs):
  257. """
  258. This method forwards all its arguments to Idefics3TokenizerFast's [`~PreTrainedTokenizer.batch_decode`]. Please
  259. refer to the docstring of this method for more information.
  260. """
  261. batched_decode_output = self.tokenizer.batch_decode(*args, **kwargs)
  262. return [self._regex_to_remove_extra_special_tokens.sub("<image>", s) for s in batched_decode_output]
  263. def decode(self, *args, **kwargs):
  264. """
  265. This method forwards all its arguments to Idefics3TokenizerFast's [`~PreTrainedTokenizer.decode`]. Please refer to
  266. the docstring of this method for more information.
  267. """
  268. decode_output = self.tokenizer.decode(*args, **kwargs)
  269. return self._regex_to_remove_extra_special_tokens.sub("<image>", decode_output)
  270. @property
  271. def model_input_names(self):
  272. tokenizer_input_names = self.tokenizer.model_input_names
  273. image_processor_input_names = self.image_processor.model_input_names
  274. return list(dict.fromkeys(tokenizer_input_names + image_processor_input_names))