Remove PersimmonForCausalLM and FuyuForCausalLM model architectures (#48096)
Signed-off-by: Xianbao QIAN <xianbao.qian@gmail.com> Signed-off-by: Harry Mellor <19981378+hmellor@users.noreply.github.com> Signed-off-by: Cyrus Leung <tlleungac@connect.ust.hk> Co-authored-by: Harry Mellor <19981378+hmellor@users.noreply.github.com> Co-authored-by: Cyrus Leung <tlleungac@connect.ust.hk>
Tiezhen WANG committed
Jul 9, 2026 at 19:59 UTC
412414d8e081e7057768714d37670728dc8cb363
14 files changed
+118
-1093
docs/configuration/conserving_memory.md
+1
-1
@@ -42,7 +42,7 @@ and the maximum batch size (`max_num_seqs` option).
42
```python
43
from vllm import LLM
44
45
-llm = LLM(model="adept/fuyu-8b", max_model_len=2048, max_num_seqs=2)
45
+llm = LLM(model="Qwen/Qwen2.5-VL-3B-Instruct", max_model_len=2048, max_num_seqs=2)
46
```
47
48
## Reduce CUDA Graphs
docs/contributing/model/multimodal.md
+115
-296
@@ -324,154 +324,44 @@ Assuming that the memory usage increases with the number of tokens, the dummy in
324
return image_token * num_images
325
```
326
327
-=== "No input placeholders: Fuyu"
327
+=== "No input placeholders: PaliGemma"
328
329
- Looking at the code of HF's `FuyuForCausalLM`:
329
+ Unlike LLaVA, PaliGemma's HF processor does not expect image placeholder
330
+ tokens in the input prompt; the placeholder feature tokens are instead
331
+ inserted afterwards (see [Prompt updates](#prompt-updates)). So the dummy
332
+ prompt text is empty regardless of the number of images:
333
331
- ??? code
332
-
333
- ```python
334
- # https://github.com/huggingface/transformers/blob/v4.48.3/src/transformers/models/fuyu/modeling_fuyu.py#L311-L322
335
- if image_patches is not None and past_key_values is None:
336
- patch_embeddings = [
337
- self.vision_embed_tokens(patch.to(self.vision_embed_tokens.weight.dtype))
338
- .squeeze(0)
339
- .to(inputs_embeds.device)
340
- for patch in image_patches
341
- ]
342
- inputs_embeds = self.gather_continuous_embeddings(
343
- word_embeddings=inputs_embeds,
344
- continuous_embeddings=patch_embeddings,
345
- image_patch_input_indices=image_patches_indices,
346
- )
347
- ```
348
-
349
- The number of placeholder feature tokens for the `i`th item in the batch is `patch_embeddings[i].shape[0]`,
350
- which is the same as `image_patches[i].shape[0]`, i.e. `num_total_patches`.
351
-
352
- Unlike LLaVA, Fuyu does not define the number of patches inside the modeling file. Where can we get more information?
353
- Considering that the model input comes from the output of `FuyuProcessor`, let's **look at the preprocessing files**.
354
-
355
- The image outputs are obtained by calling `FuyuImageProcessor.preprocess` and then
356
- `FuyuImageProcessor.preprocess_with_tokenizer_info` inside `FuyuProcessor`.
357
-
358
- In `FuyuImageProcessor.preprocess`, the images are resized and padded to the target `FuyuImageProcessor.size`,
359
- returning the dimensions after resizing (but before padding) as metadata.
360
-
361
- ??? code
362
-
363
- ```python
364
- # https://github.com/huggingface/transformers/blob/v4.48.3/src/transformers/models/fuyu/processing_fuyu.py#L541-L544
365
- image_encoding = self.image_processor.preprocess(images, **output_kwargs["images_kwargs"])
366
- batch_images = image_encoding["images"]
367
- image_unpadded_heights = image_encoding["image_unpadded_heights"]
368
- image_unpadded_widths = image_encoding["image_unpadded_widths"]
369
-
370
- # https://github.com/huggingface/transformers/blob/v4.48.3/src/transformers/models/fuyu/image_processing_fuyu.py#L480-L
371
- if do_resize:
372
- batch_images = [
373
- [self.resize(image, size=size, input_data_format=input_data_format) for image in images]
374
- for images in batch_images
375
- ]
376
-
377
- image_sizes = [get_image_size(images[0], channel_dim=input_data_format) for images in batch_images]
378
- image_unpadded_heights = [[image_size[0]] for image_size in image_sizes]
379
- image_unpadded_widths = [[image_size[1]] for image_size in image_sizes]
380
-
381
- if do_pad:
382
- batch_images = [
383
- [
384
- self.pad_image(
385
- image,
386
- size=size,
387
- mode=padding_mode,
388
- constant_values=padding_value,
389
- input_data_format=input_data_format,
390
- )
391
- for image in images
392
- ]
393
- for images in batch_images
394
- ]
395
- ```
396
-
397
- In `FuyuImageProcessor.preprocess_with_tokenizer_info`, the images are split into patches based on this metadata:
398
-
399
- ??? code
400
-
401
- ```python
402
- # https://github.com/huggingface/transformers/blob/v4.48.3/src/transformers/models/fuyu/processing_fuyu.py#L417-L425
403
- model_image_input = self.image_processor.preprocess_with_tokenizer_info(
404
- image_input=tensor_batch_images,
405
- image_present=image_present,
406
- image_unpadded_h=image_unpadded_heights,
407
- image_unpadded_w=image_unpadded_widths,
408
- image_placeholder_id=image_placeholder_id,
409
- image_newline_id=image_newline_id,
410
- variable_sized=True,
411
- )
412
-
413
- # https://github.com/huggingface/transformers/blob/v4.48.3/src/transformers/models/fuyu/image_processing_fuyu.py#L638-L658
414
- image_height, image_width = image.shape[1], image.shape[2]
415
- if variable_sized: # variable_sized=True
416
- new_h = min(
417
- image_height,
418
- math.ceil(image_unpadded_h[batch_index, subseq_index] / patch_height) * patch_height,
419
- )
420
- new_w = min(
421
- image_width,
422
- math.ceil(image_unpadded_w[batch_index, subseq_index] / patch_width) * patch_width,
423
- )
424
- image = image[:, :new_h, :new_w]
425
- image_height, image_width = new_h, new_w
426
-
427
- num_patches = self.get_num_patches(image_height=image_height, image_width=image_width)
428
- tensor_of_image_ids = torch.full(
429
- [num_patches], image_placeholder_id, dtype=torch.int32, device=image_input.device
430
- )
431
- patches = self.patchify_image(image=image.unsqueeze(0)).squeeze(0)
432
- assert num_patches == patches.shape[0]
433
- ```
334
+ ```python
335
+ def get_dummy_text(self, mm_counts: Mapping[str, int]) -> str:
336
+ return ""
337
+ ```
338
435
- The number of patches is in turn defined by `FuyuImageProcessor.get_num_patches`:
339
+ PaliGemma resizes every image to a square of `vision_config.image_size`, so
340
+ the number of placeholder feature tokens per image is fixed at
341
+ `(image_size // patch_size) ** 2`. This is computed by the SigLIP vision
342
+ encoder that PaliGemma uses:
343
344
??? code
345
346
```python
440
- # https://github.com/huggingface/transformers/blob/v4.48.3/src/transformers/models/fuyu/image_processing_fuyu.py#L552-L562
441
- patch_size = patch_size if patch_size is not None else self.patch_size
442
- patch_height, patch_width = self.patch_size["height"], self.patch_size["width"]
443
-
444
- if image_height % patch_height != 0:
445
- raise ValueError(f"{image_height=} must be divisible by {patch_height}")
446
- if image_width % patch_width != 0:
447
- raise ValueError(f"{image_width=} must be divisible by {patch_width}")
448
-
449
- num_patches_per_dim_h = image_height // patch_height
450
- num_patches_per_dim_w = image_width // patch_width
451
- num_patches = num_patches_per_dim_h * num_patches_per_dim_w
347
+ # vllm/model_executor/models/siglip.py
348
+ class SiglipEncoderInfo(VisionEncoderInfo[SiglipVisionConfig]):
349
+ def get_num_image_tokens(
350
+ self,
351
+ *,
352
+ image_width: int,
353
+ image_height: int,
354
+ ) -> int:
355
+ return self.get_patch_grid_length() ** 2
356
+
357
+ def get_patch_grid_length(self) -> int:
358
+ image_size, patch_size = self.get_image_size(), self.get_patch_size()
359
+ return image_size // patch_size
360
```
361
454
- These image patches correspond to placeholder tokens (`|SPEAKER|`). So, we just need to maximize the number of image patches. Since input images are first resized
455
- to fit within `image_processor.size`, we can maximize the number of image patches by inputting an image with size equal to `image_processor.size`.
456
-
457
- ```python
458
- def get_image_size_with_most_features(self) -> ImageSize:
459
- image_processor = self.get_image_processor()
460
- return ImageSize(
461
- width=image_processor.size["width"],
462
- height=image_processor.size["height"],
463
- )
464
- ```
465
-
466
- Fuyu does not expect image placeholders in the inputs to HF processor, so
467
- the dummy prompt text is empty regardless of the number of images.
468
-
469
- ```python
470
- def get_dummy_text(self, mm_counts: Mapping[str, int]) -> str:
471
- return ""
472
- ```
473
-
474
- For the multimodal image profiling data, the logic is very similar to LLaVA:
362
+ Since the number of image tokens doesn't depend on the input image dimensions,
363
+ we can simply use a dummy image of the model's expected input size for the
364
+ multimodal profiling data:
365
366
??? code
367
@@ -482,16 +372,18 @@ Assuming that the memory usage increases with the number of tokens, the dummy in
372
mm_counts: Mapping[str, int],
373
mm_options: Mapping[str, BaseDummyOptions],
374
) -> MultiModalDataDict:
485
- target_width, target_height = \
486
- self.info.get_image_size_with_most_features()
375
+ hf_config = self.info.get_hf_config()
376
+ vision_config = hf_config.vision_config
377
+ max_image_size = vision_config.image_size
378
+
379
num_images = mm_counts.get("image", 0)
380
381
image_overrides = mm_options.get("image")
382
383
return {
384
"image": self._get_dummy_images(
493
- width=target_width,
494
- height=target_height,
385
+ width=max_image_size,
386
+ height=max_image_size,
387
num_images=num_images,
388
overrides=image_overrides,
389
)
@@ -545,28 +437,15 @@ return a schema of the tensors outputted by the HF processor that are related to
437
Our [actual code](../../../vllm/model_executor/models/llava.py) additionally supports
438
pre-computed image embeddings, which can be passed to be model via the `image_embeds` argument.
439
548
-=== "With postprocessing: Fuyu"
440
+=== "With postprocessing: Mistral3"
441
550
- The `image_patches` output of `FuyuImageProcessor.preprocess_with_tokenizer_info` concatenates
551
- the patches from each image belonging to an item in the batch:
442
+ The `pixel_values` output of Mistral3's HF processor pads every image in the
443
+ batch to a common size, so that they can be stacked into a single tensor.
444
553
- ```python
554
- # https://github.com/huggingface/transformers/blob/v4.48.3/src/transformers/models/fuyu/image_processing_fuyu.py#L673-L679
555
- image_input_ids.append(tensor_of_image_ids)
556
- image_patches.append(patches)
557
- else:
558
- image_input_ids.append(torch.tensor([], dtype=torch.int32, device=image_input.device))
559
-
560
- batch_image_input_ids.append(image_input_ids)
561
- batch_image_patches.append(image_patches)
562
- ```
563
-
564
- The shape of `image_patches` outputted by `FuyuImageProcessor` is therefore
565
- `(1, num_images, num_patches, patch_width * patch_height * num_channels)`.
566
-
567
- In order to support the use of
568
- [MultiModalFieldConfig.batched][vllm.multimodal.inputs.MultiModalFieldConfig.batched]
569
- like in LLaVA, we remove the extra batch dimension by overriding
445
+ To use [MultiModalFieldConfig.batched][vllm.multimodal.inputs.MultiModalFieldConfig.batched]
446
+ like in LLaVA, each image's features must be independent of the others (which
447
+ is also required for prefix caching to work correctly). So, we un-pad each image
448
+ back to its own size by overriding
449
[BaseMultiModalProcessor._call_hf_processor][vllm.multimodal.processing.BaseMultiModalProcessor._call_hf_processor]:
450
451
??? code
@@ -586,33 +465,27 @@ return a schema of the tensors outputted by the HF processor that are related to
465
tok_kwargs=tok_kwargs,
466
)
467
589
- image_patches = processed_outputs.get("image_patches")
590
- if image_patches is not None:
591
- images = mm_data["images"]
592
- assert isinstance(images, list)
468
+ pixel_values = processed_outputs.get("pixel_values")
469
+ if pixel_values is not None:
470
+ # Avoid padding since we need the output for each image to be
471
+ # independent of other images for the cache to work correctly
472
+ image_sizes = processed_outputs["image_sizes"]
473
+ assert len(pixel_values) == len(image_sizes)
474
594
- # Original output: (1, num_images, Pn, Px * Py * C)
595
- # New output: (num_images, Pn, Px * Py * C)
596
- assert (isinstance(image_patches, list)
597
- and len(image_patches) == 1)
598
- assert (isinstance(image_patches[0], torch.Tensor)
599
- and len(image_patches[0]) == len(images))
600
-
601
- processed_outputs["image_patches"] = image_patches[0]
475
+ processed_outputs["pixel_values"] = [
476
+ p[:, :h, :w] for p, (h, w) in zip(pixel_values, image_sizes)
477
+ ]
478
479
return processed_outputs
480
```
481
606
- !!! note
607
- Our [actual code](../../../vllm/model_executor/models/fuyu.py) has special handling
608
- for text-only inputs to prevent unnecessary warnings from HF processor.
609
-
482
!!! note
483
The `_call_hf_processor` method specifies both `mm_kwargs` and `tok_kwargs` for
484
processing. `mm_kwargs` is used to both initialize and call the huggingface
485
processor, whereas `tok_kwargs` is only used to call the huggingface processor.
486
615
- This lets us override [_get_mm_fields_config][vllm.multimodal.processing.BaseMultiModalProcessor._get_mm_fields_config] as follows:
487
+ Since `pixel_values` is now a list with one tensor per image, we can override
488
+ [_get_mm_fields_config][vllm.multimodal.processing.BaseMultiModalProcessor._get_mm_fields_config] as follows:
489
490
```python
491
def _get_mm_fields_config(
@@ -620,9 +493,15 @@ return a schema of the tensors outputted by the HF processor that are related to
493
hf_inputs: BatchFeature,
494
hf_processor_mm_kwargs: Mapping[str, object],
495
) -> Mapping[str, MultiModalFieldConfig]:
623
- return dict(image_patches=MultiModalFieldConfig.batched("image"))
496
+ return dict(
497
+ pixel_values=MultiModalFieldConfig.batched("image"),
498
+ image_embeds=MultiModalFieldConfig.batched("image"),
499
+ )
500
```
501
502
+ !!! note
503
+ See our [actual code](../../../vllm/model_executor/models/mistral3.py) for the full implementation.
504
+
505
### Prompt updates
506
507
Override [_get_prompt_updates][vllm.multimodal.processing.BaseMultiModalProcessor._get_prompt_updates] to
@@ -678,121 +557,53 @@ Each [PromptUpdate][vllm.multimodal.processing.PromptUpdate] instance specifies
557
]
558
```
559
681
-=== "Handling additional tokens: Fuyu"
682
-
683
- Recall the layout of feature tokens from Step 2:
684
-
685
- ```
686
- |SPEAKER||SPEAKER|...|SPEAKER||NEWLINE|
687
- |SPEAKER||SPEAKER|...|SPEAKER||NEWLINE|
688
- ...
689
- |SPEAKER||SPEAKER|...|SPEAKER||NEWLINE|
690
- ```
691
-
692
- We define a helper function to return `ncols` and `nrows` directly:
693
-
694
- ??? code
695
-
696
- ```python
697
- def get_image_feature_grid_size(
698
- self,
699
- *,
700
- image_width: int,
701
- image_height: int,
702
- ) -> tuple[int, int]:
703
- image_processor = self.get_image_processor()
704
- target_width = image_processor.size["width"]
705
- target_height = image_processor.size["height"]
706
- patch_width = image_processor.patch_size["width"]
707
- patch_height = image_processor.patch_size["height"]
708
-
709
- if not (image_width <= target_width and image_height <= target_height):
710
- height_scale_factor = target_height / image_height
711
- width_scale_factor = target_width / image_width
712
- optimal_scale_factor = min(height_scale_factor, width_scale_factor)
713
-
714
- image_height = int(image_height * optimal_scale_factor)
715
- image_width = int(image_width * optimal_scale_factor)
716
-
717
- ncols = math.ceil(image_width / patch_width)
718
- nrows = math.ceil(image_height / patch_height)
719
- return ncols, nrows
720
- ```
560
+=== "Handling additional tokens: PaliGemma"
561
722
- Based on this, we can initially define our replacement tokens as:
562
+ PaliGemma's HF processor inserts, after the prompt's leading `<bos>` token, a
563
+ run of image tokens followed by a second `<bos>` token that marks the start of
564
+ the text prompt. We start by building the run of image tokens, one per
565
+ placeholder feature token:
566
567
??? code
568
569
```python
727
- def get_replacement(item_idx: int):
728
- images = mm_items.get_items("image", ImageProcessorItems)
729
- image_size = images.get_image_size(item_idx)
730
-
731
- ncols, nrows = self.info.get_image_feature_grid_size(
732
- image_width=image_size.width,
733
- image_height=image_size.height,
570
+ def get_insertion(item_idx: int):
571
+ images = mm_items.get_items(
572
+ "image", (ImageEmbeddingItems, ImageProcessorItems)
573
)
574
736
- # `_IMAGE_TOKEN_ID` corresponds to `|SPEAKER|`
737
- # `_NEWLINE_TOKEN_ID` corresponds to `|NEWLINE|`
738
- return ([_IMAGE_TOKEN_ID] * ncols + [_NEWLINE_TOKEN_ID]) * nrows
739
- ```
740
-
741
- However, this is not entirely correct. After `FuyuImageProcessor.preprocess_with_tokenizer_info` is called,
742
- a BOS token (`<s>`) is also added to the prompt:
743
-
744
- ??? code
575
+ if isinstance(images, ImageEmbeddingItems):
576
+ num_image_tokens = images.get_feature_size(item_idx)
577
+ else:
578
+ image_size = images.get_image_size(item_idx)
579
+ num_image_tokens = self.info.get_num_image_tokens(
580
+ image_width=image_size.width,
581
+ image_height=image_size.height,
582
+ )
583
746
- ```python
747
- # https://github.com/huggingface/transformers/blob/v4.48.3/src/transformers/models/fuyu/processing_fuyu.py#L417-L435
748
- model_image_input = self.image_processor.preprocess_with_tokenizer_info(
749
- image_input=tensor_batch_images,
750
- image_present=image_present,
751
- image_unpadded_h=image_unpadded_heights,
752
- image_unpadded_w=image_unpadded_widths,
753
- image_placeholder_id=image_placeholder_id,
754
- image_newline_id=image_newline_id,
755
- variable_sized=True,
756
- )
757
- prompt_tokens, prompts_length = _tokenize_prompts_with_image_and_batch(
758
- tokenizer=self.tokenizer,
759
- prompts=prompts,
760
- scale_factors=scale_factors,
761
- max_tokens_to_generate=self.max_tokens_to_generate,
762
- max_position_embeddings=self.max_position_embeddings,
763
- add_BOS=True,
764
- add_beginning_of_answer_token=True,
765
- )
584
+ image_tokens = [image_token_id] * num_image_tokens
585
+ ...
586
```
587
768
- To assign the vision embeddings to only the image tokens, instead of a string
769
- you can return an instance of [PromptUpdateDetails][vllm.multimodal.processing.PromptUpdateDetails]:
588
+ The trailing `<bos>` token is an additional token that must **not** receive a
589
+ vision embedding. To assign the vision embeddings to only the image tokens,
590
+ instead of returning the token ids directly you can return an instance of
591
+ [PromptUpdateDetails][vllm.multimodal.processing.PromptUpdateDetails] and mark
592
+ the embedding tokens with `embed_token_id`:
593
594
??? code
595
596
```python
774
- hf_config = self.info.get_hf_config()
775
- bos_token_id = hf_config.bos_token_id # `<s>`
776
- assert isinstance(bos_token_id, int)
777
-
778
- def get_replacement_fuyu(item_idx: int):
779
- images = mm_items.get_items("image", ImageProcessorItems)
780
- image_size = images.get_image_size(item_idx)
781
-
782
- ncols, nrows = self.info.get_image_feature_grid_size(
783
- image_width=image_size.width,
784
- image_height=image_size.height,
785
- )
786
- image_tokens = ([_IMAGE_TOKEN_ID] * ncols + [_NEWLINE_TOKEN_ID]) * nrows
787
-
788
- return PromptUpdateDetails.select_token_id(
789
- image_tokens + [bos_token_id],
790
- embed_token_id=_IMAGE_TOKEN_ID,
791
- )
597
+ return PromptUpdateDetails.select_token_id(
598
+ image_tokens + [bos_token_id],
599
+ embed_token_id=image_token_id,
600
+ )
601
```
602
794
- Finally, noticing that the HF processor removes the `|ENDOFTEXT|` token from the tokenized prompt,
795
- we can search for it to conduct the replacement at the start of the string:
603
+ Putting it together, we override [_get_prompt_updates][vllm.multimodal.processing.BaseMultiModalProcessor._get_prompt_updates].
604
+ Since these tokens are inserted (rather than replacing an existing placeholder)
605
+ after the prompt's leading `<bos>`, we use [PromptInsertion][vllm.multimodal.processing.PromptInsertion]
606
+ with a prefix target:
607
608
??? code
609
@@ -804,33 +615,41 @@ Each [PromptUpdate][vllm.multimodal.processing.PromptUpdate] instance specifies
615
out_mm_kwargs: MultiModalKwargsItems,
616
) -> Sequence[PromptUpdate]:
617
hf_config = self.info.get_hf_config()
807
- bos_token_id = hf_config.bos_token_id
808
- assert isinstance(bos_token_id, int)
618
+ image_token_id = hf_config.image_token_index
619
620
tokenizer = self.info.get_tokenizer()
811
- eot_token_id = tokenizer.bos_token_id
812
- assert isinstance(eot_token_id, int)
621
814
- def get_replacement_fuyu(item_idx: int):
815
- images = mm_items.get_items("image", ImageProcessorItems)
816
- image_size = images.get_image_size(item_idx)
622
+ bos_token_id = tokenizer.bos_token_id
623
+ assert isinstance(bos_token_id, int)
624
818
- ncols, nrows = self.info.get_image_feature_grid_size(
819
- image_width=image_size.width,
820
- image_height=image_size.height,
625
+ def get_insertion(item_idx: int):
626
+ images = mm_items.get_items(
627
+ "image", (ImageEmbeddingItems, ImageProcessorItems)
628
)
822
- image_tokens = ([_IMAGE_TOKEN_ID] * ncols + [_NEWLINE_TOKEN_ID]) * nrows
629
+
630
+ if isinstance(images, ImageEmbeddingItems):
631
+ num_image_tokens = images.get_feature_size(item_idx)
632
+ else:
633
+ image_size = images.get_image_size(item_idx)
634
+ num_image_tokens = self.info.get_num_image_tokens(
635
+ image_width=image_size.width,
636
+ image_height=image_size.height,
637
+ )
638
+
639
+ image_tokens = [image_token_id] * num_image_tokens
640
641
return PromptUpdateDetails.select_token_id(
642
image_tokens + [bos_token_id],
826
- embed_token_id=_IMAGE_TOKEN_ID,
643
+ embed_token_id=image_token_id,
644
)
645
646
return [
830
- PromptReplacement(
647
+ PromptInsertion(
648
modality="image",
832
- target=[eot_token_id],
833
- replacement=get_replacement_fuyu,
649
+ target=PromptIndexTargets.prefix(
650
+ [bos_token_id] if tokenizer.add_bos_token else []
651
+ ),
652
+ insertion=get_insertion,
653
)
654
]
655
```
@@ -873,7 +692,7 @@ Examples:
692
Examples:
693
694
- Chameleon (appends `sep_token`): [vllm/model_executor/models/chameleon.py](../../../vllm/model_executor/models/chameleon.py)
876
-- Fuyu (appends `boa_token`): [vllm/model_executor/models/fuyu.py](../../../vllm/model_executor/models/fuyu.py)
695
+- Molmo2 (prepends `bos_token`): [vllm/model_executor/models/molmo2.py](../../../vllm/model_executor/models/molmo2.py)
696
- Molmo (applies chat template which is not defined elsewhere): [vllm/model_executor/models/molmo.py](../../../vllm/model_executor/models/molmo.py)
697
698
### Custom HF processor
docs/models/supported_models.md
-2
@@ -436,7 +436,6 @@ th {
436
| `PhiForCausalLM` | Phi | `microsoft/phi-1_5`, `microsoft/phi-2`, etc. | ✅︎ | ✅︎ |
437
| `Phi3ForCausalLM` | Phi-4, Phi-3 | `microsoft/Phi-4-mini-instruct`, `microsoft/Phi-4`, `microsoft/Phi-3-mini-4k-instruct`, `microsoft/Phi-3-mini-128k-instruct`, `microsoft/Phi-3-medium-128k-instruct`, etc. | ✅︎ | ✅︎ |
438
| `PhiMoEForCausalLM` | Phi-3.5-MoE | `microsoft/Phi-3.5-MoE-instruct`, etc. | ✅︎ | ✅︎ |
439
-| `PersimmonForCausalLM` | Persimmon | `adept/persimmon-8b-base`, `adept/persimmon-8b-chat`, etc. | | ✅︎ |
439
| `Plamo2ForCausalLM` | PLaMo2 | `pfnet/plamo-2-1b`, `pfnet/plamo-2-8b`, etc. | ✅ | ✅︎ |
440
| `Plamo3ForCausalLM` | PLaMo3 | `pfnet/plamo-3-nict-2b-base`, `pfnet/plamo-3-nict-8b-base`, etc. | ✅ | ✅︎ |
441
| `Qwen2ForCausalLM` | QwQ, Qwen2 | `Qwen/QwQ-32B-Preview`, `Qwen/Qwen2-7B-Instruct`, `Qwen/Qwen2-7B`, etc. | ✅︎ | ✅︎ |
@@ -520,7 +519,6 @@ These models primarily accept the [`LLM.generate`](./generative_models.md#llmgen
519
| `Eagle2_5_VLForConditionalGeneration` | Eagle2.5-VL | T + I<sup>E+</sup> | `nvidia/Eagle2.5-8B`, etc. | ✅︎ | ✅︎ |
520
| `Ernie4_5_VLMoeForConditionalGeneration` | Ernie4.5-VL | T + I<sup>+</sup>/ V<sup>+</sup> | `baidu/ERNIE-4.5-VL-28B-A3B-PT`, `baidu/ERNIE-4.5-VL-424B-A47B-PT` | | ✅︎ |
521
| `Exaone4_5_ForConditionalGeneration` | EXAONE-4.5 | T + I<sup>E+</sup> | `LGAI-EXAONE/EXAONE-4.5-33B`, etc. | ✅︎ | ✅︎ |
523
-| `FuyuForCausalLM` | Fuyu | T + I | `adept/fuyu-8b`, etc. | | ✅︎ |
522
| `Gemma3ForConditionalGeneration` | Gemma 3 | T + I<sup>E+</sup> | `google/gemma-3-4b-it`, `google/gemma-3-27b-it`, etc. | ✅︎ | ✅︎ |
523
| `Gemma3nForConditionalGeneration` | Gemma 3n | T + I + A | `google/gemma-3n-E2B-it`, `google/gemma-3n-E4B-it`, etc. | | |
524
| `Gemma4ForConditionalGeneration` | Gemma 4 | T + I<sup>+</sup> + V + A<sup>*</sup> | `google/gemma-4-E2B-it`, etc. | | ✅︎ |
examples/generate/multimodal/vision_language_offline.py
-19
@@ -448,24 +448,6 @@ def run_exaone4_5(questions: list[str], modality: str) -> ModelRequestData:
448
)
449
450
451
-# Fuyu
452
-def run_fuyu(questions: list[str], modality: str) -> ModelRequestData:
453
- assert modality == "image"
454
-
455
- prompts = [f"{question}\n" for question in questions]
456
- engine_args = EngineArgs(
457
- model="adept/fuyu-8b",
458
- max_model_len=2048,
459
- max_num_seqs=2,
460
- limit_mm_per_prompt={modality: 1},
461
- )
462
-
463
- return ModelRequestData(
464
- engine_args=engine_args,
465
- prompts=prompts,
466
- )
467
-
468
-
451
# Gemma 3
452
def run_gemma3(questions: list[str], modality: str) -> ModelRequestData:
453
assert modality == "image"
@@ -2318,7 +2300,6 @@ model_example_map = {
2300
"eagle2_5": run_eagle2_5,
2301
"ernie45_vl": run_ernie45_vl,
2302
"exaone4_5": run_exaone4_5,
2321
- "fuyu": run_fuyu,
2303
"gemma3": run_gemma3,
2304
"gemma3n": run_gemma3n,
2305
"glm4v": run_glm4v,
tests/distributed/test_pipeline_parallel.py
-2
@@ -139,7 +139,6 @@ TEXT_GENERATION_MODELS = {
139
"allenai/OLMoE-1B-7B-0924-Instruct": PPTestSettings.fast(),
140
"facebook/opt-iml-max-1.3b": PPTestSettings.fast(),
141
"OrionStarAI/Orion-14B-Chat": PPTestSettings.fast(),
142
- "adept/persimmon-8b-chat": PPTestSettings.fast(),
142
"microsoft/phi-2": PPTestSettings.fast(),
143
"microsoft/Phi-3-small-8k-instruct": PPTestSettings.fast(),
144
"microsoft/Phi-3.5-MoE-instruct": PPTestSettings.detailed(
@@ -168,7 +167,6 @@ MULTIMODAL_MODELS = {
167
# [Decoder-only]
168
"Salesforce/blip2-opt-6.7b": PPTestSettings.fast(),
169
"facebook/chameleon-7b": PPTestSettings.fast(),
171
- "adept/fuyu-8b": PPTestSettings.fast(),
170
"zai-org/glm-4v-9b": PPTestSettings.fast(),
171
"OpenGVLab/InternVL3-1B": PPTestSettings.fast(),
172
"llava-hf/llava-1.5-7b-hf": PPTestSettings.fast(),
tests/models/multimodal/generation/test_common.py
-14
@@ -388,20 +388,6 @@ VLM_TEST_SETTINGS = {
388
stop_str=["<|end▁of▁sentence|>", "<|begin▁of▁sentence|>"],
389
image_size_factors=[(1.0,), (1.0, 1.0, 1.0), (0.1, 0.5, 1.0)],
390
),
391
- "fuyu": VLMTestInfo(
392
- models=["adept/fuyu-8b"],
393
- test_type=VLMTestType.IMAGE,
394
- prompt_formatter=lambda img_prompt: f"{img_prompt}\n",
395
- img_idx_to_prompt=lambda idx: "",
396
- max_model_len=2048,
397
- max_num_seqs=2,
398
- auto_cls=AutoModelForImageTextToText,
399
- use_tokenizer_eos=True,
400
- vllm_output_post_proc=model_utils.fuyu_vllm_to_hf_output,
401
- num_logprobs=10,
402
- image_size_factors=[(0.25,), (0.25, 0.25, 0.25), (0.25, 0.2, 0.15)],
403
- marks=[large_gpu_mark(min_gb=32)],
404
- ),
391
"gemma3": VLMTestInfo(
392
models=["google/gemma-3-4b-it"],
393
test_type=(VLMTestType.IMAGE, VLMTestType.MULTI_IMAGE),
tests/models/multimodal/generation/vlm_utils/model_utils.py
-9
@@ -52,15 +52,6 @@ def blip2_vllm_to_hf_output(vllm_output: RunnerOutput, model: str) -> RunnerOutp
52
return hf_output_ids, hf_output_str, out_logprobs
53
54
55
-def fuyu_vllm_to_hf_output(vllm_output: RunnerOutput, model: str) -> RunnerOutput:
56
- """Sanitize vllm output [fuyu models] to be comparable with hf output."""
57
- output_ids, output_str, out_logprobs = vllm_output
58
-
59
- hf_output_str = output_str.lstrip() + "|ENDOFTEXT|"
60
-
61
- return output_ids, hf_output_str, out_logprobs
62
-
63
-
55
def qwen_vllm_to_hf_output(
56
vllm_output: RunnerOutput, model: str
57
) -> tuple[list[int], str, SampleLogprobs | None]:
tests/models/registry.py
-2
@@ -466,7 +466,6 @@ _TEXT_GENERATION_EXAMPLE_MODELS = {
466
"bharatgenai/Param2-17B-A2.4B-Thinking",
467
trust_remote_code=True,
468
),
469
- "PersimmonForCausalLM": _HfExamplesInfo("adept/persimmon-8b-chat"),
469
"PhiForCausalLM": _HfExamplesInfo("microsoft/phi-2"),
470
"Phi3ForCausalLM": _HfExamplesInfo("microsoft/Phi-3-mini-4k-instruct"),
471
"PhiMoEForCausalLM": _HfExamplesInfo(
@@ -866,7 +865,6 @@ _MULTIMODAL_EXAMPLE_MODELS = {
865
"FunAudioChatForConditionalGeneration": _HfExamplesInfo(
866
"funaudiochat", is_available_online=False
867
),
869
- "FuyuForCausalLM": _HfExamplesInfo("adept/fuyu-8b"),
868
"Gemma3ForConditionalGeneration": _HfExamplesInfo("google/gemma-3-4b-it"),
869
"DiffusionGemmaForBlockDiffusion": _HfExamplesInfo(
870
"google/diffusiongemma-26B-A4B-it",
tests/renderers/test_hf.py
-1
@@ -426,7 +426,6 @@ def test_resolve_content_format_hf_defined(model, expected_format):
426
("Salesforce/blip2-opt-2.7b", "string"),
427
("facebook/chameleon-7b", "string"),
428
("deepseek-ai/deepseek-vl2-tiny", "string"),
429
- ("adept/fuyu-8b", "string"),
429
("google/paligemma-3b-mix-224", "string"),
430
],
431
)
vllm/model_executor/models/fuyu.py
deleted
-368
@@ -1,368 +0,0 @@
1
-# SPDX-License-Identifier: Apache-2.0
2
-# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
-
4
-# adapted from https://github.com/huggingface/transformers/blob/v4.39.3/src/transformers/models/fuyu/modeling_fuyu.py
5
-# Copyright 2023 The vLLM team.
6
-# Copyright 2023 HuggingFace Inc. team. All rights reserved.
7
-#
8
-# Licensed under the Apache License, Version 2.0 (the "License");
9
-# you may not use this file except in compliance with the License.
10
-# You may obtain a copy of the License at
11
-#
12
-# http://www.apache.org/licenses/LICENSE-2.0
13
-#
14
-# Unless required by applicable law or agreed to in writing, software
15
-# distributed under the License is distributed on an "AS IS" BASIS,
16
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17
-# See the License for the specific language governing permissions and
18
-# limitations under the License.
19
-"""PyTorch Fuyu model."""
20
-
21
-import math
22
-from collections.abc import Iterable, Mapping, Sequence
23
-from typing import Annotated, Literal
24
-
25
-import torch
26
-import torch.nn as nn
27
-from transformers import BatchFeature, FuyuConfig, FuyuImageProcessor, FuyuProcessor
28
-
29
-from vllm.config import VllmConfig
30
-from vllm.config.multimodal import BaseDummyOptions
31
-from vllm.inputs import MultiModalDataDict
32
-from vllm.model_executor.layers.linear import ColumnParallelLinear
33
-from vllm.model_executor.models.persimmon import PersimmonForCausalLM
34
-from vllm.multimodal import MULTIMODAL_REGISTRY
35
-from vllm.multimodal.inputs import (
36
- MultiModalFieldConfig,
37
- MultiModalKwargsItems,
38
-)
39
-from vllm.multimodal.parse import ImageProcessorItems, ImageSize, MultiModalDataItems
40
-from vllm.multimodal.processing import (
41
- BaseDummyInputsBuilder,
42
- BaseMultiModalProcessor,
43
- BaseProcessingInfo,
44
- PromptReplacement,
45
- PromptUpdate,
46
- PromptUpdateDetails,
47
-)
48
-from vllm.sequence import IntermediateTensors
49
-from vllm.utils.tensor_schema import TensorSchema, TensorShape
50
-
51
-from .interfaces import MultiModalEmbeddings, SupportsMultiModal, SupportsPP
52
-from .utils import AutoWeightsLoader, WeightsMapper, flatten_bn, maybe_prefix
53
-
54
-# Cannot find the following 2 numbers from hf config.
55
-_IMAGE_TOKEN_ID = 71011
56
-_NEWLINE_TOKEN_ID = 71019
57
-
58
-
59
-class FuyuImagePatchInputs(TensorSchema):
60
- """
61
- Dimensions:
62
- - bn: Batch size * number of images
63
- - bnp: Batch size * number of images * number of patches
64
- - fn: patch_size_x * patch_size_y * num_channels
65
- """
66
-
67
- type: Literal["image_patches"] = "image_patches"
68
-
69
- image_patches_flat: Annotated[torch.Tensor, TensorShape("bnp", "fn")]
70
-
71
- patches_per_image: Annotated[list[int], TensorShape("bn")]
72
- """
73
- The number of total patches for each image in the batch.
74
-
75
- This is used to split the embeddings which has the first two dimensions
76
- flattened just like `image_patches_flat`.
77
- """
78
-
79
-
80
-class FuyuProcessingInfo(BaseProcessingInfo):
81
- def get_hf_config(self):
82
- return self.ctx.get_hf_config(FuyuConfig)
83
-
84
- def get_hf_processor(self, **kwargs: object):
85
- return self.ctx.get_hf_processor(FuyuProcessor, **kwargs)
86
-
87
- def get_image_processor(self, **kwargs: object) -> FuyuImageProcessor:
88
- return self.get_hf_processor(**kwargs).image_processor
89
-
90
- def get_supported_mm_limits(self) -> Mapping[str, int | None]:
91
- return {"image": 1}
92
-
93
- def get_image_feature_grid_size(
94
- self,
95
- *,
96
- image_width: int,
97
- image_height: int,
98
- ) -> tuple[int, int]:
99
- image_processor = self.get_image_processor()
100
- target_width = image_processor.size["width"]
101
- target_height = image_processor.size["height"]
102
- patch_width = image_processor.patch_size["width"]
103
- patch_height = image_processor.patch_size["height"]
104
-
105
- if not (image_width <= target_width and image_height <= target_height):
106
- height_scale_factor = target_height / image_height
107
- width_scale_factor = target_width / image_width
108
- optimal_scale_factor = min(height_scale_factor, width_scale_factor)
109
-
110
- image_height = int(image_height * optimal_scale_factor)
111
- image_width = int(image_width * optimal_scale_factor)
112
-
113
- ncols = math.ceil(image_width / patch_width)
114
- nrows = math.ceil(image_height / patch_height)
115
- return ncols, nrows
116
-
117
- def get_num_image_tokens(
118
- self,
119
- *,
120
- image_width: int,
121
- image_height: int,
122
- ) -> int:
123
- ncols, nrows = self.get_image_feature_grid_size(
124
- image_width=image_width,
125
- image_height=image_height,
126
- )
127
-
128
- return ncols * nrows
129
-
130
- def get_image_size_with_most_features(self) -> ImageSize:
131
- image_processor = self.get_image_processor()
132
- return ImageSize(
133
- width=image_processor.size["width"], height=image_processor.size["height"]
134
- )
135
-
136
-
137
-class FuyuDummyInputsBuilder(BaseDummyInputsBuilder[FuyuProcessingInfo]):
138
- def get_dummy_text(self, mm_counts: Mapping[str, int]) -> str:
139
- return ""
140
-
141
- def get_dummy_mm_data(
142
- self,
143
- seq_len: int,
144
- mm_counts: Mapping[str, int],
145
- mm_options: Mapping[str, BaseDummyOptions],
146
- ) -> MultiModalDataDict:
147
- target_width, target_height = self.info.get_image_size_with_most_features()
148
- num_images = mm_counts.get("image", 0)
149
-
150
- image_overrides = mm_options.get("image")
151
-
152
- return {
153
- "image": self._get_dummy_images(
154
- width=target_width,
155
- height=target_height,
156
- num_images=num_images,
157
- overrides=image_overrides,
158
- )
159
- }
160
-
161
-
162
-class FuyuMultiModalProcessor(BaseMultiModalProcessor[FuyuProcessingInfo]):
163
- def _call_hf_processor(
164
- self,
165
- prompt: str,
166
- mm_data: Mapping[str, object],
167
- mm_kwargs: Mapping[str, object],
168
- tok_kwargs: Mapping[str, object],
169
- ) -> BatchFeature:
170
- if not mm_data:
171
- # Avoid warning from HF logger for text-only input
172
- prompt_ids = self.info.get_tokenizer().encode(prompt)
173
- prompt_ids = self._apply_hf_processor_tokens_only(prompt_ids)
174
- return BatchFeature(dict(input_ids=[prompt_ids]), tensor_type="pt")
175
-
176
- processed_outputs = super()._call_hf_processor(
177
- prompt=prompt,
178
- mm_data=mm_data,
179
- mm_kwargs=mm_kwargs,
180
- tok_kwargs=tok_kwargs,
181
- )
182
-
183
- image_patches = processed_outputs["image_patches"]
184
- processed_outputs["image_patches"] = flatten_bn(image_patches)
185
- processed_outputs["patches_per_image"] = torch.tensor(
186
- [len(p) for p in image_patches]
187
- )
188
-
189
- return processed_outputs
190
-
191
- def _apply_hf_processor_tokens_only(
192
- self,
193
- prompt_tokens: list[int],
194
- ) -> list[int]:
195
- # HF processor adds boa_token_id
196
- tokenizer = self.info.get_tokenizer()
197
- vocab = tokenizer.get_vocab()
198
-
199
- boa_token_id = vocab["<0x04>"]
200
- if prompt_tokens[-1] != boa_token_id:
201
- prompt_tokens.append(boa_token_id)
202
-
203
- return prompt_tokens
204
-
205
- def _get_mm_fields_config(
206
- self,
207
- hf_inputs: BatchFeature,
208
- hf_processor_mm_kwargs: Mapping[str, object],
209
- ) -> Mapping[str, MultiModalFieldConfig]:
210
- patches_per_image = hf_inputs.get("patches_per_image", torch.empty(0))
211
-
212
- return dict(
213
- image_patches=MultiModalFieldConfig.flat_from_sizes(
214
- "image", patches_per_image
215
- ),
216
- patches_per_image=MultiModalFieldConfig.batched("image"),
217
- )
218
-
219
- def _get_prompt_updates(
220
- self,
221
- mm_items: MultiModalDataItems,
222
- hf_processor_mm_kwargs: Mapping[str, object],
223
- out_mm_kwargs: MultiModalKwargsItems,
224
- ) -> Sequence[PromptUpdate]:
225
- hf_config = self.info.get_hf_config()
226
- bos_token_id = hf_config.bos_token_id
227
- assert isinstance(bos_token_id, int)
228
-
229
- tokenizer = self.info.get_tokenizer()
230
- eot_token_id = tokenizer.bos_token_id
231
- assert isinstance(eot_token_id, int)
232
-
233
- def get_replacement_fuyu(item_idx: int):
234
- images = mm_items.get_items("image", ImageProcessorItems)
235
- image_size = images.get_image_size(item_idx)
236
-
237
- ncols, nrows = self.info.get_image_feature_grid_size(
238
- image_width=image_size.width,
239
- image_height=image_size.height,
240
- )
241
- image_tokens = ([_IMAGE_TOKEN_ID] * ncols + [_NEWLINE_TOKEN_ID]) * nrows
242
-
243
- return PromptUpdateDetails.select_token_id(
244
- image_tokens + [bos_token_id],
245
- embed_token_id=_IMAGE_TOKEN_ID,
246
- )
247
-
248
- return [
249
- PromptReplacement(
250
- modality="image",
251
- target=[eot_token_id],
252
- replacement=get_replacement_fuyu,
253
- )
254
- ]
255
-
256
-
257
-@MULTIMODAL_REGISTRY.register_processor(
258
- FuyuMultiModalProcessor,
259
- info=FuyuProcessingInfo,
260
- dummy_inputs=FuyuDummyInputsBuilder,
261
-)
262
-class FuyuForCausalLM(nn.Module, SupportsMultiModal, SupportsPP):
263
- hf_to_vllm_mapper = WeightsMapper(
264
- orig_to_new_prefix={
265
- "model.vision_embed_tokens.": "vision_embed_tokens.",
266
- "model.language_model.": "language_model.model.",
267
- "lm_head.": "language_model.lm_head.",
268
- }
269
- )
270
-
271
- @classmethod
272
- def get_placeholder_str(cls, modality: str, i: int) -> str | None:
273
- if modality.startswith("image"):
274
- return None
275
-
276
- raise ValueError("Only image modality is supported")
277
-
278
- def __init__(self, *, vllm_config: VllmConfig, prefix: str = ""):
279
- super().__init__()
280
- config = vllm_config.model_config.hf_config
281
- quant_config = vllm_config.quant_config
282
- multimodal_config = vllm_config.model_config.multimodal_config
283
- self.config = config
284
- self.multimodal_config = multimodal_config
285
-
286
- self.vocab_size = config.text_config.vocab_size
287
- self.image_token_id = _IMAGE_TOKEN_ID
288
- self.image_feature_size = config.patch_size**2 * config.num_channels
289
-
290
- with self._mark_tower_model(vllm_config, "image"):
291
- self.vision_embed_tokens = ColumnParallelLinear(
292
- self.image_feature_size,
293
- config.hidden_size,
294
- quant_config=quant_config,
295
- gather_output=True,
296
- )
297
-
298
- with self._mark_language_model(vllm_config):
299
- self.language_model = PersimmonForCausalLM(
300
- vllm_config=vllm_config.with_hf_config(config.text_config),
301
- prefix=maybe_prefix(prefix, "language_model"),
302
- )
303
-
304
- self.make_empty_intermediate_tensors = (
305
- self.language_model.make_empty_intermediate_tensors
306
- )
307
-
308
- def _parse_and_validate_image_input(
309
- self, **kwargs: object
310
- ) -> FuyuImagePatchInputs | None:
311
- image_patches = kwargs.pop("image_patches", None)
312
- patches_per_image = kwargs.pop("patches_per_image", None)
313
-
314
- if image_patches is None:
315
- return None
316
-
317
- return FuyuImagePatchInputs(
318
- type="image_patches",
319
- image_patches_flat=image_patches,
320
- patches_per_image=patches_per_image,
321
- resolve_bindings={"fn": self.image_feature_size},
322
- )
323
-
324
- def _process_image_input(
325
- self, image_input: FuyuImagePatchInputs
326
- ) -> MultiModalEmbeddings:
327
- image_patches_flat = image_input["image_patches_flat"]
328
- patches_per_image = image_input["patches_per_image"]
329
-
330
- vision_embeddings_flat, _ = self.vision_embed_tokens(image_patches_flat)
331
-
332
- return vision_embeddings_flat.split(patches_per_image.tolist(), dim=0)
333
-
334
- def embed_multimodal(self, **kwargs: object) -> MultiModalEmbeddings:
335
- image_input = self._parse_and_validate_image_input(**kwargs)
336
- if image_input is None:
337
- return []
338
-
339
- return self._process_image_input(image_input)
340
-
341
- def forward(
342
- self,
343
- input_ids: torch.Tensor | None,
344
- positions: torch.Tensor,
345
- intermediate_tensors: IntermediateTensors | None = None,
346
- inputs_embeds: torch.Tensor | None = None,
347
- **kwargs: object,
348
- ):
349
- if intermediate_tensors is not None:
350
- inputs_embeds = None
351
-
352
- hidden_states = self.language_model(
353
- input_ids=input_ids,
354
- positions=positions,
355
- intermediate_tensors=intermediate_tensors,
356
- inputs_embeds=inputs_embeds,
357
- )
358
- return hidden_states
359
-
360
- def compute_logits(
361
- self,
362
- hidden_states: torch.Tensor,
363
- ) -> torch.Tensor | None:
364
- return self.language_model.compute_logits(hidden_states)
365
-
366
- def load_weights(self, weights: Iterable[tuple[str, torch.Tensor]]) -> set[str]:
367
- loader = AutoWeightsLoader(self)
368
- return loader.load_weights(weights)
vllm/model_executor/models/persimmon.py
deleted
-373
@@ -1,373 +0,0 @@
1
-# SPDX-License-Identifier: Apache-2.0
2
-# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
-
4
-# adapted from https://github.com/huggingface/transformers/blob/v4.39.3/src/transformers/models/persimmon/modeling_persimmon.py
5
-# Copyright 2023 The vLLM team.
6
-# Copyright 2023 EleutherAI and the HuggingFace Inc. team. All rights reserved.
7
-#
8
-# This code is based on EleutherAI's GPT-NeoX library and the GPT-NeoX
9
-# and OPT implementations in this library. It has been modified from its
10
-# original forms to accommodate minor architectural differences compared
11
-# to GPT-NeoX and OPT used by the Meta AI team that trained the model.
12
-#
13
-# Licensed under the Apache License, Version 2.0 (the "License");
14
-# you may not use this file except in compliance with the License.
15
-# You may obtain a copy of the License at
16
-#
17
-# http://www.apache.org/licenses/LICENSE-2.0
18
-#
19
-# Unless required by applicable law or agreed to in writing, software
20
-# distributed under the License is distributed on an "AS IS" BASIS,
21
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
22
-# See the License for the specific language governing permissions and
23
-# limitations under the License.
24
-"""Inference-only persimmon model compatible with HuggingFace weights."""
25
-
26
-from collections.abc import Iterable
27
-from itertools import islice
28
-
29
-import torch
30
-from torch import nn
31
-from transformers import PersimmonConfig
32
-
33
-from vllm.compilation.decorators import support_torch_compile
34
-from vllm.config import CacheConfig, VllmConfig
35
-from vllm.distributed import get_pp_group, get_tensor_model_parallel_world_size
36
-from vllm.model_executor.layers.activation import get_act_fn
37
-from vllm.model_executor.layers.attention import Attention
38
-from vllm.model_executor.layers.linear import (
39
- ColumnParallelLinear,
40
- QKVParallelLinear,
41
- RowParallelLinear,
42
-)
43
-from vllm.model_executor.layers.logits_processor import LogitsProcessor
44
-from vllm.model_executor.layers.quantization import QuantizationConfig
45
-from vllm.model_executor.layers.rotary_embedding import get_rope
46
-from vllm.model_executor.layers.vocab_parallel_embedding import (
47
- ParallelLMHead,
48
- VocabParallelEmbedding,
49
-)
50
-from vllm.model_executor.model_loader.weight_utils import default_weight_loader
51
-from vllm.sequence import IntermediateTensors
52
-
53
-from .interfaces import SupportsPP
54
-from .utils import (
55
- AutoWeightsLoader,
56
- is_pp_missing_parameter,
57
- make_empty_intermediate_tensors_factory,
58
- make_layers,
59
- maybe_prefix,
60
-)
61
-
62
-
63
-class PersimmonMLP(nn.Module):
64
- def __init__(
65
- self,
66
- config: PersimmonConfig,
67
- quant_config: QuantizationConfig | None = None,
68
- prefix: str = "",
69
- ):
70
- super().__init__()
71
- self.dense_h_to_4h = ColumnParallelLinear(
72
- config.hidden_size,
73
- config.intermediate_size,
74
- quant_config=quant_config,
75
- prefix=f"{prefix}.dense_h_to_4h",
76
- )
77
- self.dense_4h_to_h = RowParallelLinear(
78
- config.intermediate_size,
79
- config.hidden_size,
80
- quant_config=quant_config,
81
- prefix=f"{prefix}.dense_4h_to_h",
82
- )
83
- self.act = get_act_fn(config.hidden_act)
84
-
85
- def forward(self, hidden_states) -> torch.Tensor:
86
- hidden_states, _ = self.dense_h_to_4h(hidden_states)
87
- hidden_states = self.act(hidden_states)
88
- hidden_states, _ = self.dense_4h_to_h(hidden_states)
89
- return hidden_states
90
-
91
-
92
-class PersimmonAttention(nn.Module):
93
- def __init__(
94
- self,
95
- config: PersimmonConfig,
96
- cache_config: CacheConfig | None = None,
97
- quant_config: QuantizationConfig | None = None,
98
- prefix: str = "",
99
- ):
100
- super().__init__()
101
- self.config = config
102
- tensor_parallel_world_size = get_tensor_model_parallel_world_size()
103
-
104
- self.hidden_size = config.hidden_size
105
- self.total_num_heads = config.num_attention_heads
106
- self.num_heads = self.total_num_heads // tensor_parallel_world_size
107
- self.head_dim = self.hidden_size // self.total_num_heads
108
- self.max_position_embeddings = config.max_position_embeddings
109
- self.is_causal = True
110
-
111
- assert (self.head_dim * self.total_num_heads) == self.hidden_size
112
- assert self.total_num_heads % tensor_parallel_world_size == 0
113
-
114
- self.query_key_value = QKVParallelLinear(
115
- self.hidden_size,
116
- self.head_dim,
117
- self.total_num_heads,
118
- bias=True,
119
- quant_config=quant_config,
120
- prefix=f"{prefix}.query_key_value",
121
- )
122
- self.dense = RowParallelLinear(
123
- self.total_num_heads * self.head_dim,
124
- self.hidden_size,
125
- bias=True,
126
- quant_config=quant_config,
127
- prefix=f"{prefix}.dense",
128
- )
129
- self.is_qk_layernorm = config.qk_layernorm
130
-
131
- if self.is_qk_layernorm:
132
- self.q_layernorm = nn.LayerNorm(self.head_dim)
133
- self.k_layernorm = nn.LayerNorm(self.head_dim)
134
-
135
- self.rotary_emb = get_rope(
136
- self.head_dim,
137
- max_position=self.max_position_embeddings,
138
- rope_parameters=config.rope_parameters,
139
- )
140
- self.scaling = self.head_dim**-0.5
141
- self.attn = Attention(
142
- self.num_heads,
143
- self.head_dim,
144
- scale=self.scaling,
145
- cache_config=cache_config,
146
- quant_config=quant_config,
147
- prefix=f"{prefix}.attn",
148
- )
149
-
150
- def _split_heads(self, x: torch.Tensor) -> torch.Tensor:
151
- # [seq_length, hidden_size] -> [seq_length, num_heads, head_dim]
152
- seq_length = x.shape[0]
153
- return x.view(seq_length, self.num_heads, self.head_dim)
154
-
155
- def _merge_heads(self, x: torch.Tensor) -> torch.Tensor:
156
- # [seq_length, num_heads, head_dim] -> [seq_length, hidden_size]
157
- seq_length = x.shape[0]
158
- return x.view(seq_length, self.num_heads * self.head_dim)
159
-
160
- def forward(
161
- self,
162
- position_ids: torch.Tensor,
163
- hidden_states: torch.Tensor,
164
- ) -> torch.Tensor:
165
- # [seq_length, 3 x hidden_size]
166
- qkv, _ = self.query_key_value(hidden_states)
167
- q, k, v = qkv.chunk(chunks=3, dim=-1)
168
-
169
- if self.is_qk_layernorm:
170
- # [seq_length, num_heads, head_dim]
171
- q = self._split_heads(q)
172
- k = self._split_heads(k)
173
-
174
- q = self.q_layernorm(q)
175
- k = self.k_layernorm(k)
176
-
177
- q = self._merge_heads(q)
178
- k = self._merge_heads(k)
179
-
180
- q, k = self.rotary_emb(position_ids, q, k)
181
- attn_output = self.attn(q, k, v)
182
- output, _ = self.dense(attn_output)
183
- return output
184
-
185
-
186
-class PersimmonDecoderLayer(nn.Module):
187
- def __init__(
188
- self,
189
- config: PersimmonConfig,
190
- cache_config: CacheConfig | None = None,
191
- quant_config: QuantizationConfig | None = None,
192
- prefix: str = "",
193
- ):
194
- super().__init__()
195
- self.hidden_size = config.hidden_size
196
- self.self_attn = PersimmonAttention(
197
- config=config,
198
- cache_config=cache_config,
199
- quant_config=quant_config,
200
- prefix=f"{prefix}.self_attn",
201
- )
202
- self.mlp = PersimmonMLP(
203
- config,
204
- quant_config=quant_config,
205
- prefix=f"{prefix}.mlp",
206
- )
207
- self.input_layernorm = nn.LayerNorm(
208
- config.hidden_size, eps=config.layer_norm_eps
209
- )
210
- self.post_attention_layernorm = nn.LayerNorm(
211
- config.hidden_size, eps=config.layer_norm_eps
212
- )
213
-
214
- def forward(
215
- self,
216
- position_ids: torch.Tensor,
217
- hidden_states: torch.Tensor,
218
- ) -> torch.Tensor:
219
- residual = hidden_states
220
-
221
- hidden_states = self.input_layernorm(hidden_states)
222
-
223
- # Self Attention
224
- hidden_states = self.self_attn(
225
- position_ids=position_ids,
226
- hidden_states=hidden_states,
227
- )
228
- hidden_states = residual + hidden_states
229
-
230
- # Fully Connected
231
- residual = hidden_states
232
- hidden_states = self.post_attention_layernorm(hidden_states)
233
- hidden_states = self.mlp(hidden_states)
234
-
235
- hidden_states = hidden_states + residual
236
-
237
- outputs = hidden_states
238
- return outputs
239
-
240
-
241
-@support_torch_compile
242
-class PersimmonModel(nn.Module):
243
- def __init__(self, *, vllm_config: VllmConfig, prefix: str = ""):
244
- super().__init__()
245
-
246
- config = vllm_config.model_config.hf_config
247
- cache_config = vllm_config.cache_config
248
- quant_config = vllm_config.quant_config
249
-
250
- self.vocab_size = config.vocab_size
251
- self.config = config
252
- self.embed_tokens = VocabParallelEmbedding(
253
- config.vocab_size, config.hidden_size
254
- )
255
- self.start_layer, self.end_layer, self.layers = make_layers(
256
- config.num_hidden_layers,
257
- lambda prefix: PersimmonDecoderLayer(
258
- config, cache_config, quant_config, prefix=prefix
259
- ),
260
- prefix=f"{prefix}.layers",
261
- )
262
- self.final_layernorm = nn.LayerNorm(
263
- config.hidden_size, eps=config.layer_norm_eps
264
- )
265
- self.make_empty_intermediate_tensors = make_empty_intermediate_tensors_factory(
266
- ["hidden_states"], config.hidden_size
267
- )
268
-
269
- def embed_input_ids(self, input_ids: torch.Tensor) -> torch.Tensor:
270
- return self.embed_tokens(input_ids)
271
-
272
- def forward(
273
- self,
274
- input_ids: torch.Tensor | None,
275
- positions: torch.Tensor,
276
- intermediate_tensors: IntermediateTensors | None,
277
- inputs_embeds: torch.Tensor | None = None,
278
- ) -> torch.Tensor | IntermediateTensors:
279
- if get_pp_group().is_first_rank:
280
- if inputs_embeds is not None:
281
- hidden_states = inputs_embeds
282
- else:
283
- hidden_states = self.embed_input_ids(input_ids)
284
- else:
285
- assert intermediate_tensors is not None
286
- hidden_states = intermediate_tensors["hidden_states"]
287
- for layer in islice(self.layers, self.start_layer, self.end_layer):
288
- hidden_states = layer(positions, hidden_states)
289
- if not get_pp_group().is_last_rank:
290
- return IntermediateTensors({"hidden_states": hidden_states})
291
- hidden_states = self.final_layernorm(hidden_states)
292
- return hidden_states
293
-
294
- def load_weights(self, weights: Iterable[tuple[str, torch.Tensor]]) -> set[str]:
295
- params_dict = dict(self.named_parameters(remove_duplicate=False))
296
- loaded_params: set[str] = set()
297
- for name, loaded_weight in weights:
298
- if is_pp_missing_parameter(name, self):
299
- continue
300
- param = params_dict[name]
301
-
302
- if "query_key_value" in name:
303
- # copy from vllm/model_executor/models/bloom.py
304
- # NOTE: Persimmon's fused QKV's output_dim has the shape of
305
- # (num_heads * 3 * head_size), while the
306
- # required shape is (3 * num_heads * head_size).
307
- # Thus, we need weight conversion.
308
- output_dim = getattr(param, "output_dim", None)
309
- num_heads = self.config.num_attention_heads
310
- if output_dim is not None:
311
- loaded_weight_shape = loaded_weight.shape
312
- loaded_weight = loaded_weight.view(
313
- loaded_weight_shape[:output_dim]
314
- + (num_heads, 3, -1)
315
- + loaded_weight_shape[output_dim + 1 :]
316
- )
317
- loaded_weight = loaded_weight.transpose(output_dim, output_dim + 1)
318
- loaded_weight = loaded_weight.reshape(loaded_weight_shape)
319
-
320
- weight_loader = getattr(param, "weight_loader", default_weight_loader)
321
- weight_loader(param, loaded_weight)
322
- loaded_params.add(name)
323
- return loaded_params
324
-
325
-
326
-class PersimmonForCausalLM(nn.Module, SupportsPP):
327
- def __init__(self, *, vllm_config: VllmConfig, prefix: str = ""):
328
- super().__init__()
329
- config = vllm_config.model_config.hf_config
330
- self.config = config
331
- self.vocab_size = config.vocab_size
332
- self.model = PersimmonModel(
333
- vllm_config=vllm_config, prefix=maybe_prefix(prefix, "model")
334
- )
335
- self.lm_head = ParallelLMHead(
336
- config.vocab_size,
337
- config.hidden_size,
338
- bias=False,
339
- prefix=maybe_prefix(prefix, "lm_head"),
340
- )
341
- self.logits_processor = LogitsProcessor(config.vocab_size)
342
- self.make_empty_intermediate_tensors = (
343
- self.model.make_empty_intermediate_tensors
344
- )
345
-
346
- def embed_input_ids(self, input_ids: torch.Tensor) -> torch.Tensor:
347
- return self.model.embed_input_ids(input_ids)
348
-
349
- def forward(
350
- self,
351
- input_ids: torch.Tensor | None,
352
- positions: torch.Tensor,
353
- intermediate_tensors: IntermediateTensors | None = None,
354
- inputs_embeds: torch.Tensor | None = None,
355
- ):
356
- hidden_states = self.model(
357
- input_ids=input_ids,
358
- positions=positions,
359
- intermediate_tensors=intermediate_tensors,
360
- inputs_embeds=inputs_embeds,
361
- )
362
- return hidden_states
363
-
364
- def compute_logits(
365
- self,
366
- hidden_states: torch.Tensor,
367
- ) -> torch.Tensor | None:
368
- logits = self.logits_processor(self.lm_head, hidden_states)
369
- return logits
370
-
371
- def load_weights(self, weights: Iterable[tuple[str, torch.Tensor]]) -> set[str]:
372
- loader = AutoWeightsLoader(self)
373
- return loader.load_weights(weights)
vllm/model_executor/models/registry.py
+2
-2
@@ -180,7 +180,6 @@ _TEXT_GENERATION_MODELS = {
180
"PanguProMoEV2ForCausalLM": ("openpangu", "PanguProMoEV2ForCausalLM"),
181
"PanguUltraMoEForCausalLM": ("openpangu", "PanguUltraMoEForCausalLM"),
182
"Param2MoEForCausalLM": ("param2moe", "Param2MoEForCausalLM"),
183
- "PersimmonForCausalLM": ("persimmon", "PersimmonForCausalLM"),
183
"PhiForCausalLM": ("phi", "PhiForCausalLM"),
184
"Phi3ForCausalLM": ("phi3", "Phi3ForCausalLM"),
185
"PhiMoEForCausalLM": ("phimoe", "PhiMoEForCausalLM"),
@@ -377,7 +376,6 @@ _MULTIMODAL_MODELS = {
376
"funaudiochat",
377
"FunAudioChatForConditionalGeneration",
378
),
380
- "FuyuForCausalLM": ("fuyu", "FuyuForCausalLM"),
379
"Gemma3ForConditionalGeneration": ("gemma3_mm", "Gemma3ForConditionalGeneration"),
380
"Gemma3nForConditionalGeneration": (
381
"gemma3n_mm",
@@ -738,6 +736,8 @@ _PREVIOUSLY_SUPPORTED_MODELS = {
736
"MusicFlamingoForConditionalGeneration": "0.24.0",
737
"AyaVisionForConditionalGeneration": "0.24.0",
738
"TeleChatForCausalLM": "0.25.0",
739
+ "PersimmonForCausalLM": "0.25.0",
740
+ "FuyuForCausalLM": "0.25.0",
741
}
742
743
_OOT_SUPPORTED_MODELS = {
vllm/transformers_utils/chat_templates/registry.py
-1
@@ -31,7 +31,6 @@ _MODEL_TYPE_TO_CHAT_TEMPLATE_FALLBACK: dict[str, ChatTemplatePath] = {
31
"deepseek_ocr2": CHAT_TEMPLATES_DIR / "template_deepseek_ocr.jinja",
32
"unlimited-ocr": CHAT_TEMPLATES_DIR / "template_deepseek_ocr.jinja",
33
"deepseek_vl_v2": CHAT_TEMPLATES_DIR / "template_deepseek_vl2.jinja",
34
- "fuyu": CHAT_TEMPLATES_DIR / "template_fuyu.jinja",
34
"minicpmv": _get_minicpmv_chat_template_fallback,
35
"minicpmv4_6": _get_minicpmv_chat_template_fallback,
36
"paligemma": CHAT_TEMPLATES_DIR / "template_basic.jinja",
vllm/transformers_utils/chat_templates/template_fuyu.jinja
deleted
-3
@@ -1,3 +0,0 @@
1
-{%- for message in messages -%}
2
- {{- message['content'] + '\n' -}}
3
-{%- endfor -%}