processing_idefics.py 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416
  1. # Copyright 2022 The HuggingFace Inc. team.
  2. #
  3. # Licensed under the Apache License, Version 2.0 (the "License");
  4. # you may not use this file except in compliance with the License.
  5. # You may obtain a copy of the License at
  6. #
  7. # http://www.apache.org/licenses/LICENSE-2.0
  8. #
  9. # Unless required by applicable law or agreed to in writing, software
  10. # distributed under the License is distributed on an "AS IS" BASIS,
  11. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. # See the License for the specific language governing permissions and
  13. # limitations under the License.
  14. """
  15. Processor class for IDEFICS.
  16. """
  17. from urllib.parse import urlparse
  18. from ...feature_extraction_utils import BatchFeature
  19. from ...image_utils import ImageInput
  20. from ...processing_utils import (
  21. ProcessingKwargs,
  22. ProcessorMixin,
  23. TextKwargs,
  24. Unpack,
  25. )
  26. from ...tokenization_utils_base import PreTokenizedInput, TextInput
  27. from ...utils import auto_docstring, is_torch_available
  28. if is_torch_available():
  29. import torch
  30. IMAGE_TOKEN = "<image>"
  31. class IdeficsTextKwargs(TextKwargs, total=False):
  32. """
  33. add_eos_token (`bool`, *optional*, defaults to `False`):
  34. Whether to add an end-of-sequence token at the end of the text input. When enabled, an EOS token is
  35. appended to mark the end of the text sequence, which is useful for generation tasks.
  36. add_end_of_utterance_token (`bool`, *optional*):
  37. Whether to add an end-of-utterance token to mark the end of a user's message in conversational contexts.
  38. This token helps the model distinguish between different utterances in a multi-turn conversation and is
  39. particularly important for chat-based models.
  40. """
  41. add_eos_token: bool | None
  42. add_end_of_utterance_token: bool | None
  43. class IdeficsProcessorKwargs(ProcessingKwargs, total=False):
  44. text_kwargs: IdeficsTextKwargs
  45. _defaults = {
  46. "text_kwargs": {
  47. "add_special_tokens": False,
  48. "padding": "longest",
  49. "add_eos_token": False,
  50. },
  51. "common_kwargs": {"return_tensors": "pt"},
  52. }
  53. # copied from m4.training.packing
  54. def incremental_to_binary_attention_mask(incremental_mask, return_tensors, num_classes=-1):
  55. # Set elements >= num_classes to -1
  56. if num_classes != -1:
  57. if return_tensors == "pt":
  58. incremental_mask[incremental_mask >= num_classes] = -1
  59. # Create mask for negative values
  60. if return_tensors == "pt":
  61. negatives = incremental_mask == -1
  62. incremental_mask[negatives] = 0
  63. attn_mask = torch.nn.functional.one_hot(incremental_mask, num_classes=num_classes)
  64. attn_mask[negatives, :] = 0
  65. return attn_mask
  66. # copied from m4.training.packing
  67. def image_attention_mask_for_packed_input_ids(input_ids, tokenizer, return_tensors):
  68. if return_tensors == "pt":
  69. return image_attention_mask_for_packed_input_ids_pt(input_ids, tokenizer)
  70. def image_attention_mask_for_packed_input_ids_pt(input_ids, tokenizer):
  71. image_attention_mask = torch.full_like(input_ids, fill_value=-1)
  72. next_image_attention_mask = torch.full_like(input_ids, fill_value=-1)
  73. image_token_id = tokenizer.convert_tokens_to_ids(IMAGE_TOKEN)
  74. eod_token_id = tokenizer.eos_token_id
  75. for batch_idx in range(input_ids.size(0)):
  76. count = -1
  77. seen_eod = False
  78. for idx, token_id in enumerate(input_ids[batch_idx]):
  79. if token_id == image_token_id:
  80. count += 1
  81. image_attention_mask[batch_idx][idx] = count
  82. seen_eod = False
  83. else:
  84. image_attention_mask[batch_idx][idx] = count
  85. if seen_eod:
  86. image_attention_mask[batch_idx][idx] = -1
  87. if token_id == eod_token_id:
  88. seen_eod = True
  89. for batch_idx in range(input_ids.size(0)):
  90. count = -1
  91. seen_eod = False
  92. for idx in range(input_ids[batch_idx].size(0) - 1, -1, -1):
  93. token_id = input_ids[batch_idx][idx]
  94. if token_id == image_token_id:
  95. count += 1
  96. next_image_attention_mask[batch_idx][idx] = count
  97. seen_eod = False
  98. else:
  99. next_image_attention_mask[batch_idx][idx] = count
  100. if token_id == eod_token_id:
  101. seen_eod = True
  102. if seen_eod:
  103. next_image_attention_mask[batch_idx][idx] = -1
  104. non_negative_indices = next_image_attention_mask[batch_idx] != -1
  105. next_image_attention_mask[batch_idx][non_negative_indices] -= count
  106. next_image_attention_mask[batch_idx][non_negative_indices] *= -1
  107. return image_attention_mask, next_image_attention_mask
  108. def is_url(string):
  109. """Checks if the passed string contains a valid url and nothing else. e.g. if space is included it's immediately
  110. invalidated the url"""
  111. if " " in string:
  112. return False
  113. result = urlparse(string)
  114. return all([result.scheme, result.netloc])
  115. @auto_docstring
  116. class IdeficsProcessor(ProcessorMixin):
  117. def __init__(self, image_processor, tokenizer=None, image_size=224, add_end_of_utterance_token=None, **kwargs):
  118. r"""
  119. image_size (int, *optional*, defaults to 224):
  120. The size of the image to be processed.
  121. add_end_of_utterance_token (bool, *optional*, defaults to None):
  122. Whether to add the end of utterance token to the text.
  123. """
  124. super().__init__(image_processor, tokenizer)
  125. self.image_token_id = (
  126. tokenizer.image_token_id
  127. if hasattr(tokenizer, "image_token")
  128. else tokenizer.convert_tokens_to_ids(IMAGE_TOKEN)
  129. )
  130. self.default_image_dims = (
  131. self.image_processor.image_num_channels,
  132. self.image_processor.image_size,
  133. self.image_processor.image_size,
  134. )
  135. self.tokenizer_was_trained_with_end_of_utterance_token = (
  136. "<end_of_utterance>" in self.tokenizer.special_tokens_map.get("additional_special_tokens", [])
  137. )
  138. @auto_docstring
  139. def __call__(
  140. self,
  141. images: ImageInput | list[ImageInput] | str | list[str] | list[list[str]] = None,
  142. text: TextInput
  143. | PreTokenizedInput
  144. | list[TextInput]
  145. | list[PreTokenizedInput]
  146. | list[list[TextInput]]
  147. | list[list[PreTokenizedInput]] = None,
  148. **kwargs: Unpack[IdeficsProcessorKwargs],
  149. ) -> BatchFeature:
  150. r"""
  151. Returns:
  152. a dict with entries: `input_ids`, `attention_mask`, `pixel_values`, `image_attention_mask` which can be
  153. directly passed to `model.generate`
  154. Detailed explanation:
  155. Each entry in `text` is either a text to be passed as is or an image that will be processed.
  156. An image can be either an image object (`PIL.Image`) or a url from which the image can be retrieved.
  157. When the processor encounters an image it'll inject `<fake_token_around_image><image><fake_token_around_image>`
  158. entry into the prompt.
  159. Example:
  160. ```python
  161. checkpoint = "HuggingFaceM4/idefics-9b"
  162. processor = AutoProcessor.from_pretrained(checkpoint)
  163. url = "https://hips.hearstapps.com/hmg-prod/images/cute-photos-of-cats-in-grass-1593184777.jpg"
  164. img = processor.image_processor.fetch_images([url])[0]
  165. prompts = [
  166. "User:",
  167. img,
  168. "Describe this image.\nAssistant: An image of two kittens in grass.\n",
  169. "User:",
  170. "https://hips.hearstapps.com/hmg-prod/images/dog-puns-1581708208.jpg",
  171. "Describe this image.\nAssistant:",
  172. ]
  173. inputs = processor(text=prompts, return_tensors="pt")
  174. generated_ids = model.generate(**inputs, max_length=100)
  175. generated_text = processor.batch_decode(generated_ids, skip_special_tokens=True)[0]
  176. ```
  177. In this example the `prompts` will be converted into:
  178. ```
  179. <s>User:<fake_token_around_image><image><fake_token_around_image>Describe this image.
  180. Assistant: An image of two kittens in grass.
  181. User:<fake_token_around_image><image><fake_token_around_image>Describe this image.
  182. Assistant:'
  183. ```
  184. and the two images will be massaged using [`IdeficsImageProcessor.__call__`] method and placed inside the
  185. `pixel_values` dict entry of the return value.
  186. This example also exemplifies that images can be passed as objects or as text urls. It can be seen that the
  187. first image is passed as object and the second one as a url.
  188. To do training do:
  189. ```python
  190. image_transform = transforms.Compose(
  191. [
  192. transforms.RandomResizedCrop(
  193. (w, h), scale=(0.9, 1.0), interpolation=transforms.InterpolationMode.BICUBIC
  194. ),
  195. transforms.ToTensor(),
  196. transforms.Normalize(mean=self.image_mean, std=self.image_std),
  197. ]
  198. )
  199. inputs = processor(text=prompts, transform=image_transform, return_tensors="pt")
  200. ```
  201. In order to help debug prompt generation enable `debug=True` which will show you what's happening.
  202. """
  203. if images is None and text is None:
  204. raise ValueError("You need to specify either `text` or `images` and `text`.")
  205. if images is None:
  206. # assuming the user wants to use the old behavior with prompts as the only argument
  207. prompts = text
  208. elif text is not None:
  209. # Assuming image-text-to-text behavior:
  210. # Check if batched images are provided
  211. if not isinstance(images, (list, tuple)):
  212. images = [images]
  213. if isinstance(text, str):
  214. text = [text]
  215. # Check if batched images and text are in the correct format
  216. if isinstance(text, (list, tuple)) and len(text) != len(images):
  217. raise ValueError(
  218. "When providing both images and text arguments, the number of text prompts should be the same as the number of images."
  219. "If you want to have several images per prompt, images should be nested as such: images=[[img1, img2], [img3, img4], ...] for text=[prompt1, prompt2, ...]."
  220. )
  221. # Check that only text is present in the prompts
  222. if not all(isinstance(i, str) for i in text):
  223. raise ValueError("When using the image-text-to-text behavior, the prompts should only contain text.")
  224. if isinstance(images[0], (list, tuple)):
  225. # if nested images, un-nest each sublist and create `prompts`
  226. prompts = [[sample, *image_list] for image_list, sample in zip(images, text)]
  227. else:
  228. prompts = list(zip(images, text))
  229. output_kwargs = self._merge_kwargs(
  230. IdeficsProcessorKwargs,
  231. tokenizer_init_kwargs=self.tokenizer.init_kwargs,
  232. **kwargs,
  233. )
  234. add_eos_token = output_kwargs["text_kwargs"].pop("add_eos_token", False)
  235. add_end_of_utterance_token = output_kwargs["text_kwargs"].pop("add_end_of_utterance_token", None)
  236. # if the value isn't overridden by the user, check if the tokenizer was trained with this token and then use it
  237. if add_end_of_utterance_token is None:
  238. add_end_of_utterance_token = self.tokenizer_was_trained_with_end_of_utterance_token
  239. # turn non-batched prompts into batched
  240. if not any(isinstance(i, (list, tuple)) for i in prompts):
  241. prompts = [prompts]
  242. fake_token = "<fake_token_around_image>"
  243. image_token = "<image>"
  244. end_of_utterance_token = "<end_of_utterance>"
  245. def image_tokens(last_was_image):
  246. if last_was_image:
  247. return image_token + fake_token
  248. else:
  249. return fake_token + image_token + fake_token
  250. all_prompts = []
  251. all_images = []
  252. for sample in prompts:
  253. # the model was trained on samples starting with <s>
  254. full_text = f"{self.tokenizer.bos_token}"
  255. # an image can either be an image object in the item or the url, everything else is a verbatim prompt text
  256. image_objects = []
  257. last_was_image = False
  258. last_was_text = False
  259. for i, item in enumerate(sample):
  260. if i > 0:
  261. last_was_text = bool(not last_was_image)
  262. if isinstance(item, str):
  263. item = item.strip(" ")
  264. if is_url(item):
  265. image = self.image_processor.fetch_images(item)
  266. full_text += image_tokens(last_was_image)
  267. image_objects.append(image)
  268. last_was_image = True
  269. else:
  270. # we add end_of_utterance_token between each subsequent text prompts (but not at the last one!)
  271. if add_end_of_utterance_token and last_was_text:
  272. full_text += end_of_utterance_token
  273. full_text += item
  274. last_was_image = False
  275. else:
  276. # must be an image obj
  277. full_text += image_tokens(last_was_image)
  278. image_objects.append(item)
  279. last_was_image = True
  280. if add_eos_token:
  281. full_text += self.tokenizer.eos_token
  282. if len(image_objects) > 0:
  283. image_objects = self.image_processor(image_objects, **output_kwargs["images_kwargs"])
  284. all_prompts.append(full_text)
  285. all_images.append(image_objects)
  286. # For BC
  287. return_tensors = output_kwargs["text_kwargs"].pop("return_tensors", "pt")
  288. text_encoding = self.tokenizer(all_prompts, **output_kwargs["text_kwargs"])
  289. all_texts = text_encoding["input_ids"]
  290. all_attention_masks = text_encoding["attention_mask"]
  291. # max_num_images has to be at least 1 even when there are no images
  292. max_num_images = max(len(x) for x in all_images)
  293. max_num_images = max(1, max_num_images)
  294. at_least_one_image = sum(len(x) for x in all_images) > 0
  295. output_input_ids = []
  296. output_images = []
  297. output_attention_masks = []
  298. for text_single, attention_mask, extracted_images in zip(all_texts, all_attention_masks, all_images):
  299. padded_input_ids = text_single
  300. image_count = padded_input_ids.count(self.image_token_id)
  301. local_max_num_images = min(image_count, max_num_images)
  302. current_images = extracted_images[:local_max_num_images]
  303. if len(current_images) > 0:
  304. if return_tensors == "pt":
  305. padded_image_tensor = torch.zeros(max_num_images, *current_images.size()[1:])
  306. padded_image_tensor[: current_images.size(0)] = current_images
  307. else:
  308. if return_tensors == "pt":
  309. padded_image_tensor = torch.zeros(max_num_images, *self.default_image_dims)
  310. output_images.append(padded_image_tensor)
  311. if return_tensors == "pt":
  312. output_input_ids.append(torch.tensor(padded_input_ids))
  313. output_attention_masks.append(torch.tensor(attention_mask))
  314. if return_tensors == "pt":
  315. output_input_ids = torch.stack(output_input_ids)
  316. output_images = torch.stack(output_images)
  317. output_attention_masks = torch.stack(output_attention_masks)
  318. if at_least_one_image:
  319. image_attention_mask, _ = image_attention_mask_for_packed_input_ids(
  320. output_input_ids, self.tokenizer, return_tensors
  321. )
  322. image_attention_mask = incremental_to_binary_attention_mask(
  323. image_attention_mask, return_tensors, num_classes=max_num_images
  324. )
  325. else:
  326. # in full language mode we set the image mask to all-0s
  327. if return_tensors == "pt":
  328. image_attention_mask = torch.zeros(
  329. output_input_ids.shape[0], output_input_ids.shape[1], 1, dtype=torch.bool
  330. )
  331. return BatchFeature(
  332. data={
  333. "input_ids": output_input_ids,
  334. "attention_mask": output_attention_masks,
  335. "pixel_values": output_images,
  336. "image_attention_mask": image_attention_mask,
  337. }
  338. )
  339. @property
  340. def model_input_names(self):
  341. tokenizer_input_names = self.tokenizer.model_input_names
  342. image_processor_input_names = self.image_processor.model_input_names
  343. return list(tokenizer_input_names + image_processor_input_names + ["image_attention_mask"])
  344. __all__ = ["IdeficsProcessor"]