Add LLaVa NeXT Video (#31252)
* squash into single commit * run diff once more * docstring * tests * minor chnages and ready to go * Update src/transformers/models/llava_next_video/processing_llava_next_video.py Co-authored-by: amyeroberts <22614925+amyeroberts@users.noreply.github.com> * Update tests/models/vipllava/test_modeling_vipllava.py Co-authored-by: amyeroberts <22614925+amyeroberts@users.noreply.github.com> * [run-slow] llava-next-video * [run-slow] llava-next-video * [run-slow] llava_next_video * fix two tests * fix slow tests * remove logit checks due to numeric errors * run test once more * [run-slow] llava_next_video * final try to pass the test * [run-slow] llava_next_video * [run-slow] llava_next_video * [run-slow] llava_next_video * style * fix * style --------- Co-authored-by: amyeroberts <22614925+amyeroberts@users.noreply.github.com> Co-authored-by: ydshieh <ydshieh@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
parent
b1ec745475
commit
e71f2863d7
@@ -794,6 +794,8 @@
|
|||||||
title: Llava
|
title: Llava
|
||||||
- local: model_doc/llava_next
|
- local: model_doc/llava_next
|
||||||
title: LLaVA-NeXT
|
title: LLaVA-NeXT
|
||||||
|
- local: model_doc/llava-next-video
|
||||||
|
title: LLaVa-NeXT-Video
|
||||||
- local: model_doc/lxmert
|
- local: model_doc/lxmert
|
||||||
title: LXMERT
|
title: LXMERT
|
||||||
- local: model_doc/matcha
|
- local: model_doc/matcha
|
||||||
|
|||||||
@@ -182,6 +182,7 @@ Flax), PyTorch, and/or TensorFlow.
|
|||||||
| [Llama3](model_doc/llama3) | ✅ | ❌ | ✅ |
|
| [Llama3](model_doc/llama3) | ✅ | ❌ | ✅ |
|
||||||
| [LLaVa](model_doc/llava) | ✅ | ❌ | ❌ |
|
| [LLaVa](model_doc/llava) | ✅ | ❌ | ❌ |
|
||||||
| [LLaVA-NeXT](model_doc/llava_next) | ✅ | ❌ | ❌ |
|
| [LLaVA-NeXT](model_doc/llava_next) | ✅ | ❌ | ❌ |
|
||||||
|
| [LLaVa-NeXT-Video](model_doc/llava-next-video) | ✅ | ❌ | ❌ |
|
||||||
| [Longformer](model_doc/longformer) | ✅ | ✅ | ❌ |
|
| [Longformer](model_doc/longformer) | ✅ | ✅ | ❌ |
|
||||||
| [LongT5](model_doc/longt5) | ✅ | ❌ | ✅ |
|
| [LongT5](model_doc/longt5) | ✅ | ❌ | ✅ |
|
||||||
| [LUKE](model_doc/luke) | ✅ | ❌ | ❌ |
|
| [LUKE](model_doc/luke) | ✅ | ❌ | ❌ |
|
||||||
|
|||||||
259
docs/source/en/model_doc/llava-next-video.md
Normal file
259
docs/source/en/model_doc/llava-next-video.md
Normal file
@@ -0,0 +1,259 @@
|
|||||||
|
<!--Copyright 2024 The HuggingFace Team. All rights reserved.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
|
||||||
|
the License. You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
|
||||||
|
an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||||
|
specific language governing permissions and limitations under the License.
|
||||||
|
|
||||||
|
⚠️ Note that this file is in Markdown but contain specific syntax for our doc-builder (similar to MDX) that may not be
|
||||||
|
rendered properly in your Markdown viewer.
|
||||||
|
|
||||||
|
-->
|
||||||
|
|
||||||
|
# LLaVa-NeXT-Video
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
The LLaVa-NeXT-Video model was proposed in [LLaVA-NeXT: A Strong Zero-shot Video Understanding Model
|
||||||
|
](https://llava-vl.github.io/blog/2024-04-30-llava-next-video/) by Yuanhan Zhang, Bo Li, Haotian Liu, Yong Jae Lee, Liangke Gui, Di Fu, Jiashi Feng, Ziwei Liu, Chunyuan Li. LLaVa-NeXT-Video improves upon [LLaVa-NeXT](llava_next) by fine-tuning on a mix if video and image dataset thus increasing the model's performance on videos.
|
||||||
|
|
||||||
|
[LLaVA-NeXT](llava_next) surprisingly has strong performance in understanding video content in zero-shot fashion with the AnyRes technique that it uses. The AnyRes technique naturally represents a high-resolution image into multiple images. This technique is naturally generalizable to represent videos because videos can be considered as a set of frames (similar to a set of images in LLaVa-NeXT). The current version of LLaVA-NeXT makes use of AnyRes and trains with supervised fine-tuning (SFT) on top of LLaVA-Next on video data to achieves better video understanding capabilities.The model is a current SOTA among open-source models on [VideoMME bench](https://arxiv.org/abs/2405.21075).
|
||||||
|
|
||||||
|
|
||||||
|
The introduction from the blog is the following:
|
||||||
|
|
||||||
|
On January 30, 2024, we released LLaVA-NeXT, an open-source Large Multimodal Model (LMM) that has been trained exclusively on text-image data. With the proposed AnyRes technique, it boosts capabilities in reasoning, OCR, and world knowledge, demonstrating remarkable performance across a spectrum of image-based multimodal understanding tasks, and even exceeding Gemini-Pro on several image benchmarks, e.g. MMMU and MathVista.
|
||||||
|
|
||||||
|
**In today’s exploration, we delve into the performance of LLaVA-NeXT within the realm of video understanding tasks. We reveal that LLaVA-NeXT surprisingly has strong performance in understanding video content. The current version of LLaVA-NeXT for videos has several improvements:
|
||||||
|
|
||||||
|
- Zero-shot video representation capabilities with AnyRes: The AnyRes technique naturally represents a high-resolution image into multiple images that a pre-trained VIT is able to digest, and forms them into a concantenated sequence. This technique is naturally generalizable to represent videos (consisting of multiple frames), allowing the image-only-trained LLaVA-Next model to perform surprisingly well on video tasks. Notably, this is the first time that LMMs show strong zero-shot modality transfer ability.
|
||||||
|
- Inference with length generalization improves on longer videos. The linear scaling technique enables length generalization, allowing LLaVA-NeXT to effectively handle long-video beyond the limitation of the "max_token_length" of the LLM.
|
||||||
|
- Strong video understanding ability. (1) LLaVA-Next-Image, which combines the above two techniques, yields superior zero-shot performance than open-source LMMs tuned on videos. (2) LLaVA-Next-Video, further supervised fine-tuning (SFT) LLaVA-Next-Image on video data, achieves better video understanding capabilities compared to LLaVA-Next-Image. (3) LLaVA-Next-Video-DPO, which aligns the model response with AI feedback using direct preference optimization (DPO), showing significant performance boost.
|
||||||
|
- Efficient deployment and inference with SGLang. It allows 5x faster inference on video tasks, allowing more scalable serving such as million-level video re-captioning. See instructions in our repo.**
|
||||||
|
|
||||||
|
|
||||||
|
This model was contributed by [RaushanTurganbay](https://huggingface.co/RaushanTurganbay).
|
||||||
|
The original code can be found [here](https://github.com/LLaVA-VL/LLaVA-NeXT/tree/inference).
|
||||||
|
|
||||||
|
## Usage tips
|
||||||
|
|
||||||
|
- We advise users to use `padding_side="left"` when computing batched generation as it leads to more accurate results. Simply make sure to call `processor.tokenizer.padding_side = "left"` before generating.
|
||||||
|
|
||||||
|
- Note that each checkpoint has been trained with a specific prompt format, depending on which large language model (LLM) was used. You can use tokenizer's `apply_chat_template` to format your prompts correctly. Below is an example of how to do that.
|
||||||
|
|
||||||
|
We will use [LLaVA-NeXT-Video-7B-hf](https://huggingface.co/llava-hf/LLaVA-NeXT-Video-7B-hf) and a conversation history of videos and images. Each content field has to be a list of dicts, as follows:
|
||||||
|
|
||||||
|
```python
|
||||||
|
from transformers import LlavaNextVideoProcessor
|
||||||
|
|
||||||
|
processor = LlavaNextVideoProcessor.from_pretrained("llava-hf/LLaVA-NeXT-Video-7B-hf")
|
||||||
|
|
||||||
|
conversation = [
|
||||||
|
{
|
||||||
|
"role": "system",
|
||||||
|
"content": [
|
||||||
|
{"type": "text", "text": "A chat between a curious human and an artificial intelligence assistant. The assistant gives helpful, detailed, and polite answers to the human's questions."},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"role": "user",
|
||||||
|
"content": [
|
||||||
|
{"type": "text", "text": "What’s shown in this image?"},
|
||||||
|
{"type": "image"},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"role": "assistant",
|
||||||
|
"content": [{"type": "text", "text": "This image shows a red stop sign."},]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
|
||||||
|
"role": "user",
|
||||||
|
"content": [
|
||||||
|
{"type": "text", "text": "Why is this video funny?"},
|
||||||
|
{"type": "video"},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
text_prompt = processor.apply_chat_template(conversation, add_generation_prompt=True)
|
||||||
|
|
||||||
|
# Note that the template simply formats your prompt, you still have to tokenize it and obtain pixel values for your visuals
|
||||||
|
print(text_prompt)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage example
|
||||||
|
|
||||||
|
### Single Media Mode
|
||||||
|
|
||||||
|
The model can accept both images and videos as input. Here's an example code for inference in half-precision (`torch.float16`):
|
||||||
|
|
||||||
|
```python
|
||||||
|
import av
|
||||||
|
import torch
|
||||||
|
import numpy as np
|
||||||
|
from transformers import LlavaNextVideoForConditionalGeneration, LlavaNextVideoProcessor
|
||||||
|
|
||||||
|
def read_video_pyav(container, indices):
|
||||||
|
'''
|
||||||
|
Decode the video with PyAV decoder.
|
||||||
|
Args:
|
||||||
|
container (`av.container.input.InputContainer`): PyAV container.
|
||||||
|
indices (`List[int]`): List of frame indices to decode.
|
||||||
|
Returns:
|
||||||
|
result (np.ndarray): np array of decoded frames of shape (num_frames, height, width, 3).
|
||||||
|
'''
|
||||||
|
frames = []
|
||||||
|
container.seek(0)
|
||||||
|
start_index = indices[0]
|
||||||
|
end_index = indices[-1]
|
||||||
|
for i, frame in enumerate(container.decode(video=0)):
|
||||||
|
if i > end_index:
|
||||||
|
break
|
||||||
|
if i >= start_index and i in indices:
|
||||||
|
frames.append(frame)
|
||||||
|
return np.stack([x.to_ndarray(format="rgb24") for x in frames])
|
||||||
|
|
||||||
|
# Load the model in half-precision
|
||||||
|
model = LlavaNextVideoForConditionalGeneration.from_pretrained("llava-hf/LLaVA-NeXT-Video-7B-hf", torch_dtype=torch.float16, device_map="auto")
|
||||||
|
processor = LlavaNextVideoProcessor.from_pretrained("llava-hf/LLaVA-NeXT-Video-7B-hf")
|
||||||
|
|
||||||
|
# Load the video as an np.array, sampling uniformly 8 frames (can sample more for longer videos)
|
||||||
|
video_path = hf_hub_download(repo_id="raushan-testing-hf/videos-test", filename="sample_demo_1.mp4", repo_type="dataset")
|
||||||
|
container = av.open(video_path)
|
||||||
|
total_frames = container.streams.video[0].frames
|
||||||
|
indices = np.arange(0, total_frames, total_frames / 8).astype(int)
|
||||||
|
video = read_video_pyav(container, indices)
|
||||||
|
|
||||||
|
conversation = [
|
||||||
|
{
|
||||||
|
|
||||||
|
"role": "user",
|
||||||
|
"content": [
|
||||||
|
{"type": "text", "text": "Why is this video funny?"},
|
||||||
|
{"type": "video"},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
prompt = processor.apply_chat_template(conversation, add_generation_prompt=True)
|
||||||
|
inputs = processor(text=prompt, videos=video, return_tensors="pt")
|
||||||
|
|
||||||
|
out = model.generate(**inputs, max_new_tokens=60)
|
||||||
|
processor.batch_decode(out, skip_special_tokens=True, clean_up_tokenization_spaces=True)
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### Mixed Media Mode
|
||||||
|
|
||||||
|
The model can also generate from an interleaved image-video inputs. However note, that it was not trained in interleaved image-video setting which might affect the performance. Below is an example usage for mixed media input, add the following lines to the above code snippet:
|
||||||
|
|
||||||
|
```python
|
||||||
|
from PIL import Image
|
||||||
|
import requests
|
||||||
|
|
||||||
|
# Generate from image and video mixed inputs
|
||||||
|
# Load and image and write a new prompt
|
||||||
|
url = "http://images.cocodataset.org/val2017/000000039769.jpg"
|
||||||
|
image = Image.open(requests.get(url, stream=True).raw)
|
||||||
|
conversation = [
|
||||||
|
{
|
||||||
|
|
||||||
|
"role": "user",
|
||||||
|
"content": [
|
||||||
|
{"type": "text", "text": "How many cats are there in the image?"},
|
||||||
|
{"type": "image"},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
|
||||||
|
"role": "assistant",
|
||||||
|
"content": [{"type": "text", "text": "There are two cats"}],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
|
||||||
|
"role": "user",
|
||||||
|
"content": [
|
||||||
|
{"type": "text", "text": "Why is this video funny?"},
|
||||||
|
{"type": "video"},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]
|
||||||
|
prompt = processor.apply_chat_template(conversation, add_generation_prompt=True)
|
||||||
|
inputs = processor(text=prompt, images=image, videos=clip, padding=True, return_tensors="pt")
|
||||||
|
|
||||||
|
# Generate
|
||||||
|
generate_ids = model.generate(**inputs, max_length=50)
|
||||||
|
processor.batch_decode(generate_ids, skip_special_tokens=True, clean_up_tokenization_spaces=True)
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
## Model optimization
|
||||||
|
|
||||||
|
### Quantization using Bitsandbytes for memory efficiency
|
||||||
|
|
||||||
|
The model can be loaded in lower bits, significantly reducing memory burden while maintaining the performance of the original model. This allows for efficient deployment on resource-constrained cases.
|
||||||
|
|
||||||
|
First make sure to install bitsandbytes by running `pip install bitsandbytes` and to have access to a CUDA compatible GPU device. Load the quantized model by simply adding [`BitsAndBytesConfig`](../main_classes/quantization#transformers.BitsAndBytesConfig) as shown below:
|
||||||
|
|
||||||
|
|
||||||
|
```python
|
||||||
|
from transformers import LlavaNextVideoForConditionalGeneration, LlavaNextVideoProcessor
|
||||||
|
|
||||||
|
# specify how to quantize the model
|
||||||
|
quantization_config = BitsAndBytesConfig(
|
||||||
|
load_in_4bit=True,
|
||||||
|
bnb_4bit_quant_type="nf4",
|
||||||
|
bnb_4bit_compute_dtype=torch.float16,
|
||||||
|
)
|
||||||
|
|
||||||
|
model = LlavaNextVideoForConditionalGeneration.from_pretrained("llava-hf/LLaVA-NeXT-Video-7B-hf", quantization_config=quantization_config, device_map="auto")
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### Flash-Attention 2 to speed-up generation
|
||||||
|
|
||||||
|
Additionally, we can greatly speed-up model inference by using [Flash Attention](../perf_train_gpu_one.md#flash-attention-2), which is a faster implementation of the attention mechanism used inside the model.
|
||||||
|
|
||||||
|
First, make sure to install the latest version of Flash Attention 2:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pip install -U flash-attn --no-build-isolation
|
||||||
|
```
|
||||||
|
|
||||||
|
Also, you should have a hardware that is compatible with Flash-Attention 2. Read more about it in the official documentation of the [flash attention repository](https://github.com/Dao-AILab/flash-attention). FlashAttention-2 can only be used when a model is loaded in `torch.float16` or `torch.bfloat16`.
|
||||||
|
|
||||||
|
To load and run a model using Flash Attention-2, simply add `attn_implementation="flash_attention_2"` when loading the model as follows:
|
||||||
|
|
||||||
|
```python
|
||||||
|
from transformers import LlavaNextVideoForConditionalGeneration
|
||||||
|
|
||||||
|
model = LlavaNextVideoForConditionalGeneration.from_pretrained(
|
||||||
|
"llava-hf/LLaVA-NeXT-Video-7B-hf",
|
||||||
|
torch_dtype=torch.float16,
|
||||||
|
attn_implementation="flash_attention_2",
|
||||||
|
).to(0)
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## LlavaNextVideoConfig
|
||||||
|
|
||||||
|
[[autodoc]] LlavaNextVideoConfig
|
||||||
|
|
||||||
|
## LlavaNextVideoProcessor
|
||||||
|
|
||||||
|
[[autodoc]] LlavaNextVideoProcessor
|
||||||
|
|
||||||
|
## LlavaNextVideoImageProcessor
|
||||||
|
|
||||||
|
[[autodoc]] LlavaNextVideoImageProcessor
|
||||||
|
|
||||||
|
## LlavaNextVideoForConditionalGeneration
|
||||||
|
|
||||||
|
[[autodoc]] LlavaNextVideoForConditionalGeneration
|
||||||
|
- forward
|
||||||
@@ -55,6 +55,7 @@ FlashAttention-2 is currently supported for the following architectures:
|
|||||||
* [Llama](https://huggingface.co/docs/transformers/model_doc/llama#transformers.LlamaModel)
|
* [Llama](https://huggingface.co/docs/transformers/model_doc/llama#transformers.LlamaModel)
|
||||||
* [Llava](https://huggingface.co/docs/transformers/model_doc/llava)
|
* [Llava](https://huggingface.co/docs/transformers/model_doc/llava)
|
||||||
* [Llava-NeXT](https://huggingface.co/docs/transformers/model_doc/llava_next)
|
* [Llava-NeXT](https://huggingface.co/docs/transformers/model_doc/llava_next)
|
||||||
|
* [Llava-NeXT-Video](https://huggingface.co/docs/transformers/model_doc/llava_next_video)
|
||||||
* [VipLlava](https://huggingface.co/docs/transformers/model_doc/vipllava)
|
* [VipLlava](https://huggingface.co/docs/transformers/model_doc/vipllava)
|
||||||
* [VideoLlava](https://huggingface.co/docs/transformers/model_doc/video_llava)
|
* [VideoLlava](https://huggingface.co/docs/transformers/model_doc/video_llava)
|
||||||
* [M2M100](https://huggingface.co/docs/transformers/model_doc/m2m_100)
|
* [M2M100](https://huggingface.co/docs/transformers/model_doc/m2m_100)
|
||||||
|
|||||||
@@ -516,6 +516,10 @@ _import_structure = {
|
|||||||
"LlavaNextConfig",
|
"LlavaNextConfig",
|
||||||
"LlavaNextProcessor",
|
"LlavaNextProcessor",
|
||||||
],
|
],
|
||||||
|
"models.llava_next_video": [
|
||||||
|
"LlavaNextVideoConfig",
|
||||||
|
"LlavaNextVideoProcessor",
|
||||||
|
],
|
||||||
"models.longformer": [
|
"models.longformer": [
|
||||||
"LongformerConfig",
|
"LongformerConfig",
|
||||||
"LongformerTokenizer",
|
"LongformerTokenizer",
|
||||||
@@ -1148,6 +1152,7 @@ else:
|
|||||||
_import_structure["models.layoutlmv3"].extend(["LayoutLMv3FeatureExtractor", "LayoutLMv3ImageProcessor"])
|
_import_structure["models.layoutlmv3"].extend(["LayoutLMv3FeatureExtractor", "LayoutLMv3ImageProcessor"])
|
||||||
_import_structure["models.levit"].extend(["LevitFeatureExtractor", "LevitImageProcessor"])
|
_import_structure["models.levit"].extend(["LevitFeatureExtractor", "LevitImageProcessor"])
|
||||||
_import_structure["models.llava_next"].append("LlavaNextImageProcessor")
|
_import_structure["models.llava_next"].append("LlavaNextImageProcessor")
|
||||||
|
_import_structure["models.llava_next_video"].append("LlavaNextVideoImageProcessor")
|
||||||
_import_structure["models.mask2former"].append("Mask2FormerImageProcessor")
|
_import_structure["models.mask2former"].append("Mask2FormerImageProcessor")
|
||||||
_import_structure["models.maskformer"].extend(["MaskFormerFeatureExtractor", "MaskFormerImageProcessor"])
|
_import_structure["models.maskformer"].extend(["MaskFormerFeatureExtractor", "MaskFormerImageProcessor"])
|
||||||
_import_structure["models.mobilenet_v1"].extend(["MobileNetV1FeatureExtractor", "MobileNetV1ImageProcessor"])
|
_import_structure["models.mobilenet_v1"].extend(["MobileNetV1FeatureExtractor", "MobileNetV1ImageProcessor"])
|
||||||
@@ -2432,6 +2437,12 @@ else:
|
|||||||
"LlavaNextPreTrainedModel",
|
"LlavaNextPreTrainedModel",
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
_import_structure["models.llava_next_video"].extend(
|
||||||
|
[
|
||||||
|
"LlavaNextVideoForConditionalGeneration",
|
||||||
|
"LlavaNextVideoPreTrainedModel",
|
||||||
|
]
|
||||||
|
)
|
||||||
_import_structure["models.longformer"].extend(
|
_import_structure["models.longformer"].extend(
|
||||||
[
|
[
|
||||||
"LongformerForMaskedLM",
|
"LongformerForMaskedLM",
|
||||||
@@ -5137,6 +5148,10 @@ if TYPE_CHECKING:
|
|||||||
LlavaNextConfig,
|
LlavaNextConfig,
|
||||||
LlavaNextProcessor,
|
LlavaNextProcessor,
|
||||||
)
|
)
|
||||||
|
from .models.llava_next_video import (
|
||||||
|
LlavaNextVideoConfig,
|
||||||
|
LlavaNextVideoProcessor,
|
||||||
|
)
|
||||||
from .models.longformer import (
|
from .models.longformer import (
|
||||||
LongformerConfig,
|
LongformerConfig,
|
||||||
LongformerTokenizer,
|
LongformerTokenizer,
|
||||||
@@ -5804,6 +5819,7 @@ if TYPE_CHECKING:
|
|||||||
)
|
)
|
||||||
from .models.levit import LevitFeatureExtractor, LevitImageProcessor
|
from .models.levit import LevitFeatureExtractor, LevitImageProcessor
|
||||||
from .models.llava_next import LlavaNextImageProcessor
|
from .models.llava_next import LlavaNextImageProcessor
|
||||||
|
from .models.llava_next_video import LlavaNextVideoImageProcessor
|
||||||
from .models.mask2former import Mask2FormerImageProcessor
|
from .models.mask2former import Mask2FormerImageProcessor
|
||||||
from .models.maskformer import (
|
from .models.maskformer import (
|
||||||
MaskFormerFeatureExtractor,
|
MaskFormerFeatureExtractor,
|
||||||
@@ -6874,6 +6890,10 @@ if TYPE_CHECKING:
|
|||||||
LlavaNextForConditionalGeneration,
|
LlavaNextForConditionalGeneration,
|
||||||
LlavaNextPreTrainedModel,
|
LlavaNextPreTrainedModel,
|
||||||
)
|
)
|
||||||
|
from .models.llava_next_video import (
|
||||||
|
LlavaNextVideoForConditionalGeneration,
|
||||||
|
LlavaNextVideoPreTrainedModel,
|
||||||
|
)
|
||||||
from .models.longformer import (
|
from .models.longformer import (
|
||||||
LongformerForMaskedLM,
|
LongformerForMaskedLM,
|
||||||
LongformerForMultipleChoice,
|
LongformerForMultipleChoice,
|
||||||
|
|||||||
@@ -125,6 +125,7 @@ from . import (
|
|||||||
llama,
|
llama,
|
||||||
llava,
|
llava,
|
||||||
llava_next,
|
llava_next,
|
||||||
|
llava_next_video,
|
||||||
longformer,
|
longformer,
|
||||||
longt5,
|
longt5,
|
||||||
luke,
|
luke,
|
||||||
|
|||||||
@@ -141,6 +141,7 @@ CONFIG_MAPPING_NAMES = OrderedDict(
|
|||||||
("lilt", "LiltConfig"),
|
("lilt", "LiltConfig"),
|
||||||
("llama", "LlamaConfig"),
|
("llama", "LlamaConfig"),
|
||||||
("llava", "LlavaConfig"),
|
("llava", "LlavaConfig"),
|
||||||
|
("llava-next-video", "LlavaNextVideoConfig"),
|
||||||
("llava_next", "LlavaNextConfig"),
|
("llava_next", "LlavaNextConfig"),
|
||||||
("longformer", "LongformerConfig"),
|
("longformer", "LongformerConfig"),
|
||||||
("longt5", "LongT5Config"),
|
("longt5", "LongT5Config"),
|
||||||
@@ -421,6 +422,7 @@ MODEL_NAMES_MAPPING = OrderedDict(
|
|||||||
("llama2", "Llama2"),
|
("llama2", "Llama2"),
|
||||||
("llama3", "Llama3"),
|
("llama3", "Llama3"),
|
||||||
("llava", "LLaVa"),
|
("llava", "LLaVa"),
|
||||||
|
("llava-next-video", "LLaVa-NeXT-Video"),
|
||||||
("llava_next", "LLaVA-NeXT"),
|
("llava_next", "LLaVA-NeXT"),
|
||||||
("longformer", "Longformer"),
|
("longformer", "Longformer"),
|
||||||
("longt5", "LongT5"),
|
("longt5", "LongT5"),
|
||||||
|
|||||||
@@ -95,6 +95,7 @@ else:
|
|||||||
("layoutlmv3", ("LayoutLMv3ImageProcessor",)),
|
("layoutlmv3", ("LayoutLMv3ImageProcessor",)),
|
||||||
("levit", ("LevitImageProcessor",)),
|
("levit", ("LevitImageProcessor",)),
|
||||||
("llava", ("CLIPImageProcessor",)),
|
("llava", ("CLIPImageProcessor",)),
|
||||||
|
("llava-next-video", ("LlavaNextVideoImageProcessor",)),
|
||||||
("llava_next", ("LlavaNextImageProcessor",)),
|
("llava_next", ("LlavaNextImageProcessor",)),
|
||||||
("mask2former", ("Mask2FormerImageProcessor",)),
|
("mask2former", ("Mask2FormerImageProcessor",)),
|
||||||
("maskformer", ("MaskFormerImageProcessor",)),
|
("maskformer", ("MaskFormerImageProcessor",)),
|
||||||
|
|||||||
@@ -299,6 +299,7 @@ MODEL_FOR_PRETRAINING_MAPPING_NAMES = OrderedDict(
|
|||||||
("idefics2", "Idefics2ForConditionalGeneration"),
|
("idefics2", "Idefics2ForConditionalGeneration"),
|
||||||
("layoutlm", "LayoutLMForMaskedLM"),
|
("layoutlm", "LayoutLMForMaskedLM"),
|
||||||
("llava", "LlavaForConditionalGeneration"),
|
("llava", "LlavaForConditionalGeneration"),
|
||||||
|
("llava-next-video", "LlavaNextVideoForConditionalGeneration"),
|
||||||
("llava_next", "LlavaNextForConditionalGeneration"),
|
("llava_next", "LlavaNextForConditionalGeneration"),
|
||||||
("longformer", "LongformerForMaskedLM"),
|
("longformer", "LongformerForMaskedLM"),
|
||||||
("luke", "LukeForMaskedLM"),
|
("luke", "LukeForMaskedLM"),
|
||||||
@@ -700,6 +701,7 @@ MODEL_FOR_VISION_2_SEQ_MAPPING_NAMES = OrderedDict(
|
|||||||
("instructblipvideo", "InstructBlipVideoForConditionalGeneration"),
|
("instructblipvideo", "InstructBlipVideoForConditionalGeneration"),
|
||||||
("kosmos-2", "Kosmos2ForConditionalGeneration"),
|
("kosmos-2", "Kosmos2ForConditionalGeneration"),
|
||||||
("llava", "LlavaForConditionalGeneration"),
|
("llava", "LlavaForConditionalGeneration"),
|
||||||
|
("llava-next-video", "LlavaNextVideoForConditionalGeneration"),
|
||||||
("llava_next", "LlavaNextForConditionalGeneration"),
|
("llava_next", "LlavaNextForConditionalGeneration"),
|
||||||
("paligemma", "PaliGemmaForConditionalGeneration"),
|
("paligemma", "PaliGemmaForConditionalGeneration"),
|
||||||
("pix2struct", "Pix2StructForConditionalGeneration"),
|
("pix2struct", "Pix2StructForConditionalGeneration"),
|
||||||
|
|||||||
@@ -69,6 +69,7 @@ PROCESSOR_MAPPING_NAMES = OrderedDict(
|
|||||||
("layoutlmv2", "LayoutLMv2Processor"),
|
("layoutlmv2", "LayoutLMv2Processor"),
|
||||||
("layoutlmv3", "LayoutLMv3Processor"),
|
("layoutlmv3", "LayoutLMv3Processor"),
|
||||||
("llava", "LlavaProcessor"),
|
("llava", "LlavaProcessor"),
|
||||||
|
("llava-next-video", "LlavaNextVideoProcessor"),
|
||||||
("llava_next", "LlavaNextProcessor"),
|
("llava_next", "LlavaNextProcessor"),
|
||||||
("markuplm", "MarkupLMProcessor"),
|
("markuplm", "MarkupLMProcessor"),
|
||||||
("mctct", "MCTCTProcessor"),
|
("mctct", "MCTCTProcessor"),
|
||||||
|
|||||||
@@ -242,6 +242,7 @@ else:
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
("llava", ("LlamaTokenizer", "LlamaTokenizerFast" if is_tokenizers_available() else None)),
|
("llava", ("LlamaTokenizer", "LlamaTokenizerFast" if is_tokenizers_available() else None)),
|
||||||
|
("llava-next-video", ("LlamaTokenizer", "LlamaTokenizerFast" if is_tokenizers_available() else None)),
|
||||||
("llava_next", ("LlamaTokenizer", "LlamaTokenizerFast" if is_tokenizers_available() else None)),
|
("llava_next", ("LlamaTokenizer", "LlamaTokenizerFast" if is_tokenizers_available() else None)),
|
||||||
("longformer", ("LongformerTokenizer", "LongformerTokenizerFast" if is_tokenizers_available() else None)),
|
("longformer", ("LongformerTokenizer", "LongformerTokenizerFast" if is_tokenizers_available() else None)),
|
||||||
(
|
(
|
||||||
|
|||||||
@@ -545,8 +545,9 @@ class LlavaNextForConditionalGeneration(LlavaNextPreTrainedModel):
|
|||||||
)
|
)
|
||||||
# Compute the maximum embed dimension
|
# Compute the maximum embed dimension
|
||||||
# max_image_feature_lens is max_feature_lens per batch
|
# max_image_feature_lens is max_feature_lens per batch
|
||||||
|
feature_lens = feature_lens.to(input_ids.device)
|
||||||
feature_lens_batch = feature_lens.split(num_special_image_tokens.tolist(), dim=0)
|
feature_lens_batch = feature_lens.split(num_special_image_tokens.tolist(), dim=0)
|
||||||
feature_lens_batch_sum = torch.tensor([x.sum() for x in feature_lens_batch], device=feature_lens.device)
|
feature_lens_batch_sum = torch.tensor([x.sum() for x in feature_lens_batch], device=input_ids.device)
|
||||||
embed_sequence_lengths = (
|
embed_sequence_lengths = (
|
||||||
(attention_mask == 1).long().sum(-1) - num_special_image_tokens + feature_lens_batch_sum
|
(attention_mask == 1).long().sum(-1) - num_special_image_tokens + feature_lens_batch_sum
|
||||||
)
|
)
|
||||||
@@ -577,9 +578,9 @@ class LlavaNextForConditionalGeneration(LlavaNextPreTrainedModel):
|
|||||||
final_attention_mask = torch.zeros(
|
final_attention_mask = torch.zeros(
|
||||||
batch_size, max_embed_dim, dtype=attention_mask.dtype, device=inputs_embeds.device
|
batch_size, max_embed_dim, dtype=attention_mask.dtype, device=inputs_embeds.device
|
||||||
)
|
)
|
||||||
final_labels = None
|
final_input_ids = torch.full(
|
||||||
if labels is not None:
|
(batch_size, max_embed_dim), self.pad_token_id, dtype=input_ids.dtype, device=inputs_embeds.device
|
||||||
final_labels = torch.full_like(final_attention_mask, ignore_index).to(torch.long)
|
)
|
||||||
# In case the Vision model or the Language model has been offloaded to CPU, we need to manually
|
# In case the Vision model or the Language model has been offloaded to CPU, we need to manually
|
||||||
# set the corresponding tensors into their correct target device.
|
# set the corresponding tensors into their correct target device.
|
||||||
target_device = inputs_embeds.device
|
target_device = inputs_embeds.device
|
||||||
@@ -589,12 +590,17 @@ class LlavaNextForConditionalGeneration(LlavaNextPreTrainedModel):
|
|||||||
text_to_overwrite.to(target_device),
|
text_to_overwrite.to(target_device),
|
||||||
)
|
)
|
||||||
attention_mask = attention_mask.to(target_device)
|
attention_mask = attention_mask.to(target_device)
|
||||||
|
input_ids = input_ids.to(target_device)
|
||||||
|
|
||||||
# 4. Fill the embeddings based on the mask. If we have ["hey" "<image>", "how", "are"]
|
# 4. Fill the embeddings based on the mask. If we have ["hey" "<image>", "how", "are"]
|
||||||
# we need to index copy on [0, 577, 578, 579] for the text and [1:576] for the image features
|
# we need to index copy on [0, 577, 578, 579] for the text and [1:576] for the image features
|
||||||
final_embedding[batch_indices, text_to_overwrite] = inputs_embeds[batch_indices, non_image_indices]
|
final_embedding[batch_indices, text_to_overwrite] = inputs_embeds[batch_indices, non_image_indices]
|
||||||
final_attention_mask[batch_indices, text_to_overwrite] = attention_mask[batch_indices, non_image_indices]
|
final_attention_mask[batch_indices, text_to_overwrite] = attention_mask[batch_indices, non_image_indices]
|
||||||
|
final_input_ids[batch_indices, text_to_overwrite] = input_ids[batch_indices, non_image_indices]
|
||||||
|
final_labels = None
|
||||||
if labels is not None:
|
if labels is not None:
|
||||||
|
labels = labels.to(target_device)
|
||||||
|
final_labels = torch.full_like(final_attention_mask, ignore_index).to(torch.long)
|
||||||
final_labels[batch_indices, text_to_overwrite] = labels[batch_indices, non_image_indices]
|
final_labels[batch_indices, text_to_overwrite] = labels[batch_indices, non_image_indices]
|
||||||
|
|
||||||
# 5. Fill the embeddings corresponding to the images. Anything that is not `text_positions` needs filling (#29835)
|
# 5. Fill the embeddings corresponding to the images. Anything that is not `text_positions` needs filling (#29835)
|
||||||
@@ -609,6 +615,7 @@ class LlavaNextForConditionalGeneration(LlavaNextPreTrainedModel):
|
|||||||
|
|
||||||
if left_padding:
|
if left_padding:
|
||||||
# exclude padding on the left
|
# exclude padding on the left
|
||||||
|
max_embed_dim = max_embed_dim.to(target_device)
|
||||||
val = (max_embed_dim - embed_indices) <= embed_seq_lens
|
val = (max_embed_dim - embed_indices) <= embed_seq_lens
|
||||||
else:
|
else:
|
||||||
# exclude padding on the right
|
# exclude padding on the right
|
||||||
@@ -626,7 +633,7 @@ class LlavaNextForConditionalGeneration(LlavaNextPreTrainedModel):
|
|||||||
final_attention_mask |= image_to_overwrite
|
final_attention_mask |= image_to_overwrite
|
||||||
position_ids = (final_attention_mask.cumsum(-1) - 1).masked_fill_((final_attention_mask == 0), 1)
|
position_ids = (final_attention_mask.cumsum(-1) - 1).masked_fill_((final_attention_mask == 0), 1)
|
||||||
|
|
||||||
return final_embedding, final_attention_mask, position_ids, final_labels
|
return final_embedding, final_attention_mask, position_ids, final_labels, final_input_ids
|
||||||
|
|
||||||
def pack_image_features(self, image_features, image_sizes, image_newline=None):
|
def pack_image_features(self, image_features, image_sizes, image_newline=None):
|
||||||
"""
|
"""
|
||||||
@@ -796,7 +803,7 @@ class LlavaNextForConditionalGeneration(LlavaNextPreTrainedModel):
|
|||||||
)
|
)
|
||||||
|
|
||||||
inputs_embeds = inputs_embeds.to(image_features.dtype)
|
inputs_embeds = inputs_embeds.to(image_features.dtype)
|
||||||
inputs_embeds, attention_mask, position_ids, labels = self._merge_input_ids_with_image_features(
|
inputs_embeds, attention_mask, position_ids, labels, _ = self._merge_input_ids_with_image_features(
|
||||||
image_features,
|
image_features,
|
||||||
feature_lens,
|
feature_lens,
|
||||||
inputs_embeds,
|
inputs_embeds,
|
||||||
|
|||||||
70
src/transformers/models/llava_next_video/__init__.py
Normal file
70
src/transformers/models/llava_next_video/__init__.py
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
# Copyright 2024 The HuggingFace Team. All rights reserved.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
|
from ...utils import OptionalDependencyNotAvailable, _LazyModule, is_torch_available, is_vision_available
|
||||||
|
|
||||||
|
|
||||||
|
_import_structure = {
|
||||||
|
"configuration_llava_next_video": ["LlavaNextVideoConfig"],
|
||||||
|
"processing_llava_next_video": ["LlavaNextVideoProcessor"],
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
try:
|
||||||
|
if not is_vision_available():
|
||||||
|
raise OptionalDependencyNotAvailable()
|
||||||
|
except OptionalDependencyNotAvailable:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
_import_structure["image_processing_llava_next_video"] = ["LlavaNextVideoImageProcessor"]
|
||||||
|
|
||||||
|
try:
|
||||||
|
if not is_torch_available():
|
||||||
|
raise OptionalDependencyNotAvailable()
|
||||||
|
except OptionalDependencyNotAvailable:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
_import_structure["modeling_llava_next_video"] = [
|
||||||
|
"LlavaNextVideoForConditionalGeneration",
|
||||||
|
"LlavaNextVideoPreTrainedModel",
|
||||||
|
]
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from .configuration_llava_next_video import LlavaNextVideoConfig
|
||||||
|
from .processing_llava_next_video import LlavaNextVideoProcessor
|
||||||
|
|
||||||
|
try:
|
||||||
|
if not is_vision_available():
|
||||||
|
raise OptionalDependencyNotAvailable()
|
||||||
|
except OptionalDependencyNotAvailable:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
from .image_processing_llava_next_video import LlavaNextVideoImageProcessor
|
||||||
|
|
||||||
|
try:
|
||||||
|
if not is_torch_available():
|
||||||
|
raise OptionalDependencyNotAvailable()
|
||||||
|
except OptionalDependencyNotAvailable:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
from .modeling_llava_next_video import (
|
||||||
|
LlavaNextVideoForConditionalGeneration,
|
||||||
|
LlavaNextVideoPreTrainedModel,
|
||||||
|
)
|
||||||
|
|
||||||
|
else:
|
||||||
|
import sys
|
||||||
|
|
||||||
|
sys.modules[__name__] = _LazyModule(__name__, globals()["__file__"], _import_structure)
|
||||||
@@ -0,0 +1,153 @@
|
|||||||
|
# 🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨
|
||||||
|
# This file was automatically generated from <path_to_diff_file.py>.
|
||||||
|
# Do NOT edit this file manually as any edits will be overwritten by the generation of
|
||||||
|
# the file from the diff. If any change should be done, please apply the change to the
|
||||||
|
# diff.py file directly.
|
||||||
|
# 🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨
|
||||||
|
# coding=utf-8
|
||||||
|
# Copyright 2024 HuggingFace Inc. team. All rights reserved.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
|
||||||
|
from transformers import PretrainedConfig
|
||||||
|
|
||||||
|
from ..auto import CONFIG_MAPPING
|
||||||
|
|
||||||
|
|
||||||
|
class LlavaNextVideoConfig(PretrainedConfig):
|
||||||
|
r"""
|
||||||
|
This is the configuration class to store the configuration of a [`LlavaNextVideoForConditionalGeneration`]. It is used to instantiate an
|
||||||
|
Llava-NeXT model according to the specified arguments, defining the model architecture. Instantiating a configuration
|
||||||
|
with the defaults will yield a similar configuration to that of the [llava-hf/LLaVA-NeXT-Video-7B-hf](https://huggingface.co/llava-hf/LLaVA-NeXT-Video-7B-hf)
|
||||||
|
model.
|
||||||
|
Configuration objects inherit from [`PretrainedConfig`] and can be used to control the model outputs. Read the
|
||||||
|
documentation from [`PretrainedConfig`] for more information.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
vision_config (`Union[AutoConfig, dict]`, *optional*, defaults to `CLIPVisionConfig`):
|
||||||
|
The config object or dictionary of the vision backbone.
|
||||||
|
text_config (`Union[AutoConfig, dict]`, *optional*, defaults to `LlamaConfig`):
|
||||||
|
The config object or dictionary of the text backbone.
|
||||||
|
ignore_index (`int`, *optional*, defaults to -100):
|
||||||
|
The ignore index for the loss function.
|
||||||
|
image_token_index (`int`, *optional*, defaults to 32001):
|
||||||
|
The image token index to encode the image prompt.
|
||||||
|
projector_hidden_act (`str`, *optional*, defaults to `"gelu"`):
|
||||||
|
The activation function used by the multimodal projector.
|
||||||
|
vision_feature_select_strategy (`str`, *optional*, defaults to `"default"`):
|
||||||
|
The feature selection strategy used to select the vision feature from the vision backbone.
|
||||||
|
Can be one of `"default"` or `"full"`. If `"default"`, the CLS token is removed from the vision features.
|
||||||
|
If `"full"`, the full vision features are used.
|
||||||
|
vision_feature_layer (`int`, *optional*, defaults to -2):
|
||||||
|
The index of the layer to select the vision feature.
|
||||||
|
image_grid_pinpoints (`List`, *optional*, defaults to `[[336, 672], [672, 336], [672, 672], [1008, 336], [336, 1008]]`):
|
||||||
|
A list of possible resolutions to use for processing high resolution images. Each item in the list should be a tuple or list
|
||||||
|
of the form `(height, width)`.
|
||||||
|
tie_word_embeddings (`bool`, *optional*, defaults to `False`):
|
||||||
|
Whether the model's input and output word embeddings should be tied.
|
||||||
|
video_token_index (`int`, *optional*, defaults to 32000):
|
||||||
|
The video token index to encode the image prompt.
|
||||||
|
spatial_pool_mode (`str`, *optional*, defaults to `"average"`):
|
||||||
|
Pooling mode to use for videos. Can be "average", "max" or "conv".
|
||||||
|
spatial_pool_stride (`int`, *optional*, defaults to 2):
|
||||||
|
Stride used in the pooling layer for videos.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```python
|
||||||
|
>>> from transformers import LlavaNextVideoForConditionalGeneration, LlavaNextVideoConfig, CLIPVisionConfig, LlamaConfig
|
||||||
|
|
||||||
|
>>> # Initializing a CLIP-vision config
|
||||||
|
>>> vision_config = CLIPVisionConfig()
|
||||||
|
|
||||||
|
>>> # Initializing a Llama config
|
||||||
|
>>> text_config = LlamaConfig()
|
||||||
|
|
||||||
|
>>> configuration = LlavaNextVideoConfig(vision_config, text_config)
|
||||||
|
|
||||||
|
>>> model = LlavaNextVideoForConditionalGeneration(configuration)
|
||||||
|
|
||||||
|
>>> # Accessing the model configuration
|
||||||
|
>>> configuration = model.config
|
||||||
|
```"""
|
||||||
|
|
||||||
|
model_type = "llava_next_video"
|
||||||
|
is_composition = True
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
vision_config=None,
|
||||||
|
text_config=None,
|
||||||
|
ignore_index=-100,
|
||||||
|
image_token_index=32001,
|
||||||
|
projector_hidden_act="gelu",
|
||||||
|
vision_feature_select_strategy="default",
|
||||||
|
vision_feature_layer=-2,
|
||||||
|
image_grid_pinpoints=None,
|
||||||
|
tie_word_embeddings=False,
|
||||||
|
video_token_index=32000,
|
||||||
|
spatial_pool_mode="average",
|
||||||
|
spatial_pool_stride=2,
|
||||||
|
**kwargs,
|
||||||
|
):
|
||||||
|
self.video_token_index = video_token_index
|
||||||
|
self.spatial_pool_mode = spatial_pool_mode
|
||||||
|
self.spatial_pool_stride = spatial_pool_stride
|
||||||
|
self.ignore_index = ignore_index
|
||||||
|
self.image_token_index = image_token_index
|
||||||
|
self.projector_hidden_act = projector_hidden_act
|
||||||
|
|
||||||
|
if vision_feature_select_strategy not in ["default", "full"]:
|
||||||
|
raise ValueError(
|
||||||
|
"vision_feature_select_strategy should be one of 'default', 'full'."
|
||||||
|
f"Got: {vision_feature_select_strategy}"
|
||||||
|
)
|
||||||
|
|
||||||
|
self.vision_feature_select_strategy = vision_feature_select_strategy
|
||||||
|
self.vision_feature_layer = vision_feature_layer
|
||||||
|
image_grid_pinpoints = (
|
||||||
|
image_grid_pinpoints
|
||||||
|
if image_grid_pinpoints is not None
|
||||||
|
else [[336, 672], [672, 336], [672, 672], [1008, 336], [336, 1008]]
|
||||||
|
)
|
||||||
|
self.image_grid_pinpoints = image_grid_pinpoints
|
||||||
|
|
||||||
|
if isinstance(vision_config, dict):
|
||||||
|
vision_config["model_type"] = (
|
||||||
|
vision_config["model_type"] if "model_type" in vision_config else "clip_vision_model"
|
||||||
|
)
|
||||||
|
vision_config = CONFIG_MAPPING[vision_config["model_type"]](**vision_config)
|
||||||
|
elif vision_config is None:
|
||||||
|
vision_config = CONFIG_MAPPING["clip_vision_model"](
|
||||||
|
intermediate_size=4096,
|
||||||
|
hidden_size=1024,
|
||||||
|
patch_size=14,
|
||||||
|
image_size=336,
|
||||||
|
num_hidden_layers=24,
|
||||||
|
num_attention_heads=16,
|
||||||
|
vocab_size=32000,
|
||||||
|
projection_dim=768,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.vision_config = vision_config
|
||||||
|
|
||||||
|
if isinstance(text_config, dict):
|
||||||
|
text_config["model_type"] = text_config["model_type"] if "model_type" in text_config else "llama"
|
||||||
|
text_config = CONFIG_MAPPING[text_config["model_type"]](**text_config)
|
||||||
|
elif text_config is None:
|
||||||
|
text_config = CONFIG_MAPPING["llama"]()
|
||||||
|
|
||||||
|
self.text_config = text_config
|
||||||
|
|
||||||
|
super().__init__(tie_word_embeddings=tie_word_embeddings, **kwargs)
|
||||||
@@ -0,0 +1,276 @@
|
|||||||
|
# Copyright 2024 The HuggingFace Inc. team. All rights reserved.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
"""Convert LLaVa-NeXT-Video checkpoints from the original repository.
|
||||||
|
|
||||||
|
URL: https://github.com/LLaVA-VL/LLaVA-NeXT/tree/inference
|
||||||
|
"""
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import glob
|
||||||
|
import json
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import torch
|
||||||
|
from accelerate import init_empty_weights
|
||||||
|
from huggingface_hub import hf_hub_download, snapshot_download
|
||||||
|
from safetensors import safe_open
|
||||||
|
|
||||||
|
from transformers import (
|
||||||
|
AddedToken,
|
||||||
|
AutoConfig,
|
||||||
|
AutoTokenizer,
|
||||||
|
LlavaNextImageProcessor,
|
||||||
|
LlavaNextVideoConfig,
|
||||||
|
LlavaNextVideoForConditionalGeneration,
|
||||||
|
LlavaNextVideoImageProcessor,
|
||||||
|
LlavaNextVideoProcessor,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
KEYS_TO_MODIFY_MAPPING = {
|
||||||
|
"model.vision_tower.": "",
|
||||||
|
".vision_resampler": "", # all lmms-lab models do avg pooling, so no vision_resampler
|
||||||
|
"model.mm_projector": "multi_modal_projector",
|
||||||
|
"model": "model.model",
|
||||||
|
"vision_model.model": "vision_model",
|
||||||
|
"lm_head": "language_model.lm_head",
|
||||||
|
"model.model": "language_model.model",
|
||||||
|
"multi_modal_projector.0": "multi_modal_projector.linear_1",
|
||||||
|
"multi_modal_projector.2": "multi_modal_projector.linear_2",
|
||||||
|
"language_model.model.image_newline": "image_newline",
|
||||||
|
}
|
||||||
|
|
||||||
|
# {{SYSTEM_PROMPT}} USER: <image>\n{{PROMPT}} ASSISTANT:" assistant end with "</s> "
|
||||||
|
chat_vicuna = (
|
||||||
|
"{% for message in messages %}"
|
||||||
|
"{% if message['role'] == 'system' %}"
|
||||||
|
"{{ message['content'][0]['text'] }}"
|
||||||
|
"{% else %}"
|
||||||
|
"{{ message['role'].upper() + ': '}}"
|
||||||
|
"{% endif %}"
|
||||||
|
"{# Render all images first #}"
|
||||||
|
"{% for content in message['content'] | selectattr('type', 'equalto', 'image') %}"
|
||||||
|
"{{ '<image>\n' }}"
|
||||||
|
"{% endfor %}"
|
||||||
|
"{# Render all text next #}"
|
||||||
|
"{% for content in message['content'] | selectattr('type', 'equalto', 'text') %}"
|
||||||
|
"{{ content['text'] + ' '}}"
|
||||||
|
"{% endfor %}"
|
||||||
|
"{% endfor %}"
|
||||||
|
"{% if add_generation_prompt %}"
|
||||||
|
"{{ 'ASSISTANT:' }}"
|
||||||
|
"{% endif %}"
|
||||||
|
)
|
||||||
|
|
||||||
|
# "[INST] <image>\nWhat is shown in this image? [/INST]" assistant end with "</s> "
|
||||||
|
chat_mistral = (
|
||||||
|
"{% for message in messages %}"
|
||||||
|
"{% if message['role'] == 'user' %}"
|
||||||
|
"{{ '[INST] ' }}"
|
||||||
|
"{# Render all images first #}"
|
||||||
|
"{% for content in message['content'] | selectattr('type', 'equalto', 'image') %}"
|
||||||
|
"{{ '<image>\n' }}"
|
||||||
|
"{% endfor %}"
|
||||||
|
"{# Render all text next #}"
|
||||||
|
"{% for content in message['content'] | selectattr('type', 'equalto', 'text') %}"
|
||||||
|
"{{ content['text'] }}"
|
||||||
|
"{% endfor %}"
|
||||||
|
"{{' [/INST]' }}"
|
||||||
|
"{% elif message['role'] == 'assistant' %}"
|
||||||
|
r"{{ ' ' + message['content'][0]['text'] + '<\s> '}}"
|
||||||
|
"{% else %}"
|
||||||
|
"{{ raise_exception('Only user and assistant roles are supported!') }}"
|
||||||
|
"{% endif %}"
|
||||||
|
"{% endfor %}"
|
||||||
|
)
|
||||||
|
|
||||||
|
# "<|im_start|>system\nAnswer the questions.<|im_end|><|im_start|>user\n<image>\nWhat is shown in this image?<|im_end|><|im_start|>assistant\n"
|
||||||
|
chat_yi = (
|
||||||
|
"{% for message in messages %}"
|
||||||
|
"{{'<|im_start|>' + message['role'] + '\n'}}"
|
||||||
|
"{# Render all images first #}"
|
||||||
|
"{% for content in message['content'] | selectattr('type', 'equalto', 'image') %}"
|
||||||
|
"{{ '<image>\n' }}"
|
||||||
|
"{% endfor %}"
|
||||||
|
"{# Render all text next #}"
|
||||||
|
"{% for content in message['content'] | selectattr('type', 'equalto', 'text') %}"
|
||||||
|
"{{ content['text'] }}"
|
||||||
|
"{% endfor %}"
|
||||||
|
"{{'<|im_end|>' + '\n'}}"
|
||||||
|
"{% endfor %}"
|
||||||
|
"{% if add_generation_prompt %}"
|
||||||
|
"{{ '<|im_start|>assistant\n' }}"
|
||||||
|
"{% endif %}"
|
||||||
|
)
|
||||||
|
|
||||||
|
model2template = {
|
||||||
|
"lmms-lab/LLaVA-NeXT-Video-7B-32K": chat_mistral,
|
||||||
|
"lmms-lab/LLaVA-NeXT-Video-7B": chat_vicuna,
|
||||||
|
"lmms-lab/LLaVA-NeXT-Video-7B-DPO": chat_vicuna,
|
||||||
|
"lmms-lab/LLaVA-NeXT-Video-34B": chat_yi,
|
||||||
|
"lmms-lab/LLaVA-NeXT-Video-34B-DPO": chat_yi,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def load_original_state_dict(model_id):
|
||||||
|
directory_path = snapshot_download(repo_id=model_id, allow_patterns=["*.safetensors"])
|
||||||
|
|
||||||
|
original_state_dict = {}
|
||||||
|
for path in glob.glob(f"{directory_path}/*"):
|
||||||
|
if path.endswith(".safetensors"):
|
||||||
|
with safe_open(path, framework="pt", device="cpu") as f:
|
||||||
|
for key in f.keys():
|
||||||
|
original_state_dict[key] = f.get_tensor(key)
|
||||||
|
|
||||||
|
return original_state_dict
|
||||||
|
|
||||||
|
|
||||||
|
def convert_state_dict_to_hf(state_dict):
|
||||||
|
new_state_dict = {}
|
||||||
|
for key, value in state_dict.items():
|
||||||
|
if key.endswith(".inv_freq"):
|
||||||
|
continue
|
||||||
|
for key_to_modify, new_key in KEYS_TO_MODIFY_MAPPING.items():
|
||||||
|
if key_to_modify in key:
|
||||||
|
key = key.replace(key_to_modify, new_key)
|
||||||
|
|
||||||
|
new_state_dict[key] = value.to(torch.bfloat16)
|
||||||
|
return new_state_dict
|
||||||
|
|
||||||
|
|
||||||
|
def convert_llava_to_hf(model_id, pytorch_dump_folder_path, push_to_hub=False):
|
||||||
|
# load original config
|
||||||
|
filepath = hf_hub_download(repo_id=model_id, filename="config.json", repo_type="model")
|
||||||
|
with open(filepath) as f:
|
||||||
|
data = json.load(f)
|
||||||
|
print(data)
|
||||||
|
|
||||||
|
if model_id == "lmms-lab/LLaVA-NeXT-Video-7B-32K":
|
||||||
|
text_model_id = "mistralai/Mistral-7B-Instruct-v0.2"
|
||||||
|
video_token_index = 32000
|
||||||
|
image_token_index = 32001
|
||||||
|
overwrite_text_config = {}
|
||||||
|
elif model_id in ["lmms-lab/LLaVA-NeXT-Video-7B", "lmms-lab/LLaVA-NeXT-Video-7B-DPO"]:
|
||||||
|
text_model_id = "lmsys/vicuna-7b-v1.5"
|
||||||
|
video_token_index = 32000
|
||||||
|
image_token_index = 32001
|
||||||
|
overwrite_text_config = {"factor": 2.0, "type": "linear"}
|
||||||
|
elif model_id in ["lmms-lab/LLaVA-NeXT-Video-34B", "lmms-lab/LLaVA-NeXT-Video-34B-DPO"]:
|
||||||
|
text_model_id = "NousResearch/Nous-Hermes-2-Yi-34B"
|
||||||
|
video_token_index = 64000
|
||||||
|
image_token_index = 64001
|
||||||
|
overwrite_text_config = {}
|
||||||
|
else:
|
||||||
|
raise ValueError("Incorrect checkpoint referenced. Text model-id not identified!")
|
||||||
|
|
||||||
|
vision_model_id = data["mm_vision_tower"]
|
||||||
|
|
||||||
|
torch.set_default_dtype(torch.bfloat16)
|
||||||
|
text_config = AutoConfig.from_pretrained(text_model_id)
|
||||||
|
text_config = text_config.to_dict()
|
||||||
|
text_config.update(overwrite_text_config)
|
||||||
|
|
||||||
|
tokenizer = AutoTokenizer.from_pretrained(text_model_id, use_fast=True, padding_side="left")
|
||||||
|
tokenizer.add_tokens(AddedToken("<video>", special=True, normalized=False), special_tokens=True)
|
||||||
|
tokenizer.add_tokens(AddedToken("<image>", special=True, normalized=False), special_tokens=True)
|
||||||
|
|
||||||
|
image_processor = LlavaNextImageProcessor.from_pretrained(vision_model_id)
|
||||||
|
video_processor = LlavaNextVideoImageProcessor.from_pretrained(vision_model_id)
|
||||||
|
processor = LlavaNextVideoProcessor(
|
||||||
|
tokenizer=tokenizer,
|
||||||
|
video_processor=video_processor,
|
||||||
|
image_processor=image_processor,
|
||||||
|
chat_template=model2template[model_id],
|
||||||
|
)
|
||||||
|
|
||||||
|
config = LlavaNextVideoConfig(
|
||||||
|
text_config=text_config,
|
||||||
|
image_grid_pinpoints=image_processor.image_grid_pinpoints,
|
||||||
|
use_image_newline_parameter=True,
|
||||||
|
video_token_index=video_token_index,
|
||||||
|
image_token_index=image_token_index,
|
||||||
|
)
|
||||||
|
|
||||||
|
with init_empty_weights():
|
||||||
|
model = LlavaNextVideoForConditionalGeneration(config)
|
||||||
|
|
||||||
|
# load original state dict
|
||||||
|
state_dict = load_original_state_dict(model_id)
|
||||||
|
state_dict = convert_state_dict_to_hf(state_dict)
|
||||||
|
model.load_state_dict(state_dict, assign=True, strict=True)
|
||||||
|
|
||||||
|
# See https://nlp.stanford.edu/~johnhew/vocab-expansion.html for why we get mean/stdev this way to expand embeddings
|
||||||
|
pre_expansion_embeddings = model.language_model.model.embed_tokens.weight.data
|
||||||
|
mu = torch.mean(pre_expansion_embeddings, dim=0).float()
|
||||||
|
n = pre_expansion_embeddings.size()[0]
|
||||||
|
sigma = ((pre_expansion_embeddings - mu).T @ (pre_expansion_embeddings - mu)) / n
|
||||||
|
dist = torch.distributions.multivariate_normal.MultivariateNormal(mu, covariance_matrix=1e-5 * sigma)
|
||||||
|
|
||||||
|
# We add an image token so we resize the model
|
||||||
|
# Pad to 64 for performance reasons
|
||||||
|
pad_shape = 64
|
||||||
|
vocab_size = config.text_config.vocab_size
|
||||||
|
|
||||||
|
# this one has 2 additional tokens, namely <image>, <video> and <pad>
|
||||||
|
num_tokens = vocab_size + 3
|
||||||
|
model.resize_token_embeddings(num_tokens, pad_to_multiple_of=pad_shape)
|
||||||
|
model.language_model.model.embed_tokens.weight.data[vocab_size:] = torch.stack(
|
||||||
|
tuple(
|
||||||
|
(dist.sample() for _ in range(model.language_model.model.embed_tokens.weight.data[vocab_size:].shape[0]))
|
||||||
|
),
|
||||||
|
dim=0,
|
||||||
|
)
|
||||||
|
model.language_model.lm_head.weight.data[vocab_size:] = torch.stack(
|
||||||
|
tuple((dist.sample() for _ in range(model.language_model.lm_head.weight.data[vocab_size:].shape[0]))),
|
||||||
|
dim=0,
|
||||||
|
)
|
||||||
|
|
||||||
|
if pytorch_dump_folder_path is not None:
|
||||||
|
print(f"Saving model and processor for {model_id} to {pytorch_dump_folder_path}")
|
||||||
|
Path(pytorch_dump_folder_path).mkdir(exist_ok=True)
|
||||||
|
model.save_pretrained(pytorch_dump_folder_path)
|
||||||
|
processor.save_pretrained(pytorch_dump_folder_path)
|
||||||
|
|
||||||
|
if push_to_hub:
|
||||||
|
repo_id = model_id.split("/")[-1]
|
||||||
|
print(f"Pushing model to hub repo: {repo_id}")
|
||||||
|
model.push_to_hub(f"llava-hf/{repo_id}-hf")
|
||||||
|
processor.push_to_hub(f"llava-hf/{repo_id}-hf")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
parser.add_argument(
|
||||||
|
"--model_id",
|
||||||
|
help="Hub location of the model to convert",
|
||||||
|
default="lmms-lab/LLaVA-NeXT-Video-7B",
|
||||||
|
choices=[
|
||||||
|
"lmms-lab/LLaVA-NeXT-Video-7B",
|
||||||
|
"lmms-lab/LLaVA-NeXT-Video-7B-DPO",
|
||||||
|
"lmms-lab/LLaVA-NeXT-Video-7B-32K",
|
||||||
|
"lmms-lab/LLaVA-NeXT-Video-34B",
|
||||||
|
"lmms-lab/LLaVA-NeXT-Video-34B-DPO",
|
||||||
|
],
|
||||||
|
required=False,
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--pytorch_dump_folder_path", default=None, type=str, help="Path to the output PyTorch model directory."
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--push_to_hub", action="store_true", help="Whether or not to push the converted model to the 🤗 hub."
|
||||||
|
)
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
convert_llava_to_hf(args.model_id, args.pytorch_dump_folder_path, args.push_to_hub)
|
||||||
@@ -0,0 +1,559 @@
|
|||||||
|
# coding=utf-8
|
||||||
|
# Copyright 2024 HuggingFace Inc. team. All rights reserved.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
import math
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from typing import List, Optional, Tuple, Union
|
||||||
|
|
||||||
|
import torch
|
||||||
|
import torch.utils.checkpoint
|
||||||
|
from torch import nn
|
||||||
|
|
||||||
|
from transformers import PretrainedConfig
|
||||||
|
from transformers.models.llava_next.modeling_llava_next import (
|
||||||
|
LlavaNextCausalLMOutputWithPast,
|
||||||
|
LlavaNextForConditionalGeneration,
|
||||||
|
LlavaNextMultiModalProjector,
|
||||||
|
image_size_to_num_patches,
|
||||||
|
)
|
||||||
|
|
||||||
|
from ...cache_utils import Cache
|
||||||
|
from ...utils import (
|
||||||
|
logging,
|
||||||
|
replace_return_docstrings,
|
||||||
|
)
|
||||||
|
from ..auto import CONFIG_MAPPING
|
||||||
|
|
||||||
|
|
||||||
|
logger = logging.get_logger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class LlavaNextVideoConfig(PretrainedConfig):
|
||||||
|
r"""
|
||||||
|
This is the configuration class to store the configuration of a [`LlavaNextVideoForConditionalGeneration`]. It is used to instantiate an
|
||||||
|
Llava-NeXT model according to the specified arguments, defining the model architecture. Instantiating a configuration
|
||||||
|
with the defaults will yield a similar configuration to that of the [llava-hf/LLaVA-NeXT-Video-7B-hf](https://huggingface.co/llava-hf/LLaVA-NeXT-Video-7B-hf)
|
||||||
|
model.
|
||||||
|
Configuration objects inherit from [`PretrainedConfig`] and can be used to control the model outputs. Read the
|
||||||
|
documentation from [`PretrainedConfig`] for more information.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
vision_config (`Union[AutoConfig, dict]`, *optional*, defaults to `CLIPVisionConfig`):
|
||||||
|
The config object or dictionary of the vision backbone.
|
||||||
|
text_config (`Union[AutoConfig, dict]`, *optional*, defaults to `LlamaConfig`):
|
||||||
|
The config object or dictionary of the text backbone.
|
||||||
|
ignore_index (`int`, *optional*, defaults to -100):
|
||||||
|
The ignore index for the loss function.
|
||||||
|
video_token_index (`int`, *optional*, defaults to 32000):
|
||||||
|
The video token index to encode the image prompt.
|
||||||
|
image_token_index (`int`, *optional*, defaults to 32001):
|
||||||
|
The image token index to encode the image prompt.
|
||||||
|
spatial_pool_mode (`str`, *optional*, defaults to `"average"`):
|
||||||
|
Pooling mode to use for videos. Can be "average", "max" or "conv".
|
||||||
|
spatial_pool_stride (`int`, *optional*, defaults to 2):
|
||||||
|
Stride used in the pooling layer for videos.
|
||||||
|
projector_hidden_act (`str`, *optional*, defaults to `"gelu"`):
|
||||||
|
The activation function used by the multimodal projector.
|
||||||
|
vision_feature_select_strategy (`str`, *optional*, defaults to `"default"`):
|
||||||
|
The feature selection strategy used to select the vision feature from the vision backbone.
|
||||||
|
Can be one of `"default"` or `"full"`. If `"default"`, the CLS token is removed from the vision features.
|
||||||
|
If `"full"`, the full vision features are used.
|
||||||
|
vision_feature_layer (`int`, *optional*, defaults to -2):
|
||||||
|
The index of the layer to select the vision feature.
|
||||||
|
image_grid_pinpoints (`List`, *optional*, defaults to `[[336, 672], [672, 336], [672, 672], [1008, 336], [336, 1008]]`):
|
||||||
|
A list of possible resolutions to use for processing high resolution images. Each item in the list should be a tuple or list
|
||||||
|
of the form `(height, width)`.
|
||||||
|
tie_word_embeddings (`bool`, *optional*, defaults to `False`):
|
||||||
|
Whether the model's input and output word embeddings should be tied.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```python
|
||||||
|
>>> from transformers import LlavaNextVideoForConditionalGeneration, LlavaNextVideoConfig, CLIPVisionConfig, LlamaConfig
|
||||||
|
|
||||||
|
>>> # Initializing a CLIP-vision config
|
||||||
|
>>> vision_config = CLIPVisionConfig()
|
||||||
|
|
||||||
|
>>> # Initializing a Llama config
|
||||||
|
>>> text_config = LlamaConfig()
|
||||||
|
|
||||||
|
>>> configuration = LlavaNextVideoConfig(vision_config, text_config)
|
||||||
|
|
||||||
|
>>> model = LlavaNextVideoForConditionalGeneration(configuration)
|
||||||
|
|
||||||
|
>>> # Accessing the model configuration
|
||||||
|
>>> configuration = model.config
|
||||||
|
```"""
|
||||||
|
|
||||||
|
model_type = "llava_next_video"
|
||||||
|
is_composition = True
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
vision_config=None,
|
||||||
|
text_config=None,
|
||||||
|
ignore_index=-100,
|
||||||
|
image_token_index=32001,
|
||||||
|
projector_hidden_act="gelu",
|
||||||
|
vision_feature_select_strategy="default",
|
||||||
|
vision_feature_layer=-2,
|
||||||
|
image_grid_pinpoints=None,
|
||||||
|
tie_word_embeddings=False,
|
||||||
|
video_token_index=32000,
|
||||||
|
spatial_pool_mode="average",
|
||||||
|
spatial_pool_stride=2,
|
||||||
|
**kwargs,
|
||||||
|
):
|
||||||
|
self.video_token_index = video_token_index
|
||||||
|
self.spatial_pool_mode = spatial_pool_mode
|
||||||
|
self.spatial_pool_stride = spatial_pool_stride
|
||||||
|
self.ignore_index = ignore_index
|
||||||
|
self.image_token_index = image_token_index
|
||||||
|
self.projector_hidden_act = projector_hidden_act
|
||||||
|
|
||||||
|
if vision_feature_select_strategy not in ["default", "full"]:
|
||||||
|
raise ValueError(
|
||||||
|
"vision_feature_select_strategy should be one of 'default', 'full'."
|
||||||
|
f"Got: {vision_feature_select_strategy}"
|
||||||
|
)
|
||||||
|
|
||||||
|
self.vision_feature_select_strategy = vision_feature_select_strategy
|
||||||
|
self.vision_feature_layer = vision_feature_layer
|
||||||
|
image_grid_pinpoints = (
|
||||||
|
image_grid_pinpoints
|
||||||
|
if image_grid_pinpoints is not None
|
||||||
|
else [[336, 672], [672, 336], [672, 672], [1008, 336], [336, 1008]]
|
||||||
|
)
|
||||||
|
self.image_grid_pinpoints = image_grid_pinpoints
|
||||||
|
|
||||||
|
if isinstance(vision_config, dict):
|
||||||
|
vision_config["model_type"] = (
|
||||||
|
vision_config["model_type"] if "model_type" in vision_config else "clip_vision_model"
|
||||||
|
)
|
||||||
|
vision_config = CONFIG_MAPPING[vision_config["model_type"]](**vision_config)
|
||||||
|
elif vision_config is None:
|
||||||
|
vision_config = CONFIG_MAPPING["clip_vision_model"](
|
||||||
|
intermediate_size=4096,
|
||||||
|
hidden_size=1024,
|
||||||
|
patch_size=14,
|
||||||
|
image_size=336,
|
||||||
|
num_hidden_layers=24,
|
||||||
|
num_attention_heads=16,
|
||||||
|
vocab_size=32000,
|
||||||
|
projection_dim=768,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.vision_config = vision_config
|
||||||
|
|
||||||
|
if isinstance(text_config, dict):
|
||||||
|
text_config["model_type"] = text_config["model_type"] if "model_type" in text_config else "llama"
|
||||||
|
text_config = CONFIG_MAPPING[text_config["model_type"]](**text_config)
|
||||||
|
elif text_config is None:
|
||||||
|
text_config = CONFIG_MAPPING["llama"]()
|
||||||
|
|
||||||
|
self.text_config = text_config
|
||||||
|
|
||||||
|
super().__init__(tie_word_embeddings=tie_word_embeddings, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class LlavaNextVideoCausalLMOutputWithPast(LlavaNextCausalLMOutputWithPast):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class LlavaNextVideoPooler(nn.Module):
|
||||||
|
def __init__(self, config):
|
||||||
|
super().__init__()
|
||||||
|
|
||||||
|
mode = config.spatial_pool_mode
|
||||||
|
stride = config.spatial_pool_stride
|
||||||
|
out_channels = getattr(config, "spatial_pool_out_channels", config.vision_config.hidden_size)
|
||||||
|
self.image_size = config.vision_config.image_size // config.vision_config.patch_size**2
|
||||||
|
|
||||||
|
if mode == "average":
|
||||||
|
self.pool = nn.AvgPool2d(kernel_size=stride, stride=stride)
|
||||||
|
elif mode == "max":
|
||||||
|
self.pool = nn.MaxPool2d(kernel_size=stride, stride=stride)
|
||||||
|
elif mode == "conv":
|
||||||
|
self.pool = nn.Conv2d(
|
||||||
|
in_channels=config.vision_config.hidden_size,
|
||||||
|
out_channels=out_channels,
|
||||||
|
kernel_size=stride,
|
||||||
|
stride=stride,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
raise ValueError(f"Unknown pooling mode: {mode}. Has to be one of [`average`, `max`, `conv`]")
|
||||||
|
|
||||||
|
def forward(self, image_features):
|
||||||
|
ori_width = int(math.sqrt(image_features.shape[1] * self.image_size // self.image_size))
|
||||||
|
ori_height = int(ori_width * self.image_size // self.image_size)
|
||||||
|
|
||||||
|
batch_size, _, dim = image_features.shape
|
||||||
|
image_features_spatial = image_features.view(batch_size, ori_height, ori_height, dim).permute(0, 3, 1, 2)
|
||||||
|
image_features_spatial_pool = self.pool(image_features_spatial)
|
||||||
|
|
||||||
|
return image_features_spatial_pool.flatten(2).transpose(1, 2).contiguous()
|
||||||
|
|
||||||
|
|
||||||
|
class LlavaNextVideoMultiModalProjector(LlavaNextMultiModalProjector):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class LlavaNextVideoForConditionalGeneration(LlavaNextForConditionalGeneration):
|
||||||
|
def __init__(self, config: LlavaNextVideoConfig, **super_kwargs):
|
||||||
|
super().__init__(config, **super_kwargs)
|
||||||
|
self.vision_resampler = LlavaNextVideoPooler(config)
|
||||||
|
self.post_init()
|
||||||
|
|
||||||
|
def _get_image_features(self, pixel_values, image_sizes):
|
||||||
|
# ! infer image_num_patches from image_sizes
|
||||||
|
image_num_patches = [
|
||||||
|
image_size_to_num_patches(
|
||||||
|
image_size=imsize,
|
||||||
|
grid_pinpoints=self.config.image_grid_pinpoints,
|
||||||
|
patch_size=self.config.vision_config.image_size,
|
||||||
|
)
|
||||||
|
for imsize in image_sizes
|
||||||
|
]
|
||||||
|
if pixel_values.dim() == 5:
|
||||||
|
# stacked if input is (batch_size, num_patches, num_channels, height, width)
|
||||||
|
_pixel_values_list = [pix_val[:num_patch] for pix_val, num_patch in zip(pixel_values, image_num_patches)]
|
||||||
|
pixel_values = torch.cat(_pixel_values_list, dim=0)
|
||||||
|
elif pixel_values.dim() != 4:
|
||||||
|
# otherwise has to be stacked from list of (num_patches, num_channels, height, width)
|
||||||
|
raise ValueError(f"pixel_values of shape {pixel_values.shape}, expect to be of 4 or 5 dimensions")
|
||||||
|
|
||||||
|
image_features = self.vision_tower(pixel_values, output_hidden_states=True)
|
||||||
|
selected_image_feature = image_features.hidden_states[self.vision_feature_layer]
|
||||||
|
if self.vision_feature_select_strategy == "default":
|
||||||
|
selected_image_feature = selected_image_feature[:, 1:]
|
||||||
|
elif self.vision_feature_select_strategy == "full":
|
||||||
|
selected_image_feature = selected_image_feature
|
||||||
|
image_features = self.multi_modal_projector(selected_image_feature)
|
||||||
|
image_features = torch.split(image_features, image_num_patches, dim=0)
|
||||||
|
return image_features
|
||||||
|
|
||||||
|
def _get_video_features(self, pixel_values):
|
||||||
|
batch_size, frames, channels, height, width = pixel_values.shape
|
||||||
|
pixel_values = pixel_values.reshape(batch_size * frames, channels, height, width)
|
||||||
|
image_features = self.vision_tower(pixel_values, output_hidden_states=True)
|
||||||
|
selected_image_feature = image_features.hidden_states[self.vision_feature_layer]
|
||||||
|
if self.vision_feature_select_strategy == "default":
|
||||||
|
selected_image_feature = selected_image_feature[:, 1:]
|
||||||
|
elif self.vision_feature_select_strategy == "full":
|
||||||
|
selected_image_feature = selected_image_feature
|
||||||
|
|
||||||
|
# Same as image features except that video has pooling layer
|
||||||
|
image_features = self.vision_resampler(selected_image_feature)
|
||||||
|
image_features = self.multi_modal_projector(image_features)
|
||||||
|
image_features = torch.split(image_features, frames, dim=0)
|
||||||
|
return image_features
|
||||||
|
|
||||||
|
@replace_return_docstrings(output_type=LlavaNextVideoCausalLMOutputWithPast, config_class="LlavaNextVideoConfig")
|
||||||
|
def forward(
|
||||||
|
self,
|
||||||
|
input_ids: torch.LongTensor = None,
|
||||||
|
pixel_values: torch.FloatTensor = None,
|
||||||
|
pixel_values_videos: torch.FloatTensor = None,
|
||||||
|
image_sizes: Optional[torch.LongTensor] = None,
|
||||||
|
attention_mask: Optional[torch.Tensor] = None,
|
||||||
|
position_ids: Optional[torch.LongTensor] = None,
|
||||||
|
past_key_values: Optional[List[torch.FloatTensor]] = None,
|
||||||
|
inputs_embeds: Optional[torch.FloatTensor] = None,
|
||||||
|
vision_feature_layer: Optional[int] = None,
|
||||||
|
vision_feature_select_strategy: Optional[str] = None,
|
||||||
|
labels: Optional[torch.LongTensor] = None,
|
||||||
|
use_cache: Optional[bool] = None,
|
||||||
|
output_attentions: Optional[bool] = None,
|
||||||
|
output_hidden_states: Optional[bool] = None,
|
||||||
|
return_dict: Optional[bool] = None,
|
||||||
|
) -> Union[Tuple, LlavaNextVideoCausalLMOutputWithPast]:
|
||||||
|
r"""
|
||||||
|
Args:
|
||||||
|
pixel_values_videos (`torch.FloatTensor` of shape `(batch_size, num_frames, num_channels, image_size, image_size)):
|
||||||
|
The tensors corresponding to the input videos. Pixel values can be obtained using
|
||||||
|
[`AutoImageProcessor`]. See [`LlavaNextVideoVideoProcessor.__call__`] for details. [`LlavaProcessor`] uses
|
||||||
|
[`LlavaNextVideoVideoProcessor`] for processing videos.
|
||||||
|
labels (`torch.LongTensor` of shape `(batch_size, sequence_length)`, *optional*):
|
||||||
|
Labels for computing the masked language modeling loss. Indices should either be in `[0, ...,
|
||||||
|
config.vocab_size]` or -100 (see `input_ids` docstring). Tokens with indices set to `-100` are ignored
|
||||||
|
(masked), the loss is only computed for the tokens with labels in `[0, ..., config.vocab_size]`.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```python
|
||||||
|
>>> from PIL import Image
|
||||||
|
>>> import requests
|
||||||
|
>>> import av
|
||||||
|
>>> from transformers import AutoProcessor, LlavaNextVideoForConditionalGeneration
|
||||||
|
|
||||||
|
>>> def read_video_pyav(container, indices):
|
||||||
|
... '''
|
||||||
|
... Decode the video with PyAV decoder.
|
||||||
|
... Args:
|
||||||
|
... container (`av.container.input.InputContainer`): PyAV container.
|
||||||
|
... indices (`List[int]`): List of frame indices to decode.
|
||||||
|
... Returns:
|
||||||
|
... result (np.ndarray): np array of decoded frames of shape (num_frames, height, width, 3).
|
||||||
|
... '''
|
||||||
|
... frames = []
|
||||||
|
... container.seek(0)
|
||||||
|
... start_index = indices[0]
|
||||||
|
... end_index = indices[-1]
|
||||||
|
... for i, frame in enumerate(container.decode(video=0)):
|
||||||
|
... if i > end_index:
|
||||||
|
... break
|
||||||
|
... if i >= start_index and i in indices:
|
||||||
|
... frames.append(frame)
|
||||||
|
... return np.stack([x.to_ndarray(format="rgb24") for x in frames])
|
||||||
|
|
||||||
|
>>> model = LlavaNextVideoForConditionalGeneration.from_pretrained("llava-hf/LLaVA-NeXT-Video-7B-hf", device_map="auto)
|
||||||
|
>>> processor = AutoProcessor.from_pretrained("llava-hf/LLaVA-NeXT-Video-7B-hf")
|
||||||
|
|
||||||
|
>>> prompt = "USER: <video>\nWhy is this video funny? ASSISTANT:"
|
||||||
|
>>> video_path = hf_hub_download(repo_id="raushan-testing-hf/videos-test", filename="sample_demo_1.mp4", repo_type="dataset")
|
||||||
|
>>> container = av.open(video_path)
|
||||||
|
|
||||||
|
>>> # sample uniformly 8 frames from the video (model was trained with 32 frames per video, but this video is short)
|
||||||
|
>>> total_frames = container.streams.video[0].frames
|
||||||
|
>>> indices = np.arange(0, total_frames, total_frames / 8).astype(int)
|
||||||
|
>>> clip = read_video_pyav(container, indices)
|
||||||
|
>>> inputs_video = processor(text=prompt, videos=clip, return_tensors="pt").to(model.device)
|
||||||
|
|
||||||
|
>>> # load an image to generate from an image
|
||||||
|
>>> prompt = "USER:<image>\nWhat is shown in this image? ASSISTANT:"
|
||||||
|
>>> url = "https://www.ilankelman.org/stopsigns/australia.jpg"
|
||||||
|
>>> image = Image.open(requests.get(url, stream=True).raw)
|
||||||
|
>>> inputs_image = processor(text=prompt, images=image, return_tensors="pt").to(model.device)
|
||||||
|
|
||||||
|
>>> # Generate from video
|
||||||
|
>>> generate_ids = model.generate(**inputs_video, max_length=50)
|
||||||
|
>>> processor.batch_decode(generate_ids, skip_special_tokens=True, clean_up_tokenization_spaces=False)[0]
|
||||||
|
"USER:\nWhy is this video funny? ASSISTANT: The humor in this video comes from the unexpected and endearing sight of a baby wearing glasses and (...)"
|
||||||
|
|
||||||
|
>>> # Generate from image
|
||||||
|
>>> generate_ids = model.generate(**inputs_image, max_length=30)
|
||||||
|
>>> processor.batch_decode(generate_ids, skip_special_tokens=True, clean_up_tokenization_spaces=False)[0]
|
||||||
|
"USER: \nWhat's the content of the image? ASSISTANT: The image shows a red stop sign on a pole, with a traditional Chinese archway (...)"
|
||||||
|
```"""
|
||||||
|
|
||||||
|
output_attentions = output_attentions if output_attentions is not None else self.config.output_attentions
|
||||||
|
output_hidden_states = (
|
||||||
|
output_hidden_states if output_hidden_states is not None else self.config.output_hidden_states
|
||||||
|
)
|
||||||
|
return_dict = return_dict if return_dict is not None else self.config.use_return_dict
|
||||||
|
self.vision_feature_layer = (
|
||||||
|
vision_feature_layer if vision_feature_layer is not None else self.config.vision_feature_layer
|
||||||
|
)
|
||||||
|
self.vision_feature_select_strategy = (
|
||||||
|
vision_feature_select_strategy
|
||||||
|
if vision_feature_select_strategy is not None
|
||||||
|
else self.config.vision_feature_select_strategy
|
||||||
|
)
|
||||||
|
|
||||||
|
if (input_ids is None) ^ (inputs_embeds is not None):
|
||||||
|
raise ValueError(
|
||||||
|
"You cannot specify both input_ids and inputs_embeds at the same time, and must specify either one"
|
||||||
|
)
|
||||||
|
|
||||||
|
if (pixel_values is not None or pixel_values_videos is not None) and inputs_embeds is not None:
|
||||||
|
raise ValueError(
|
||||||
|
"You cannot specify both pixel_values and inputs_embeds at the same time, and must specify either one"
|
||||||
|
)
|
||||||
|
|
||||||
|
if inputs_embeds is None:
|
||||||
|
inputs_embeds = self.get_input_embeddings()(input_ids)
|
||||||
|
|
||||||
|
# Merge text and images in prefill stage
|
||||||
|
if past_key_values is None:
|
||||||
|
# First merge image tokens if there are any
|
||||||
|
if pixel_values is not None and pixel_values.size(0) > 0:
|
||||||
|
image_features = self._get_image_features(pixel_values, image_sizes)
|
||||||
|
image_features, feature_lens = self.pack_image_features(
|
||||||
|
image_features,
|
||||||
|
image_sizes,
|
||||||
|
image_newline=self.image_newline,
|
||||||
|
)
|
||||||
|
inputs_embeds = inputs_embeds.to(image_features.dtype)
|
||||||
|
inputs_embeds, attention_mask, position_ids, labels, input_ids = (
|
||||||
|
self._merge_input_ids_with_image_features(
|
||||||
|
image_features,
|
||||||
|
feature_lens,
|
||||||
|
inputs_embeds,
|
||||||
|
input_ids,
|
||||||
|
attention_mask,
|
||||||
|
position_ids,
|
||||||
|
labels=labels,
|
||||||
|
image_token_index=self.config.image_token_index,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
# Then merge video tokens if there are any
|
||||||
|
if pixel_values_videos is not None and pixel_values_videos.size(0) > 0:
|
||||||
|
video_features = self._get_video_features(pixel_values_videos)
|
||||||
|
video_features = [feature.flatten(0, 1) for feature in video_features]
|
||||||
|
feature_lens = [feature.size(0) for feature in video_features]
|
||||||
|
video_features = torch.cat(video_features, dim=0)
|
||||||
|
feature_lens = torch.tensor(feature_lens, dtype=torch.long, device=video_features.device)
|
||||||
|
inputs_embeds, attention_mask, position_ids, labels, input_ids = (
|
||||||
|
self._merge_input_ids_with_image_features(
|
||||||
|
video_features,
|
||||||
|
feature_lens,
|
||||||
|
inputs_embeds,
|
||||||
|
input_ids,
|
||||||
|
attention_mask,
|
||||||
|
position_ids,
|
||||||
|
labels=labels,
|
||||||
|
image_token_index=self.config.video_token_index,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
# pixel_values is not None but is empty ---> text only cases
|
||||||
|
elif (pixel_values is not None and pixel_values.size(0) == 0) or (
|
||||||
|
pixel_values_videos is not None and pixel_values_videos.size(0) == 0
|
||||||
|
):
|
||||||
|
pass
|
||||||
|
|
||||||
|
# generation with cache, decoding stage
|
||||||
|
elif past_key_values is not None and (pixel_values is not None or pixel_values_videos is not None):
|
||||||
|
# Retrieve the first layer to inspect the logits and mask out the hidden states that are set to 0
|
||||||
|
first_layer_past_key_value = past_key_values[0][0][:, :, :, 0]
|
||||||
|
# Sum all dimensions of head_dim (-2) to avoid random errors such as: https://github.com/huggingface/transformers/pull/28032#issuecomment-1863691941
|
||||||
|
batch_index, non_attended_tokens = torch.where(first_layer_past_key_value.float().sum(-2) == 0)
|
||||||
|
# Get the target length
|
||||||
|
target_length = input_ids.shape[1]
|
||||||
|
past_length = first_layer_past_key_value.shape[-1]
|
||||||
|
extended_attention_mask = torch.ones(
|
||||||
|
(attention_mask.shape[0], past_length),
|
||||||
|
dtype=attention_mask.dtype,
|
||||||
|
device=attention_mask.device,
|
||||||
|
)
|
||||||
|
# Filter out only the tokens that can be un-attended, this can happen
|
||||||
|
# if one uses Llava + Fused modules where the cache on the
|
||||||
|
# first iteration is already big enough, or if one passes custom cache
|
||||||
|
valid_indices = non_attended_tokens < extended_attention_mask.size(-1)
|
||||||
|
new_batch_index = batch_index[valid_indices]
|
||||||
|
new_non_attended_tokens = non_attended_tokens[valid_indices]
|
||||||
|
# Zero-out the places where we don't need to attend
|
||||||
|
extended_attention_mask[new_batch_index, new_non_attended_tokens] = 0
|
||||||
|
attention_mask = torch.cat((extended_attention_mask, attention_mask[:, -target_length:]), dim=1)
|
||||||
|
position_ids = torch.sum(attention_mask, dim=1).unsqueeze(-1) - 1
|
||||||
|
|
||||||
|
outputs = self.language_model(
|
||||||
|
attention_mask=attention_mask,
|
||||||
|
position_ids=position_ids,
|
||||||
|
past_key_values=past_key_values,
|
||||||
|
inputs_embeds=inputs_embeds,
|
||||||
|
use_cache=use_cache,
|
||||||
|
output_attentions=output_attentions,
|
||||||
|
output_hidden_states=output_hidden_states,
|
||||||
|
return_dict=return_dict,
|
||||||
|
)
|
||||||
|
|
||||||
|
logits = outputs[0]
|
||||||
|
|
||||||
|
loss = None
|
||||||
|
if labels is not None:
|
||||||
|
# Shift so that tokens < n predict n
|
||||||
|
if attention_mask is not None:
|
||||||
|
shift_attention_mask = attention_mask[..., 1:]
|
||||||
|
shift_logits = logits[..., :-1, :][shift_attention_mask.to(logits.device) != 0].contiguous()
|
||||||
|
shift_labels = labels[..., 1:][shift_attention_mask.to(labels.device) != 0].contiguous()
|
||||||
|
else:
|
||||||
|
shift_logits = logits[..., :-1, :].contiguous()
|
||||||
|
shift_labels = labels[..., 1:].contiguous()
|
||||||
|
# Flatten the tokens
|
||||||
|
loss_fct = nn.CrossEntropyLoss()
|
||||||
|
loss = loss_fct(
|
||||||
|
shift_logits.view(-1, shift_logits.size(-1)), shift_labels.view(-1).to(shift_logits.device)
|
||||||
|
)
|
||||||
|
|
||||||
|
if not return_dict:
|
||||||
|
output = (logits,) + outputs[1:]
|
||||||
|
return (loss,) + output if loss is not None else output
|
||||||
|
|
||||||
|
return LlavaNextVideoCausalLMOutputWithPast(
|
||||||
|
loss=loss,
|
||||||
|
logits=logits,
|
||||||
|
past_key_values=outputs.past_key_values,
|
||||||
|
hidden_states=outputs.hidden_states,
|
||||||
|
attentions=outputs.attentions,
|
||||||
|
)
|
||||||
|
|
||||||
|
def prepare_inputs_for_generation(
|
||||||
|
self,
|
||||||
|
input_ids,
|
||||||
|
past_key_values=None,
|
||||||
|
inputs_embeds=None,
|
||||||
|
pixel_values=None,
|
||||||
|
pixel_values_videos=None,
|
||||||
|
image_sizes=None,
|
||||||
|
attention_mask=None,
|
||||||
|
**kwargs,
|
||||||
|
):
|
||||||
|
if past_key_values is not None:
|
||||||
|
if isinstance(past_key_values, Cache):
|
||||||
|
cache_length = past_key_values.get_seq_length()
|
||||||
|
past_length = past_key_values.seen_tokens
|
||||||
|
else:
|
||||||
|
cache_length = past_length = past_key_values[0][0].shape[2]
|
||||||
|
|
||||||
|
# Keep only the unprocessed tokens:
|
||||||
|
# 1 - If the length of the attention_mask exceeds the length of input_ids, then we are in a setting where
|
||||||
|
# some of the inputs are exclusively passed as part of the cache (e.g. when passing input_embeds as
|
||||||
|
# input)
|
||||||
|
if attention_mask is not None and attention_mask.shape[1] > input_ids.shape[1]:
|
||||||
|
input_ids = input_ids[:, -(attention_mask.shape[1] - past_length) :]
|
||||||
|
# 2 - If the past_length is smaller than input_ids', then input_ids holds all input tokens. We can discard
|
||||||
|
# input_ids based on the past_length.
|
||||||
|
elif past_length < input_ids.shape[1]:
|
||||||
|
input_ids = input_ids[:, past_length:]
|
||||||
|
|
||||||
|
# 3 - Otherwise (past_length >= input_ids.shape[1]), let's assume input_ids only has unprocessed tokens.
|
||||||
|
elif self.config.image_token_index in input_ids or self.config.video_token_index in input_ids:
|
||||||
|
input_ids = input_ids[:, input_ids.shape[1] - 1 :]
|
||||||
|
|
||||||
|
# If the cache has seen more tokens than it can hold, then the cache has a size limit. Let's discard the
|
||||||
|
# older attention values, as their corresponding values are not part of the input.
|
||||||
|
if cache_length < past_length and attention_mask is not None:
|
||||||
|
attention_mask = attention_mask[:, -(cache_length + input_ids.shape[1]) :]
|
||||||
|
|
||||||
|
position_ids = kwargs.get("position_ids", None)
|
||||||
|
if attention_mask is not None and position_ids is None:
|
||||||
|
# create position_ids on the fly for batch generation
|
||||||
|
position_ids = attention_mask.long().cumsum(-1) - 1
|
||||||
|
position_ids.masked_fill_(attention_mask == 0, 1)
|
||||||
|
if past_key_values:
|
||||||
|
position_ids = position_ids[:, -input_ids.shape[1] :]
|
||||||
|
|
||||||
|
# if `inputs_embeds` are passed, we only want to use them in the 1st generation step
|
||||||
|
if inputs_embeds is not None and past_key_values is None:
|
||||||
|
model_inputs = {"inputs_embeds": inputs_embeds}
|
||||||
|
else:
|
||||||
|
model_inputs = {"input_ids": input_ids}
|
||||||
|
|
||||||
|
model_inputs.update(
|
||||||
|
{
|
||||||
|
"position_ids": position_ids,
|
||||||
|
"past_key_values": past_key_values,
|
||||||
|
"use_cache": kwargs.get("use_cache"),
|
||||||
|
"attention_mask": attention_mask,
|
||||||
|
"pixel_values": pixel_values,
|
||||||
|
"pixel_values_videos": pixel_values_videos,
|
||||||
|
"image_sizes": image_sizes,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
return model_inputs
|
||||||
@@ -0,0 +1,421 @@
|
|||||||
|
# coding=utf-8
|
||||||
|
# Copyright 2024 The HuggingFace Inc. team. All rights reserved.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
"""Image processor class for LLaVa-NeXT-Video."""
|
||||||
|
|
||||||
|
from typing import Dict, List, Optional, Union
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
from ...image_processing_utils import BaseImageProcessor, BatchFeature, get_size_dict
|
||||||
|
from ...image_transforms import (
|
||||||
|
convert_to_rgb,
|
||||||
|
get_resize_output_image_size,
|
||||||
|
resize,
|
||||||
|
to_channel_dimension_format,
|
||||||
|
)
|
||||||
|
from ...image_utils import (
|
||||||
|
OPENAI_CLIP_MEAN,
|
||||||
|
OPENAI_CLIP_STD,
|
||||||
|
ChannelDimension,
|
||||||
|
ImageInput,
|
||||||
|
PILImageResampling,
|
||||||
|
VideoInput,
|
||||||
|
infer_channel_dimension_format,
|
||||||
|
is_scaled_image,
|
||||||
|
is_valid_image,
|
||||||
|
make_list_of_images,
|
||||||
|
to_numpy_array,
|
||||||
|
validate_preprocess_arguments,
|
||||||
|
)
|
||||||
|
from ...utils import TensorType, is_vision_available, logging
|
||||||
|
|
||||||
|
|
||||||
|
logger = logging.get_logger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
if is_vision_available():
|
||||||
|
from PIL import Image
|
||||||
|
|
||||||
|
|
||||||
|
def make_batched_videos(videos) -> List[VideoInput]:
|
||||||
|
if isinstance(videos, (list, tuple)) and isinstance(videos[0], (list, tuple)) and is_valid_image(videos[0][0]):
|
||||||
|
return videos
|
||||||
|
|
||||||
|
elif isinstance(videos, (list, tuple)) and is_valid_image(videos[0]):
|
||||||
|
if isinstance(videos[0], Image.Image):
|
||||||
|
return [videos]
|
||||||
|
elif len(videos[0].shape) == 4:
|
||||||
|
return [list(video) for video in videos]
|
||||||
|
|
||||||
|
elif is_valid_image(videos) and len(videos.shape) == 4:
|
||||||
|
return [list(videos)]
|
||||||
|
|
||||||
|
raise ValueError(f"Could not make batched video from {videos}")
|
||||||
|
|
||||||
|
|
||||||
|
class LlavaNextVideoImageProcessor(BaseImageProcessor):
|
||||||
|
r"""
|
||||||
|
Constructs a LLaVa-NeXT-Video video processor. Based on [`CLIPImageProcessor`] with incorporation of processing each video frame.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
do_resize (`bool`, *optional*, defaults to `True`):
|
||||||
|
Whether to resize the image's (height, width) dimensions to the specified `size`. Can be overridden by
|
||||||
|
`do_resize` in the `preprocess` method.
|
||||||
|
size (`Dict[str, int]` *optional*, defaults to `{"shortest_edge": 224}`):
|
||||||
|
Size of the image after resizing. The shortest edge of the image is resized to size["shortest_edge"], with
|
||||||
|
the longest edge resized to keep the input aspect ratio. Can be overridden by `size` in the `preprocess`
|
||||||
|
method.
|
||||||
|
image_grid_pinpoints (`List` *optional*, defaults to `[[672, 336], [336, 672], [672, 672], [336, 1008], [1008, 336]]`):
|
||||||
|
A list of possible resolutions to use for processing high resolution images. The best resolution is selected
|
||||||
|
based on the original size of the image. Can be overridden by `image_grid_pinpoints` in the `preprocess`
|
||||||
|
method. Not used for processinf videos.
|
||||||
|
resample (`PILImageResampling`, *optional*, defaults to `Resampling.BICUBIC`):
|
||||||
|
Resampling filter to use if resizing the image. Can be overridden by `resample` in the `preprocess` method.
|
||||||
|
do_center_crop (`bool`, *optional*, defaults to `True`):
|
||||||
|
Whether to center crop the image to the specified `crop_size`. Can be overridden by `do_center_crop` in the
|
||||||
|
`preprocess` method.
|
||||||
|
crop_size (`Dict[str, int]` *optional*, defaults to 224):
|
||||||
|
Size of the output image after applying `center_crop`. Can be overridden by `crop_size` in the `preprocess`
|
||||||
|
method.
|
||||||
|
do_rescale (`bool`, *optional*, defaults to `True`):
|
||||||
|
Whether to rescale the image by the specified scale `rescale_factor`. Can be overridden by `do_rescale` in
|
||||||
|
the `preprocess` method.
|
||||||
|
rescale_factor (`int` or `float`, *optional*, defaults to `1/255`):
|
||||||
|
Scale factor to use if rescaling the image. Can be overridden by `rescale_factor` in the `preprocess`
|
||||||
|
method.
|
||||||
|
do_normalize (`bool`, *optional*, defaults to `True`):
|
||||||
|
Whether to normalize the image. Can be overridden by `do_normalize` in the `preprocess` method.
|
||||||
|
image_mean (`float` or `List[float]`, *optional*, defaults to `[0.48145466, 0.4578275, 0.40821073]`):
|
||||||
|
Mean to use if normalizing the image. This is a float or list of floats the length of the number of
|
||||||
|
channels in the image. Can be overridden by the `image_mean` parameter in the `preprocess` method.
|
||||||
|
image_std (`float` or `List[float]`, *optional*, defaults to `[0.26862954, 0.26130258, 0.27577711]`):
|
||||||
|
Standard deviation to use if normalizing the image. This is a float or list of floats the length of the
|
||||||
|
number of channels in the image. Can be overridden by the `image_std` parameter in the `preprocess` method.
|
||||||
|
Can be overridden by the `image_std` parameter in the `preprocess` method.
|
||||||
|
do_convert_rgb (`bool`, *optional*, defaults to `True`):
|
||||||
|
Whether to convert the image to RGB.
|
||||||
|
"""
|
||||||
|
|
||||||
|
model_input_names = ["pixel_values_videos"]
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
do_resize: bool = True,
|
||||||
|
size: Dict[str, int] = None,
|
||||||
|
image_grid_pinpoints: List = None,
|
||||||
|
resample: PILImageResampling = PILImageResampling.BICUBIC,
|
||||||
|
do_center_crop: bool = True,
|
||||||
|
crop_size: Dict[str, int] = None,
|
||||||
|
do_rescale: bool = True,
|
||||||
|
rescale_factor: Union[int, float] = 1 / 255,
|
||||||
|
do_normalize: bool = True,
|
||||||
|
image_mean: Optional[Union[float, List[float]]] = None,
|
||||||
|
image_std: Optional[Union[float, List[float]]] = None,
|
||||||
|
do_convert_rgb: bool = True,
|
||||||
|
**kwargs,
|
||||||
|
) -> None:
|
||||||
|
super().__init__(**kwargs)
|
||||||
|
size = size if size is not None else {"shortest_edge": 224}
|
||||||
|
size = get_size_dict(size, default_to_square=False)
|
||||||
|
crop_size = crop_size if crop_size is not None else {"height": 224, "width": 224}
|
||||||
|
crop_size = get_size_dict(crop_size, default_to_square=True, param_name="crop_size")
|
||||||
|
|
||||||
|
self.do_resize = do_resize
|
||||||
|
self.size = size
|
||||||
|
self.image_grid_pinpoints = image_grid_pinpoints
|
||||||
|
self.resample = resample
|
||||||
|
self.do_center_crop = do_center_crop
|
||||||
|
self.crop_size = crop_size
|
||||||
|
self.do_rescale = do_rescale
|
||||||
|
self.rescale_factor = rescale_factor
|
||||||
|
self.do_normalize = do_normalize
|
||||||
|
self.image_mean = image_mean if image_mean is not None else OPENAI_CLIP_MEAN
|
||||||
|
self.image_std = image_std if image_std is not None else OPENAI_CLIP_STD
|
||||||
|
self.do_convert_rgb = do_convert_rgb
|
||||||
|
|
||||||
|
# Copied from transformers.models.clip.image_processing_clip.CLIPImageProcessor.resize with CLIP->LLaVa
|
||||||
|
def resize(
|
||||||
|
self,
|
||||||
|
image: np.ndarray,
|
||||||
|
size: Dict[str, int],
|
||||||
|
resample: PILImageResampling = PILImageResampling.BICUBIC,
|
||||||
|
data_format: Optional[Union[str, ChannelDimension]] = None,
|
||||||
|
input_data_format: Optional[Union[str, ChannelDimension]] = None,
|
||||||
|
**kwargs,
|
||||||
|
) -> np.ndarray:
|
||||||
|
"""
|
||||||
|
Resize an image. The shortest edge of the image is resized to size["shortest_edge"], with the longest edge
|
||||||
|
resized to keep the input aspect ratio.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
image (`np.ndarray`):
|
||||||
|
Image to resize.
|
||||||
|
size (`Dict[str, int]`):
|
||||||
|
Size of the output image.
|
||||||
|
resample (`PILImageResampling`, *optional*, defaults to `PILImageResampling.BICUBIC`):
|
||||||
|
Resampling filter to use when resiizing the image.
|
||||||
|
data_format (`str` or `ChannelDimension`, *optional*):
|
||||||
|
The channel dimension format of the image. If not provided, it will be the same as the input image.
|
||||||
|
input_data_format (`ChannelDimension` or `str`, *optional*):
|
||||||
|
The channel dimension format of the input image. If not provided, it will be inferred.
|
||||||
|
"""
|
||||||
|
default_to_square = True
|
||||||
|
if "shortest_edge" in size:
|
||||||
|
size = size["shortest_edge"]
|
||||||
|
default_to_square = False
|
||||||
|
elif "height" in size and "width" in size:
|
||||||
|
size = (size["height"], size["width"])
|
||||||
|
else:
|
||||||
|
raise ValueError("Size must contain either 'shortest_edge' or 'height' and 'width'.")
|
||||||
|
|
||||||
|
output_size = get_resize_output_image_size(
|
||||||
|
image,
|
||||||
|
size=size,
|
||||||
|
default_to_square=default_to_square,
|
||||||
|
input_data_format=input_data_format,
|
||||||
|
)
|
||||||
|
|
||||||
|
return resize(
|
||||||
|
image,
|
||||||
|
size=output_size,
|
||||||
|
resample=resample,
|
||||||
|
data_format=data_format,
|
||||||
|
input_data_format=input_data_format,
|
||||||
|
**kwargs,
|
||||||
|
)
|
||||||
|
|
||||||
|
def _preprocess(
|
||||||
|
self,
|
||||||
|
images: ImageInput,
|
||||||
|
do_resize: bool = None,
|
||||||
|
size: Dict[str, int] = None,
|
||||||
|
resample: PILImageResampling = None,
|
||||||
|
do_center_crop: bool = None,
|
||||||
|
crop_size: int = None,
|
||||||
|
do_rescale: bool = None,
|
||||||
|
rescale_factor: float = None,
|
||||||
|
do_normalize: bool = None,
|
||||||
|
image_mean: Optional[Union[float, List[float]]] = None,
|
||||||
|
image_std: Optional[Union[float, List[float]]] = None,
|
||||||
|
do_convert_rgb: bool = None,
|
||||||
|
data_format: Optional[ChannelDimension] = ChannelDimension.FIRST,
|
||||||
|
input_data_format: Optional[Union[str, ChannelDimension]] = None,
|
||||||
|
) -> Image.Image:
|
||||||
|
"""
|
||||||
|
Preprocess an image or batch of images. Copy of the `preprocess` method from `CLIPImageProcessor`.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
images (`ImageInput`):
|
||||||
|
Batch of frames (one video) to preprocess. Expects a batch of frames with pixel values ranging from 0 to 255. If
|
||||||
|
passing in images with pixel values between 0 and 1, set `do_rescale=False`.
|
||||||
|
do_resize (`bool`, *optional*, defaults to `self.do_resize`):
|
||||||
|
Whether to resize the image.
|
||||||
|
size (`Dict[str, int]`, *optional*, defaults to `self.size`):
|
||||||
|
Size of the image after resizing. Shortest edge of the image is resized to size["shortest_edge"], with
|
||||||
|
the longest edge resized to keep the input aspect ratio.
|
||||||
|
resample (`int`, *optional*, defaults to `self.resample`):
|
||||||
|
Resampling filter to use if resizing the image. This can be one of the enum `PILImageResampling`. Only
|
||||||
|
has an effect if `do_resize` is set to `True`.
|
||||||
|
do_center_crop (`bool`, *optional*, defaults to `self.do_center_crop`):
|
||||||
|
Whether to center crop the image.
|
||||||
|
crop_size (`Dict[str, int]`, *optional*, defaults to `self.crop_size`):
|
||||||
|
Size of the center crop. Only has an effect if `do_center_crop` is set to `True`.
|
||||||
|
do_rescale (`bool`, *optional*, defaults to `self.do_rescale`):
|
||||||
|
Whether to rescale the image.
|
||||||
|
rescale_factor (`float`, *optional*, defaults to `self.rescale_factor`):
|
||||||
|
Rescale factor to rescale the image by if `do_rescale` is set to `True`.
|
||||||
|
do_normalize (`bool`, *optional*, defaults to `self.do_normalize`):
|
||||||
|
Whether to normalize the image.
|
||||||
|
image_mean (`float` or `List[float]`, *optional*, defaults to `self.image_mean`):
|
||||||
|
Image mean to use for normalization. Only has an effect if `do_normalize` is set to `True`.
|
||||||
|
image_std (`float` or `List[float]`, *optional*, defaults to `self.image_std`):
|
||||||
|
Image standard deviation to use for normalization. Only has an effect if `do_normalize` is set to
|
||||||
|
`True`.
|
||||||
|
data_format (`ChannelDimension` or `str`, *optional*, defaults to `ChannelDimension.FIRST`):
|
||||||
|
The channel dimension format for the output image. Can be one of:
|
||||||
|
- `"channels_first"` or `ChannelDimension.FIRST`: image in (num_channels, height, width) format.
|
||||||
|
- `"channels_last"` or `ChannelDimension.LAST`: image in (height, width, num_channels) format.
|
||||||
|
- Unset: Use the channel dimension format of the input image.
|
||||||
|
input_data_format (`ChannelDimension` or `str`, *optional*):
|
||||||
|
The channel dimension format for the input image. If unset, the channel dimension format is inferred
|
||||||
|
from the input image. Can be one of:
|
||||||
|
- `"channels_first"` or `ChannelDimension.FIRST`: image in (num_channels, height, width) format.
|
||||||
|
- `"channels_last"` or `ChannelDimension.LAST`: image in (height, width, num_channels) format.
|
||||||
|
- `"none"` or `ChannelDimension.NONE`: image in (height, width) format.
|
||||||
|
"""
|
||||||
|
images = make_list_of_images(images)
|
||||||
|
|
||||||
|
if do_convert_rgb:
|
||||||
|
images = [convert_to_rgb(image) for image in images]
|
||||||
|
|
||||||
|
# All transformations expect numpy arrays.
|
||||||
|
images = [to_numpy_array(image) for image in images]
|
||||||
|
|
||||||
|
if is_scaled_image(images[0]) and do_rescale:
|
||||||
|
logger.warning_once(
|
||||||
|
"It looks like you are trying to rescale already rescaled images. If the input"
|
||||||
|
" images have pixel values between 0 and 1, set `do_rescale=False` to avoid rescaling them again."
|
||||||
|
)
|
||||||
|
if input_data_format is None:
|
||||||
|
# We assume that all images have the same channel dimension format.
|
||||||
|
input_data_format = infer_channel_dimension_format(images[0])
|
||||||
|
|
||||||
|
if do_resize:
|
||||||
|
images = [
|
||||||
|
self.resize(image=image, size=size, resample=resample, input_data_format=input_data_format)
|
||||||
|
for image in images
|
||||||
|
]
|
||||||
|
|
||||||
|
if do_center_crop:
|
||||||
|
images = [
|
||||||
|
self.center_crop(image=image, size=crop_size, input_data_format=input_data_format) for image in images
|
||||||
|
]
|
||||||
|
|
||||||
|
if do_rescale:
|
||||||
|
images = [
|
||||||
|
self.rescale(image=image, scale=rescale_factor, input_data_format=input_data_format)
|
||||||
|
for image in images
|
||||||
|
]
|
||||||
|
|
||||||
|
if do_normalize:
|
||||||
|
images = [
|
||||||
|
self.normalize(image=image, mean=image_mean, std=image_std, input_data_format=input_data_format)
|
||||||
|
for image in images
|
||||||
|
]
|
||||||
|
|
||||||
|
images = [
|
||||||
|
to_channel_dimension_format(image, data_format, input_channel_dim=input_data_format) for image in images
|
||||||
|
]
|
||||||
|
|
||||||
|
return images
|
||||||
|
|
||||||
|
def preprocess(
|
||||||
|
self,
|
||||||
|
images: VideoInput,
|
||||||
|
do_resize: bool = None,
|
||||||
|
size: Dict[str, int] = None,
|
||||||
|
resample: PILImageResampling = None,
|
||||||
|
do_center_crop: bool = None,
|
||||||
|
crop_size: int = None,
|
||||||
|
do_rescale: bool = None,
|
||||||
|
rescale_factor: float = None,
|
||||||
|
do_normalize: bool = None,
|
||||||
|
image_mean: Optional[Union[float, List[float]]] = None,
|
||||||
|
image_std: Optional[Union[float, List[float]]] = None,
|
||||||
|
do_convert_rgb: bool = None,
|
||||||
|
return_tensors: Optional[Union[str, TensorType]] = None,
|
||||||
|
data_format: Optional[ChannelDimension] = ChannelDimension.FIRST,
|
||||||
|
input_data_format: Optional[Union[str, ChannelDimension]] = None,
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Args:
|
||||||
|
images (`VideoInput`):
|
||||||
|
Videos to preprocess. Expects a single or batch of videos with pixel values ranging from 0 to 255. If
|
||||||
|
passing in images with pixel values between 0 and 1, set `do_rescale=False`.
|
||||||
|
do_resize (`bool`, *optional*, defaults to `self.do_resize`):
|
||||||
|
Whether to resize the video.
|
||||||
|
size (`Dict[str, int]`, *optional*, defaults to `self.size`):
|
||||||
|
Size of the video after resizing. Shortest edge of the video is resized to size["shortest_edge"], with
|
||||||
|
the longest edge resized to keep the input aspect ratio.
|
||||||
|
resample (`int`, *optional*, defaults to `self.resample`):
|
||||||
|
Resampling filter to use if resizing the video. This can be one of the enum `PILImageResampling`. Only
|
||||||
|
has an effect if `do_resize` is set to `True`.
|
||||||
|
do_center_crop (`bool`, *optional*, defaults to `self.do_center_crop`):
|
||||||
|
Whether to center crop the video.
|
||||||
|
crop_size (`Dict[str, int]`, *optional*, defaults to `self.crop_size`):
|
||||||
|
Size of the center crop. Only has an effect if `do_center_crop` is set to `True`.
|
||||||
|
do_rescale (`bool`, *optional*, defaults to `self.do_rescale`):
|
||||||
|
Whether to rescale the video.
|
||||||
|
rescale_factor (`float`, *optional*, defaults to `self.rescale_factor`):
|
||||||
|
Rescale factor to rescale the video by if `do_rescale` is set to `True`.
|
||||||
|
do_normalize (`bool`, *optional*, defaults to `self.do_normalize`):
|
||||||
|
Whether to normalize the video.
|
||||||
|
image_mean (`float` or `List[float]`, *optional*, defaults to `self.image_mean`):
|
||||||
|
Frame mean to use for normalization. Only has an effect if `do_normalize` is set to `True`.
|
||||||
|
image_std (`float` or `List[float]`, *optional*, defaults to `self.image_std`):
|
||||||
|
Frame standard deviation to use for normalization. Only has an effect if `do_normalize` is set to
|
||||||
|
`True`.
|
||||||
|
do_convert_rgb (`bool`, *optional*, defaults to `self.do_convert_rgb`):
|
||||||
|
Whether to convert the video to RGB.
|
||||||
|
return_tensors (`str` or `TensorType`, *optional*):
|
||||||
|
The type of tensors to return. Can be one of:
|
||||||
|
- Unset: Return a list of `np.ndarray`.
|
||||||
|
- `TensorType.TENSORFLOW` or `'tf'`: Return a batch of type `tf.Tensor`.
|
||||||
|
- `TensorType.PYTORCH` or `'pt'`: Return a batch of type `torch.Tensor`.
|
||||||
|
- `TensorType.NUMPY` or `'np'`: Return a batch of type `np.ndarray`.
|
||||||
|
- `TensorType.JAX` or `'jax'`: Return a batch of type `jax.numpy.ndarray`.
|
||||||
|
data_format (`ChannelDimension` or `str`, *optional*, defaults to `ChannelDimension.FIRST`):
|
||||||
|
The channel dimension format for the output image. Can be one of:
|
||||||
|
- `"channels_first"` or `ChannelDimension.FIRST`: image in (num_channels, height, width) format.
|
||||||
|
- `"channels_last"` or `ChannelDimension.LAST`: image in (height, width, num_channels) format.
|
||||||
|
- Unset: Use the channel dimension format of the input image.
|
||||||
|
input_data_format (`ChannelDimension` or `str`, *optional*):
|
||||||
|
The channel dimension format for the input image. If unset, the channel dimension format is inferred
|
||||||
|
from the input image. Can be one of:
|
||||||
|
- `"channels_first"` or `ChannelDimension.FIRST`: image in (num_channels, height, width) format.
|
||||||
|
- `"channels_last"` or `ChannelDimension.LAST`: image in (height, width, num_channels) format.
|
||||||
|
- `"none"` or `ChannelDimension.NONE`: image in (height, width) format.
|
||||||
|
"""
|
||||||
|
do_resize = do_resize if do_resize is not None else self.do_resize
|
||||||
|
size = size if size is not None else self.size
|
||||||
|
size = get_size_dict(size, param_name="size", default_to_square=False)
|
||||||
|
resample = resample if resample is not None else self.resample
|
||||||
|
do_center_crop = do_center_crop if do_center_crop is not None else self.do_center_crop
|
||||||
|
crop_size = crop_size if crop_size is not None else self.crop_size
|
||||||
|
crop_size = get_size_dict(crop_size, param_name="crop_size", default_to_square=True)
|
||||||
|
do_rescale = do_rescale if do_rescale is not None else self.do_rescale
|
||||||
|
rescale_factor = rescale_factor if rescale_factor is not None else self.rescale_factor
|
||||||
|
do_normalize = do_normalize if do_normalize is not None else self.do_normalize
|
||||||
|
image_mean = image_mean if image_mean is not None else self.image_mean
|
||||||
|
image_std = image_std if image_std is not None else self.image_std
|
||||||
|
do_convert_rgb = do_convert_rgb if do_convert_rgb is not None else self.do_convert_rgb
|
||||||
|
|
||||||
|
images = make_batched_videos(images)
|
||||||
|
|
||||||
|
validate_preprocess_arguments(
|
||||||
|
do_rescale=do_rescale,
|
||||||
|
rescale_factor=rescale_factor,
|
||||||
|
do_normalize=do_normalize,
|
||||||
|
image_mean=image_mean,
|
||||||
|
image_std=image_std,
|
||||||
|
do_center_crop=do_center_crop,
|
||||||
|
crop_size=crop_size,
|
||||||
|
do_resize=do_resize,
|
||||||
|
size=size,
|
||||||
|
resample=resample,
|
||||||
|
)
|
||||||
|
|
||||||
|
# preprocess each video frame by frame
|
||||||
|
pixel_values = [
|
||||||
|
self._preprocess(
|
||||||
|
frames,
|
||||||
|
do_resize=do_resize,
|
||||||
|
size=size,
|
||||||
|
resample=resample,
|
||||||
|
do_center_crop=do_center_crop,
|
||||||
|
crop_size=crop_size,
|
||||||
|
do_rescale=do_rescale,
|
||||||
|
rescale_factor=rescale_factor,
|
||||||
|
do_normalize=do_normalize,
|
||||||
|
image_mean=image_mean,
|
||||||
|
image_std=image_std,
|
||||||
|
data_format=data_format,
|
||||||
|
input_data_format=input_data_format,
|
||||||
|
)
|
||||||
|
for frames in images
|
||||||
|
]
|
||||||
|
|
||||||
|
data = {"pixel_values_videos": pixel_values}
|
||||||
|
return BatchFeature(data=data, tensor_type=return_tensors)
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,220 @@
|
|||||||
|
# coding=utf-8
|
||||||
|
# Copyright 2024 The HuggingFace Inc. team.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
"""
|
||||||
|
Processor class for LLaVa-NeXT-Video.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from typing import TYPE_CHECKING, List, Optional, Union
|
||||||
|
|
||||||
|
from ...feature_extraction_utils import BatchFeature
|
||||||
|
from ...image_utils import ImageInput, VideoInput
|
||||||
|
from ...processing_utils import ProcessorMixin
|
||||||
|
from ...tokenization_utils_base import PaddingStrategy, PreTokenizedInput, TextInput, TruncationStrategy
|
||||||
|
from ...utils import TensorType, logging
|
||||||
|
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
pass
|
||||||
|
|
||||||
|
logger = logging.get_logger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class LlavaNextVideoProcessor(ProcessorMixin):
|
||||||
|
r"""
|
||||||
|
Constructs a LLaVa-NeXT-Video processor which wraps a LLaVa-NeXT image processor, LLaVa-NeXT-Video video processor and
|
||||||
|
a LLaMa tokenizer into a single processor.
|
||||||
|
|
||||||
|
[`LlavaNextVideoProcessor`] offers all the functionalities of [`LlavaNextImageProcessor`], [`LlavaNextVideoImageProcessor`] and
|
||||||
|
[`LlamaTokenizerFast`]. See the [`~LlavaNextVideoProcessor.__call__`] and [`~LlavaNextVideoProcessor.decode`] for more information.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
video_processor ([`LlavaNextVideoImageProcessor`], *optional*):
|
||||||
|
The video processor is a required input.
|
||||||
|
image_processor ([`LlavaNextImageProcessor`], *optional*):
|
||||||
|
The image processor is a required input.
|
||||||
|
tokenizer ([`LlamaTokenizerFast`], *optional*):
|
||||||
|
The tokenizer is a required input.
|
||||||
|
chat_template (`str`, *optional*):
|
||||||
|
Jinja chat template that will be used in tokenizer's `apply_chat_template`
|
||||||
|
"""
|
||||||
|
|
||||||
|
# video and image processor share same args, but have different processing logic
|
||||||
|
# only image processor config is saved in the hub
|
||||||
|
attributes = ["video_processor", "image_processor", "tokenizer"]
|
||||||
|
image_processor_class = "LlavaNextImageProcessor"
|
||||||
|
video_processor_class = "LlavaNextVideoImageProcessor"
|
||||||
|
tokenizer_class = ("LlamaTokenizer", "LlamaTokenizerFast")
|
||||||
|
|
||||||
|
def __init__(self, video_processor=None, image_processor=None, tokenizer=None, chat_template=None):
|
||||||
|
super().__init__(video_processor, image_processor, tokenizer, chat_template=chat_template)
|
||||||
|
|
||||||
|
def __call__(
|
||||||
|
self,
|
||||||
|
text: Union[TextInput, PreTokenizedInput, List[TextInput], List[PreTokenizedInput]],
|
||||||
|
images: ImageInput = None,
|
||||||
|
videos: VideoInput = None,
|
||||||
|
padding: Union[bool, str, PaddingStrategy] = False,
|
||||||
|
truncation: Union[bool, str, TruncationStrategy] = None,
|
||||||
|
max_length: int = None,
|
||||||
|
return_tensors: Optional[Union[str, TensorType]] = TensorType.PYTORCH,
|
||||||
|
) -> BatchFeature:
|
||||||
|
"""
|
||||||
|
Main method to prepare for the model one or several sequences(s) and image(s). This method forwards the `text`
|
||||||
|
and `kwargs` arguments to LlamaTokenizerFast's [`~LlamaTokenizerFast.__call__`] if `text` is not `None` to encode
|
||||||
|
the text. To prepare the image(s), this method forwards the `images` and `kwrags` arguments to
|
||||||
|
LlavaNextImageProcessor's [`~LlavaNextImageProcessor.__call__`] if `images` is not `None`. To prepare the video(s),
|
||||||
|
this method forwards the `videos` and `kwrags` arguments to LlavaNextVideoImageProcessor's
|
||||||
|
[`~LlavaNextVideoImageProcessor.__call__`] if `videos` is not `None`. Please refer to the doctsring
|
||||||
|
of the above two methods for more information.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
text (`str`, `List[str]`, `List[List[str]]`):
|
||||||
|
The sequence or batch of sequences to be encoded. Each sequence can be a string or a list of strings
|
||||||
|
(pretokenized string). If the sequences are provided as list of strings (pretokenized), you must set
|
||||||
|
`is_split_into_words=True` (to lift the ambiguity with a batch of sequences).
|
||||||
|
images (`PIL.Image.Image`, `np.ndarray`, `torch.Tensor`, `List[PIL.Image.Image]`, `List[np.ndarray]`, `List[torch.Tensor]`):
|
||||||
|
The image or batch of images to be prepared. Each image can be a PIL image, NumPy array or PyTorch
|
||||||
|
tensor. Both channels-first and channels-last formats are supported.
|
||||||
|
videos (`np.ndarray`, `torch.Tensor`, `List[np.ndarray]`, `List[torch.Tensor]`):
|
||||||
|
The image or batch of videos to be prepared. Each video can be a 4D NumPy array or PyTorch
|
||||||
|
tensor, or a nested list of 3D frames. Both channels-first and channels-last formats are supported.
|
||||||
|
padding (`bool`, `str` or [`~utils.PaddingStrategy`], *optional*, defaults to `False`):
|
||||||
|
Select a strategy to pad the returned sequences (according to the model's padding side and padding
|
||||||
|
index) among:
|
||||||
|
- `True` or `'longest'`: Pad to the longest sequence in the batch (or no padding if only a single
|
||||||
|
sequence if provided).
|
||||||
|
- `'max_length'`: Pad to a maximum length specified with the argument `max_length` or to the maximum
|
||||||
|
acceptable input length for the model if that argument is not provided.
|
||||||
|
- `False` or `'do_not_pad'` (default): No padding (i.e., can output a batch with sequences of different
|
||||||
|
lengths).
|
||||||
|
max_length (`int`, *optional*):
|
||||||
|
Maximum length of the returned list and optionally padding length (see above).
|
||||||
|
truncation (`bool`, *optional*):
|
||||||
|
Activates truncation to cut input sequences longer than `max_length` to `max_length`.
|
||||||
|
return_tensors (`str` or [`~utils.TensorType`], *optional*):
|
||||||
|
If set, will return tensors of a particular framework. Acceptable values are:
|
||||||
|
|
||||||
|
- `'tf'`: Return TensorFlow `tf.constant` objects.
|
||||||
|
- `'pt'`: Return PyTorch `torch.Tensor` objects.
|
||||||
|
- `'np'`: Return NumPy `np.ndarray` objects.
|
||||||
|
- `'jax'`: Return JAX `jnp.ndarray` objects.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
[`BatchFeature`]: A [`BatchFeature`] with the following fields:
|
||||||
|
|
||||||
|
- **input_ids** -- List of token ids to be fed to a model. Returned when `text` is not `None`.
|
||||||
|
- **attention_mask** -- List of indices specifying which tokens should be attended to by the model (when
|
||||||
|
`return_attention_mask=True` or if *"attention_mask"* is in `self.model_input_names` and if `text` is not
|
||||||
|
`None`).
|
||||||
|
- **pixel_values** -- Pixel values to be fed to a model. Returned when `images` is not `None`.
|
||||||
|
"""
|
||||||
|
if images is not None:
|
||||||
|
image_inputs = self.image_processor(images, return_tensors=return_tensors)
|
||||||
|
else:
|
||||||
|
image_inputs = {}
|
||||||
|
|
||||||
|
if videos is not None:
|
||||||
|
videos_inputs = self.video_processor(videos, return_tensors=return_tensors)
|
||||||
|
else:
|
||||||
|
videos_inputs = {}
|
||||||
|
|
||||||
|
text_inputs = self.tokenizer(
|
||||||
|
text, return_tensors=return_tensors, padding=padding, truncation=truncation, max_length=max_length
|
||||||
|
)
|
||||||
|
|
||||||
|
return BatchFeature(data={**text_inputs, **image_inputs, **videos_inputs})
|
||||||
|
|
||||||
|
# Copied from transformers.models.clip.processing_clip.CLIPProcessor.batch_decode with CLIP->Llama
|
||||||
|
def batch_decode(self, *args, **kwargs):
|
||||||
|
"""
|
||||||
|
This method forwards all its arguments to LlamaTokenizerFast's [`~PreTrainedTokenizer.batch_decode`]. Please
|
||||||
|
refer to the docstring of this method for more information.
|
||||||
|
"""
|
||||||
|
return self.tokenizer.batch_decode(*args, **kwargs)
|
||||||
|
|
||||||
|
# Copied from transformers.models.clip.processing_clip.CLIPProcessor.decode with CLIP->Llama
|
||||||
|
def decode(self, *args, **kwargs):
|
||||||
|
"""
|
||||||
|
This method forwards all its arguments to LlamaTokenizerFast's [`~PreTrainedTokenizer.decode`]. Please refer to
|
||||||
|
the docstring of this method for more information.
|
||||||
|
"""
|
||||||
|
return self.tokenizer.decode(*args, **kwargs)
|
||||||
|
|
||||||
|
@property
|
||||||
|
# Copied from transformers.models.clip.processing_clip.CLIPProcessor.model_input_names
|
||||||
|
def model_input_names(self):
|
||||||
|
tokenizer_input_names = self.tokenizer.model_input_names
|
||||||
|
image_processor_input_names = self.image_processor.model_input_names
|
||||||
|
return list(dict.fromkeys(tokenizer_input_names + image_processor_input_names))
|
||||||
|
|
||||||
|
@property
|
||||||
|
def default_chat_template(self):
|
||||||
|
"""
|
||||||
|
This default vicuna template formats inputs in the form of a chat history. For each message in the chat history:
|
||||||
|
* the template will output the role of the speaker followed by the content of the message.
|
||||||
|
* content is a list of strings and images.
|
||||||
|
* If the content element is an image, the template will output a sequence of <image> or <video> tokens
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```python
|
||||||
|
messages = [{
|
||||||
|
"role": "user",
|
||||||
|
"content": [
|
||||||
|
{"type": "text", "text": "What’s the content of this video?"},
|
||||||
|
{"type": "video"},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"role": "assistant",
|
||||||
|
"content": [{"type": "text", "text": "This picture shows a red stop sign."},]
|
||||||
|
}]
|
||||||
|
```
|
||||||
|
|
||||||
|
Will create outputs like:
|
||||||
|
```
|
||||||
|
USER: <video>\nWhat is the content of this video?
|
||||||
|
ASSITANT: This picture shows a red stop sign
|
||||||
|
```
|
||||||
|
"""
|
||||||
|
# fmt: off
|
||||||
|
return (
|
||||||
|
"{% for message in messages %}"
|
||||||
|
"{% if message['role'] == 'system' %}"
|
||||||
|
"{{ message['content'][0]['text'] }}"
|
||||||
|
"{% else %}"
|
||||||
|
"{{ message['role'].upper() + ': '}}"
|
||||||
|
"{% endif %}"
|
||||||
|
|
||||||
|
"{# Render all images first #}"
|
||||||
|
"{% for content in message['content'] | selectattr('type', 'equalto', 'image') %}"
|
||||||
|
"{{ '<image>\n' }}"
|
||||||
|
"{% endfor %}"
|
||||||
|
|
||||||
|
"{# Render all videos next #}"
|
||||||
|
"{% for content in message['content'] | selectattr('type', 'equalto', 'video') %}"
|
||||||
|
"{{ '<video>\n' }}"
|
||||||
|
"{% endfor %}"
|
||||||
|
|
||||||
|
"{# Render all text finally #}"
|
||||||
|
"{% for content in message['content'] | selectattr('type', 'equalto', 'text') %}"
|
||||||
|
"{{ content['text'] + ' '}}"
|
||||||
|
"{% endfor %}"
|
||||||
|
"{% endfor %}"
|
||||||
|
"{% if add_generation_prompt %}"
|
||||||
|
"{{ 'ASSISTANT:' }}"
|
||||||
|
"{% endif %}"
|
||||||
|
)
|
||||||
|
# fmt: on
|
||||||
@@ -5140,6 +5140,20 @@ class LlavaNextPreTrainedModel(metaclass=DummyObject):
|
|||||||
requires_backends(self, ["torch"])
|
requires_backends(self, ["torch"])
|
||||||
|
|
||||||
|
|
||||||
|
class LlavaNextVideoForConditionalGeneration(metaclass=DummyObject):
|
||||||
|
_backends = ["torch"]
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
requires_backends(self, ["torch"])
|
||||||
|
|
||||||
|
|
||||||
|
class LlavaNextVideoPreTrainedModel(metaclass=DummyObject):
|
||||||
|
_backends = ["torch"]
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
requires_backends(self, ["torch"])
|
||||||
|
|
||||||
|
|
||||||
class LongformerForMaskedLM(metaclass=DummyObject):
|
class LongformerForMaskedLM(metaclass=DummyObject):
|
||||||
_backends = ["torch"]
|
_backends = ["torch"]
|
||||||
|
|
||||||
|
|||||||
@@ -359,6 +359,13 @@ class LlavaNextImageProcessor(metaclass=DummyObject):
|
|||||||
requires_backends(self, ["vision"])
|
requires_backends(self, ["vision"])
|
||||||
|
|
||||||
|
|
||||||
|
class LlavaNextVideoImageProcessor(metaclass=DummyObject):
|
||||||
|
_backends = ["vision"]
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
requires_backends(self, ["vision"])
|
||||||
|
|
||||||
|
|
||||||
class Mask2FormerImageProcessor(metaclass=DummyObject):
|
class Mask2FormerImageProcessor(metaclass=DummyObject):
|
||||||
_backends = ["vision"]
|
_backends = ["vision"]
|
||||||
|
|
||||||
|
|||||||
@@ -204,6 +204,14 @@ class LlavaForConditionalGenerationModelTest(ModelTesterMixin, unittest.TestCase
|
|||||||
def test_training_gradient_checkpointing_use_reentrant_false(self):
|
def test_training_gradient_checkpointing_use_reentrant_false(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@unittest.skip(reason="Compile not yet supported because in LLava models")
|
||||||
|
def test_sdpa_can_compile_dynamic(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@unittest.skip(reason="Compile not yet supported because in LLava models")
|
||||||
|
def test_sdpa_can_dispatch_on_flash(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
@require_torch
|
@require_torch
|
||||||
class LlavaForConditionalGenerationIntegrationTest(unittest.TestCase):
|
class LlavaForConditionalGenerationIntegrationTest(unittest.TestCase):
|
||||||
|
|||||||
@@ -265,6 +265,14 @@ class LlavaNextForConditionalGenerationModelTest(ModelTesterMixin, GenerationTes
|
|||||||
def test_cpu_offload(self):
|
def test_cpu_offload(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@unittest.skip(reason="Compile not yet supported because in LLava models")
|
||||||
|
def test_sdpa_can_compile_dynamic(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@unittest.skip(reason="Compile not yet supported because in LLava models")
|
||||||
|
def test_sdpa_can_dispatch_on_flash(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
@require_torch
|
@require_torch
|
||||||
class LlavaNextForConditionalGenerationIntegrationTest(unittest.TestCase):
|
class LlavaNextForConditionalGenerationIntegrationTest(unittest.TestCase):
|
||||||
|
|||||||
0
tests/models/llava_next_video/__init__.py
Normal file
0
tests/models/llava_next_video/__init__.py
Normal file
@@ -0,0 +1,218 @@
|
|||||||
|
# coding=utf-8
|
||||||
|
# Copyright 2024 HuggingFace Inc.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
from transformers.image_utils import OPENAI_CLIP_MEAN, OPENAI_CLIP_STD
|
||||||
|
from transformers.testing_utils import require_torch, require_vision
|
||||||
|
from transformers.utils import is_torch_available, is_vision_available
|
||||||
|
|
||||||
|
from ...test_image_processing_common import ImageProcessingTestMixin, prepare_image_inputs
|
||||||
|
|
||||||
|
|
||||||
|
if is_torch_available():
|
||||||
|
import torch
|
||||||
|
|
||||||
|
if is_vision_available():
|
||||||
|
from PIL import Image
|
||||||
|
|
||||||
|
from transformers import LlavaNextVideoImageProcessor
|
||||||
|
|
||||||
|
|
||||||
|
class LlavaNextVideoProcessingTester(unittest.TestCase):
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
parent,
|
||||||
|
batch_size=5,
|
||||||
|
num_channels=3,
|
||||||
|
image_size=18,
|
||||||
|
min_resolution=30,
|
||||||
|
max_resolution=80,
|
||||||
|
do_resize=True,
|
||||||
|
size=None,
|
||||||
|
do_center_crop=True,
|
||||||
|
crop_size=None,
|
||||||
|
do_normalize=True,
|
||||||
|
image_mean=OPENAI_CLIP_MEAN,
|
||||||
|
image_std=OPENAI_CLIP_STD,
|
||||||
|
do_convert_rgb=True,
|
||||||
|
):
|
||||||
|
size = size if size is not None else {"shortest_edge": 20}
|
||||||
|
crop_size = crop_size if crop_size is not None else {"height": 18, "width": 18}
|
||||||
|
self.parent = parent
|
||||||
|
self.batch_size = batch_size
|
||||||
|
self.num_channels = num_channels
|
||||||
|
self.image_size = image_size
|
||||||
|
self.min_resolution = min_resolution
|
||||||
|
self.max_resolution = max_resolution
|
||||||
|
self.do_resize = do_resize
|
||||||
|
self.size = size
|
||||||
|
self.do_center_crop = do_center_crop
|
||||||
|
self.crop_size = crop_size
|
||||||
|
self.do_normalize = do_normalize
|
||||||
|
self.image_mean = image_mean
|
||||||
|
self.image_std = image_std
|
||||||
|
self.do_convert_rgb = do_convert_rgb
|
||||||
|
|
||||||
|
def prepare_image_processor_dict(self):
|
||||||
|
return {
|
||||||
|
"do_resize": self.do_resize,
|
||||||
|
"size": self.size,
|
||||||
|
"do_center_crop": self.do_center_crop,
|
||||||
|
"crop_size": self.crop_size,
|
||||||
|
"do_normalize": self.do_normalize,
|
||||||
|
"image_mean": self.image_mean,
|
||||||
|
"image_std": self.image_std,
|
||||||
|
"do_convert_rgb": self.do_convert_rgb,
|
||||||
|
}
|
||||||
|
|
||||||
|
# Copied from tests.models.clip.test_image_processing_clip.CLIPImageProcessingTester.expected_output_image_shape
|
||||||
|
def expected_output_image_shape(self, images):
|
||||||
|
return self.num_channels, self.crop_size["height"], self.crop_size["width"]
|
||||||
|
|
||||||
|
# Copied from tests.models.clip.test_image_processing_clip.CLIPImageProcessingTester.prepare_image_inputs
|
||||||
|
def prepare_image_inputs(self, equal_resolution=False, numpify=False, torchify=False):
|
||||||
|
return prepare_image_inputs(
|
||||||
|
batch_size=self.batch_size,
|
||||||
|
num_channels=self.num_channels,
|
||||||
|
min_resolution=self.min_resolution,
|
||||||
|
max_resolution=self.max_resolution,
|
||||||
|
equal_resolution=equal_resolution,
|
||||||
|
numpify=numpify,
|
||||||
|
torchify=torchify,
|
||||||
|
)
|
||||||
|
|
||||||
|
def prepare_video_inputs(self, equal_resolution=False, numpify=False, torchify=False):
|
||||||
|
images = prepare_image_inputs(
|
||||||
|
batch_size=self.batch_size,
|
||||||
|
num_channels=self.num_channels,
|
||||||
|
min_resolution=self.min_resolution,
|
||||||
|
max_resolution=self.max_resolution,
|
||||||
|
equal_resolution=equal_resolution,
|
||||||
|
numpify=numpify,
|
||||||
|
torchify=torchify,
|
||||||
|
)
|
||||||
|
|
||||||
|
# let's simply copy the frames to fake a long video-clip
|
||||||
|
if numpify or torchify:
|
||||||
|
videos = []
|
||||||
|
for image in images:
|
||||||
|
if numpify:
|
||||||
|
video = image[None, ...].repeat(8, 0)
|
||||||
|
else:
|
||||||
|
video = image[None, ...].repeat(8, 1, 1, 1)
|
||||||
|
videos.append(video)
|
||||||
|
else:
|
||||||
|
videos = []
|
||||||
|
for pil_image in images:
|
||||||
|
videos.append([pil_image] * 8)
|
||||||
|
|
||||||
|
return videos
|
||||||
|
|
||||||
|
|
||||||
|
@require_torch
|
||||||
|
@require_vision
|
||||||
|
class LlavaNextVideoProcessingTest(ImageProcessingTestMixin, unittest.TestCase):
|
||||||
|
image_processing_class = LlavaNextVideoImageProcessor if is_vision_available() else None
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super().setUp()
|
||||||
|
self.image_processor_tester = LlavaNextVideoProcessingTester(self)
|
||||||
|
|
||||||
|
@property
|
||||||
|
# Copied from tests.models.clip.test_image_processing_clip.CLIPImageProcessingTest.image_processor_dict
|
||||||
|
def image_processor_dict(self):
|
||||||
|
return self.image_processor_tester.prepare_image_processor_dict()
|
||||||
|
|
||||||
|
def test_image_processor_properties(self):
|
||||||
|
image_processing = self.image_processing_class(**self.image_processor_dict)
|
||||||
|
self.assertTrue(hasattr(image_processing, "do_resize"))
|
||||||
|
self.assertTrue(hasattr(image_processing, "size"))
|
||||||
|
self.assertTrue(hasattr(image_processing, "do_center_crop"))
|
||||||
|
self.assertTrue(hasattr(image_processing, "center_crop"))
|
||||||
|
self.assertTrue(hasattr(image_processing, "do_normalize"))
|
||||||
|
self.assertTrue(hasattr(image_processing, "image_mean"))
|
||||||
|
self.assertTrue(hasattr(image_processing, "image_std"))
|
||||||
|
self.assertTrue(hasattr(image_processing, "do_convert_rgb"))
|
||||||
|
|
||||||
|
# Copied from tests.models.clip.test_image_processing_clip.CLIPImageProcessingTest.test_image_processor_from_dict_with_kwargs
|
||||||
|
def test_image_processor_from_dict_with_kwargs(self):
|
||||||
|
image_processor = self.image_processing_class.from_dict(self.image_processor_dict)
|
||||||
|
self.assertEqual(image_processor.size, {"shortest_edge": 20})
|
||||||
|
self.assertEqual(image_processor.crop_size, {"height": 18, "width": 18})
|
||||||
|
|
||||||
|
image_processor = self.image_processing_class.from_dict(self.image_processor_dict, size=42, crop_size=84)
|
||||||
|
self.assertEqual(image_processor.size, {"shortest_edge": 42})
|
||||||
|
self.assertEqual(image_processor.crop_size, {"height": 84, "width": 84})
|
||||||
|
|
||||||
|
def test_call_pil(self):
|
||||||
|
# Initialize image_processing
|
||||||
|
image_processing = self.image_processing_class(**self.image_processor_dict)
|
||||||
|
# create random numpy tensors
|
||||||
|
video_inputs = self.image_processor_tester.prepare_video_inputs(equal_resolution=True)
|
||||||
|
for video in video_inputs:
|
||||||
|
self.assertIsInstance(video[0], Image.Image)
|
||||||
|
|
||||||
|
# Test not batched input (pass as `videos` arg to test that ImageProcessor can handle videos in absence of images!)
|
||||||
|
encoded_videos = image_processing(images=video_inputs[0], return_tensors="pt").pixel_values_videos
|
||||||
|
expected_output_video_shape = (1, 8, 3, 18, 18)
|
||||||
|
self.assertEqual(tuple(encoded_videos.shape), expected_output_video_shape)
|
||||||
|
|
||||||
|
# Test batched
|
||||||
|
encoded_videos = image_processing(images=video_inputs, return_tensors="pt").pixel_values_videos
|
||||||
|
expected_output_video_shape = (5, 8, 3, 18, 18)
|
||||||
|
self.assertEqual(tuple(encoded_videos.shape), expected_output_video_shape)
|
||||||
|
|
||||||
|
def test_call_numpy(self):
|
||||||
|
# Initialize image_processing
|
||||||
|
image_processing = self.image_processing_class(**self.image_processor_dict)
|
||||||
|
# create random numpy tensors
|
||||||
|
video_inputs = self.image_processor_tester.prepare_video_inputs(equal_resolution=True, numpify=True)
|
||||||
|
for video in video_inputs:
|
||||||
|
self.assertIsInstance(video, np.ndarray)
|
||||||
|
|
||||||
|
# Test not batched input (pass as `videos` arg to test that ImageProcessor can handle videos in absence of images!)
|
||||||
|
encoded_videos = image_processing(images=video_inputs[0], return_tensors="pt").pixel_values_videos
|
||||||
|
expected_output_video_shape = (1, 8, 3, 18, 18)
|
||||||
|
self.assertEqual(tuple(encoded_videos.shape), expected_output_video_shape)
|
||||||
|
|
||||||
|
# Test batched
|
||||||
|
encoded_videos = image_processing(images=video_inputs, return_tensors="pt").pixel_values_videos
|
||||||
|
expected_output_video_shape = (5, 8, 3, 18, 18)
|
||||||
|
self.assertEqual(tuple(encoded_videos.shape), expected_output_video_shape)
|
||||||
|
|
||||||
|
def test_call_pytorch(self):
|
||||||
|
# Initialize image_processing
|
||||||
|
image_processing = self.image_processing_class(**self.image_processor_dict)
|
||||||
|
# create random PyTorch tensors
|
||||||
|
video_inputs = self.image_processor_tester.prepare_video_inputs(equal_resolution=True, torchify=True)
|
||||||
|
for video in video_inputs:
|
||||||
|
self.assertIsInstance(video, torch.Tensor)
|
||||||
|
|
||||||
|
# Test not batched input
|
||||||
|
encoded_videos = image_processing(images=video_inputs[0], return_tensors="pt").pixel_values_videos
|
||||||
|
expected_output_video_shape = (1, 8, 3, 18, 18)
|
||||||
|
self.assertEqual(tuple(encoded_videos.shape), expected_output_video_shape)
|
||||||
|
|
||||||
|
# Test batched
|
||||||
|
encoded_videos = image_processing(images=video_inputs, return_tensors="pt").pixel_values_videos
|
||||||
|
expected_output_video_shape = (5, 8, 3, 18, 18)
|
||||||
|
self.assertEqual(tuple(encoded_videos.shape), expected_output_video_shape)
|
||||||
|
|
||||||
|
@unittest.skip("LlavaNextVideoImageProcessor doesn't treat 4 channel PIL and numpy consistently yet")
|
||||||
|
def test_call_numpy_4_channels(self):
|
||||||
|
pass
|
||||||
455
tests/models/llava_next_video/test_modeling_llava_next_video.py
Normal file
455
tests/models/llava_next_video/test_modeling_llava_next_video.py
Normal file
@@ -0,0 +1,455 @@
|
|||||||
|
# coding=utf-8
|
||||||
|
# Copyright 2024 The HuggingFace Inc. team. All rights reserved.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
"""Testing suite for the PyTorch Llava-NeXT model."""
|
||||||
|
|
||||||
|
import gc
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
|
from huggingface_hub import hf_hub_download
|
||||||
|
|
||||||
|
from transformers import (
|
||||||
|
AutoProcessor,
|
||||||
|
LlavaNextVideoConfig,
|
||||||
|
LlavaNextVideoForConditionalGeneration,
|
||||||
|
is_torch_available,
|
||||||
|
is_vision_available,
|
||||||
|
)
|
||||||
|
from transformers.testing_utils import (
|
||||||
|
require_bitsandbytes,
|
||||||
|
require_torch,
|
||||||
|
slow,
|
||||||
|
torch_device,
|
||||||
|
)
|
||||||
|
|
||||||
|
from ...generation.test_utils import GenerationTesterMixin
|
||||||
|
from ...test_configuration_common import ConfigTester
|
||||||
|
from ...test_modeling_common import (
|
||||||
|
ModelTesterMixin,
|
||||||
|
_config_zero_init,
|
||||||
|
floats_tensor,
|
||||||
|
ids_tensor,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
if is_torch_available():
|
||||||
|
import torch
|
||||||
|
|
||||||
|
else:
|
||||||
|
is_torch_greater_or_equal_than_2_0 = False
|
||||||
|
|
||||||
|
if is_vision_available():
|
||||||
|
from PIL import Image
|
||||||
|
|
||||||
|
|
||||||
|
class LlavaNextVideoVisionText2TextModelTester:
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
parent,
|
||||||
|
ignore_index=-100,
|
||||||
|
image_token_index=0,
|
||||||
|
video_token_index=1,
|
||||||
|
projector_hidden_act="gelu",
|
||||||
|
seq_length=7,
|
||||||
|
vision_feature_select_strategy="default",
|
||||||
|
vision_feature_layer=-1,
|
||||||
|
text_config={
|
||||||
|
"model_type": "llama",
|
||||||
|
"seq_length": 7,
|
||||||
|
"is_training": True,
|
||||||
|
"use_input_mask": True,
|
||||||
|
"use_token_type_ids": False,
|
||||||
|
"use_labels": True,
|
||||||
|
"vocab_size": 99,
|
||||||
|
"hidden_size": 32,
|
||||||
|
"num_hidden_layers": 2,
|
||||||
|
"num_attention_heads": 4,
|
||||||
|
"intermediate_size": 37,
|
||||||
|
"hidden_act": "gelu",
|
||||||
|
"hidden_dropout_prob": 0.1,
|
||||||
|
"attention_probs_dropout_prob": 0.1,
|
||||||
|
"max_position_embeddings": 580,
|
||||||
|
"type_vocab_size": 16,
|
||||||
|
"type_sequence_label_size": 2,
|
||||||
|
"initializer_range": 0.02,
|
||||||
|
"num_labels": 3,
|
||||||
|
"num_choices": 4,
|
||||||
|
"pad_token_id": 0,
|
||||||
|
},
|
||||||
|
is_training=True,
|
||||||
|
vision_config={
|
||||||
|
"image_size": 16,
|
||||||
|
"patch_size": 2,
|
||||||
|
"num_channels": 3,
|
||||||
|
"is_training": True,
|
||||||
|
"hidden_size": 32,
|
||||||
|
"projection_dim": 32,
|
||||||
|
"num_hidden_layers": 2,
|
||||||
|
"num_attention_heads": 4,
|
||||||
|
"intermediate_size": 37,
|
||||||
|
"dropout": 0.1,
|
||||||
|
"attention_dropout": 0.1,
|
||||||
|
"initializer_range": 0.02,
|
||||||
|
},
|
||||||
|
):
|
||||||
|
self.parent = parent
|
||||||
|
self.ignore_index = ignore_index
|
||||||
|
self.image_token_index = image_token_index
|
||||||
|
self.video_token_index = video_token_index
|
||||||
|
self.projector_hidden_act = projector_hidden_act
|
||||||
|
self.vision_feature_select_strategy = vision_feature_select_strategy
|
||||||
|
self.vision_feature_layer = vision_feature_layer
|
||||||
|
self.text_config = text_config
|
||||||
|
self.vision_config = vision_config
|
||||||
|
self.seq_length = seq_length
|
||||||
|
|
||||||
|
self.num_hidden_layers = text_config["num_hidden_layers"]
|
||||||
|
self.vocab_size = text_config["vocab_size"]
|
||||||
|
self.hidden_size = text_config["hidden_size"]
|
||||||
|
self.num_attention_heads = text_config["num_attention_heads"]
|
||||||
|
self.is_training = is_training
|
||||||
|
|
||||||
|
self.batch_size = 3
|
||||||
|
self.num_channels = 3
|
||||||
|
self.image_size = 30
|
||||||
|
self.encoder_seq_length = 468
|
||||||
|
self.image_grid_pinpoints = [[32, 32]]
|
||||||
|
|
||||||
|
def get_config(self):
|
||||||
|
return LlavaNextVideoConfig(
|
||||||
|
text_config=self.text_config,
|
||||||
|
vision_config=self.vision_config,
|
||||||
|
ignore_index=self.ignore_index,
|
||||||
|
image_token_index=self.image_token_index,
|
||||||
|
video_token_index=self.video_token_index,
|
||||||
|
projector_hidden_act=self.projector_hidden_act,
|
||||||
|
vision_feature_select_strategy=self.vision_feature_select_strategy,
|
||||||
|
vision_feature_layer=self.vision_feature_layer,
|
||||||
|
image_grid_pinpoints=self.image_grid_pinpoints,
|
||||||
|
)
|
||||||
|
|
||||||
|
def prepare_config_and_inputs(self):
|
||||||
|
pixel_values = floats_tensor(
|
||||||
|
[
|
||||||
|
self.batch_size,
|
||||||
|
5,
|
||||||
|
self.vision_config["num_channels"],
|
||||||
|
self.vision_config["image_size"],
|
||||||
|
self.vision_config["image_size"],
|
||||||
|
]
|
||||||
|
)
|
||||||
|
pixel_values_videos = floats_tensor(
|
||||||
|
[
|
||||||
|
self.batch_size,
|
||||||
|
8,
|
||||||
|
self.vision_config["num_channels"],
|
||||||
|
self.vision_config["image_size"],
|
||||||
|
self.vision_config["image_size"],
|
||||||
|
]
|
||||||
|
)
|
||||||
|
config = self.get_config()
|
||||||
|
|
||||||
|
return config, pixel_values, pixel_values_videos
|
||||||
|
|
||||||
|
def prepare_config_and_inputs_for_common(self):
|
||||||
|
config, pixel_values, pixel_values_videos = self.prepare_config_and_inputs()
|
||||||
|
input_ids = ids_tensor([self.batch_size, self.seq_length], config.text_config.vocab_size - 2) + 2
|
||||||
|
# make attention mask left-padded to avoid issues with "model has no attribute padding_side"
|
||||||
|
attention_mask = torch.ones(input_ids.shape, dtype=torch.long).to(torch_device)
|
||||||
|
attention_mask[:, :1] = 0
|
||||||
|
# we are giving 3 images and videos let's make sure we pass in 3 special tokens
|
||||||
|
input_ids[:, 1] = config.image_token_index
|
||||||
|
input_ids[:, 2] = config.video_token_index
|
||||||
|
labels = torch.zeros((self.batch_size, self.seq_length), dtype=torch.long, device=torch_device)
|
||||||
|
# maskout where the image/video token is
|
||||||
|
labels[:, 1] == self.ignore_index
|
||||||
|
labels[:, 2] == self.ignore_index
|
||||||
|
inputs_dict = {
|
||||||
|
"pixel_values": pixel_values,
|
||||||
|
"pixel_values_videos": pixel_values_videos,
|
||||||
|
"image_sizes": torch.tensor(
|
||||||
|
[[self.vision_config["image_size"], self.vision_config["image_size"]]] * self.batch_size
|
||||||
|
),
|
||||||
|
"input_ids": input_ids,
|
||||||
|
"attention_mask": attention_mask,
|
||||||
|
"labels": labels,
|
||||||
|
}
|
||||||
|
return config, inputs_dict
|
||||||
|
|
||||||
|
def create_and_check_llava_next_video_model_fp16_forward(
|
||||||
|
self, config, input_ids, pixel_values, pixel_values_videos, attention_mask, image_sizes
|
||||||
|
):
|
||||||
|
model = LlavaNextVideoForConditionalGeneration(config=config)
|
||||||
|
model.to(torch_device)
|
||||||
|
model.half()
|
||||||
|
model.eval()
|
||||||
|
logits = model(
|
||||||
|
input_ids=input_ids,
|
||||||
|
attention_mask=attention_mask,
|
||||||
|
image_sizes=image_sizes,
|
||||||
|
pixel_values=pixel_values.to(torch.bfloat16),
|
||||||
|
pixel_values_videos=pixel_values_videos.to(torch.bfloat16),
|
||||||
|
return_dict=True,
|
||||||
|
)["logits"]
|
||||||
|
self.parent.assertFalse(torch.isnan(logits).any().item())
|
||||||
|
|
||||||
|
def create_and_check_llava_next_video_model_fp16_autocast_forward(
|
||||||
|
self, config, input_ids, pixel_values, pixel_values_videos, attention_mask, image_sizes
|
||||||
|
):
|
||||||
|
config.torch_dtype = torch.float16
|
||||||
|
model = LlavaNextVideoForConditionalGeneration(config=config)
|
||||||
|
model.to(torch_device)
|
||||||
|
model.eval()
|
||||||
|
with torch.autocast(device_type="cuda", dtype=torch.float16):
|
||||||
|
logits = model(
|
||||||
|
input_ids=input_ids,
|
||||||
|
attention_mask=attention_mask,
|
||||||
|
image_sizes=image_sizes,
|
||||||
|
pixel_values=pixel_values.to(torch.bfloat16),
|
||||||
|
pixel_values_videos=pixel_values_videos.to(torch.bfloat16),
|
||||||
|
return_dict=True,
|
||||||
|
)["logits"]
|
||||||
|
self.parent.assertFalse(torch.isnan(logits).any().item())
|
||||||
|
|
||||||
|
|
||||||
|
@require_torch
|
||||||
|
class LlavaNextVideoForConditionalGenerationModelTest(ModelTesterMixin, GenerationTesterMixin, unittest.TestCase):
|
||||||
|
"""
|
||||||
|
Model tester for `LlavaNextVideoForConditionalGeneration`.
|
||||||
|
"""
|
||||||
|
|
||||||
|
all_model_classes = (LlavaNextVideoForConditionalGeneration,) if is_torch_available() else ()
|
||||||
|
test_pruning = False
|
||||||
|
test_head_masking = False
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
self.model_tester = LlavaNextVideoVisionText2TextModelTester(self)
|
||||||
|
self.config_tester = ConfigTester(self, config_class=LlavaNextVideoConfig, has_text_modality=False)
|
||||||
|
|
||||||
|
def test_initialization(self):
|
||||||
|
config, inputs_dict = self.model_tester.prepare_config_and_inputs_for_common()
|
||||||
|
|
||||||
|
configs_no_init = _config_zero_init(config)
|
||||||
|
for model_class in self.all_model_classes:
|
||||||
|
model = model_class(config=configs_no_init)
|
||||||
|
for name, param in model.named_parameters():
|
||||||
|
if "image_newline" in name:
|
||||||
|
continue
|
||||||
|
elif param.requires_grad:
|
||||||
|
self.assertIn(
|
||||||
|
((param.data.mean() * 1e9).round() / 1e9).item(),
|
||||||
|
[0.0, 1.0],
|
||||||
|
msg=f"Parameter {name} of model {model_class} seems not properly initialized",
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_inputs_embeds(self):
|
||||||
|
# overwrite because llava can't support both inputs_embeds and pixel values at ipnut
|
||||||
|
config, inputs_dict = self.model_tester.prepare_config_and_inputs_for_common()
|
||||||
|
|
||||||
|
for model_class in self.all_model_classes:
|
||||||
|
model = model_class(config)
|
||||||
|
model.to(torch_device)
|
||||||
|
model.eval()
|
||||||
|
|
||||||
|
inputs = self._prepare_for_class(inputs_dict, model_class)
|
||||||
|
|
||||||
|
input_ids = inputs["input_ids"]
|
||||||
|
del inputs["input_ids"]
|
||||||
|
del inputs["pixel_values"]
|
||||||
|
del inputs["pixel_values_videos"]
|
||||||
|
|
||||||
|
wte = model.get_input_embeddings()
|
||||||
|
inputs["inputs_embeds"] = wte(input_ids)
|
||||||
|
|
||||||
|
with torch.no_grad():
|
||||||
|
model(**inputs)
|
||||||
|
|
||||||
|
@unittest.skip(
|
||||||
|
reason="This architecure seem to not compute gradients properly when using GC, check: https://github.com/huggingface/transformers/pull/27124"
|
||||||
|
)
|
||||||
|
def test_training_gradient_checkpointing(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@unittest.skip(
|
||||||
|
reason="This architecure seem to not compute gradients properly when using GC, check: https://github.com/huggingface/transformers/pull/27124"
|
||||||
|
)
|
||||||
|
def test_training_gradient_checkpointing_use_reentrant(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@unittest.skip(
|
||||||
|
reason="This architecure seem to not compute gradients properly when using GC, check: https://github.com/huggingface/transformers/pull/27124"
|
||||||
|
)
|
||||||
|
def test_training_gradient_checkpointing_use_reentrant_false(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@unittest.skip(reason="Feedforward chunking is not yet supported")
|
||||||
|
def test_feed_forward_chunking(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@unittest.skip(reason="CPU offload is not yet supported")
|
||||||
|
def test_cpu_offload(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@unittest.skip(
|
||||||
|
reason="Compile not yet supported because in LLava models (https://github.com/huggingface/transformers/issues/29891)"
|
||||||
|
)
|
||||||
|
def test_sdpa_can_compile_dynamic(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@unittest.skip(
|
||||||
|
reason="Compile not yet supported because in LLava models (https://github.com/huggingface/transformers/issues/29891)"
|
||||||
|
)
|
||||||
|
def test_sdpa_can_dispatch_on_flash(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@require_torch
|
||||||
|
class LlavaNextVideoForConditionalGenerationIntegrationTest(unittest.TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
self.processor = AutoProcessor.from_pretrained("llava-hf/LLaVA-NeXT-Video-7B-hf")
|
||||||
|
image_file = hf_hub_download(
|
||||||
|
repo_id="raushan-testing-hf/images_test", filename="llava_v1_5_radar.jpg", repo_type="dataset"
|
||||||
|
)
|
||||||
|
video_file = hf_hub_download(
|
||||||
|
repo_id="raushan-testing-hf/videos-test", filename="video_demo.npy", repo_type="dataset"
|
||||||
|
)
|
||||||
|
self.image = Image.open(image_file)
|
||||||
|
self.video = np.load(video_file)
|
||||||
|
self.prompt_image = "USER: <image>\nWhat is shown in this image? ASSISTANT:"
|
||||||
|
self.prompt_video = "USER: <video>\nWhy is this video funny? ASSISTANT:"
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
gc.collect()
|
||||||
|
torch.cuda.empty_cache()
|
||||||
|
|
||||||
|
@slow
|
||||||
|
@require_bitsandbytes
|
||||||
|
def test_small_model_integration_test(self):
|
||||||
|
model = LlavaNextVideoForConditionalGeneration.from_pretrained(
|
||||||
|
"llava-hf/LLaVA-NeXT-Video-7B-hf", load_in_4bit=True, cache_dir="./"
|
||||||
|
)
|
||||||
|
|
||||||
|
inputs = self.processor(self.prompt_video, videos=self.video, return_tensors="pt")
|
||||||
|
expected_input_ids = [
|
||||||
|
1,
|
||||||
|
3148,
|
||||||
|
1001,
|
||||||
|
29901,
|
||||||
|
29871,
|
||||||
|
32000,
|
||||||
|
13,
|
||||||
|
11008,
|
||||||
|
338,
|
||||||
|
445,
|
||||||
|
4863,
|
||||||
|
2090,
|
||||||
|
1460,
|
||||||
|
29973,
|
||||||
|
319,
|
||||||
|
1799,
|
||||||
|
9047,
|
||||||
|
13566,
|
||||||
|
29901,
|
||||||
|
]
|
||||||
|
self.assertListEqual(expected_input_ids, inputs.input_ids[0].tolist())
|
||||||
|
|
||||||
|
# verify single forward pass
|
||||||
|
inputs = inputs.to(torch_device)
|
||||||
|
with torch.no_grad():
|
||||||
|
output = model(**inputs)
|
||||||
|
|
||||||
|
# verify generation
|
||||||
|
output = model.generate(**inputs, do_sample=False, max_new_tokens=40)
|
||||||
|
EXPECTED_DECODED_TEXT = 'USER: \nWhy is this video funny? ASSISTANT: The humor in this video comes from the unexpected and exaggerated reactions of the child to the book. The child appears to be reading a book, but instead of a calm and focused reading experience' # fmt: skip
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
self.processor.decode(output[0], skip_special_tokens=True),
|
||||||
|
EXPECTED_DECODED_TEXT,
|
||||||
|
)
|
||||||
|
|
||||||
|
@slow
|
||||||
|
@require_bitsandbytes
|
||||||
|
def test_small_model_integration_test_batch(self):
|
||||||
|
model = LlavaNextVideoForConditionalGeneration.from_pretrained(
|
||||||
|
"llava-hf/LLaVA-NeXT-Video-7B-hf", load_in_4bit=True, cache_dir="./"
|
||||||
|
)
|
||||||
|
|
||||||
|
inputs = self.processor(
|
||||||
|
[self.prompt_video, self.prompt_video],
|
||||||
|
videos=[self.video, self.video],
|
||||||
|
return_tensors="pt",
|
||||||
|
padding=True,
|
||||||
|
).to(torch_device)
|
||||||
|
|
||||||
|
output = model.generate(**inputs, do_sample=False, max_new_tokens=20)
|
||||||
|
|
||||||
|
EXPECTED_DECODED_TEXT = ['USER: \nWhy is this video funny? ASSISTANT: The humor in this video comes from the unexpected and exaggerated reactions of the child to the', 'USER: \nWhy is this video funny? ASSISTANT: The humor in this video comes from the unexpected and exaggerated reactions of the child to the'] # fmt: skip
|
||||||
|
self.assertEqual(
|
||||||
|
self.processor.batch_decode(output, skip_special_tokens=True),
|
||||||
|
EXPECTED_DECODED_TEXT,
|
||||||
|
)
|
||||||
|
|
||||||
|
@slow
|
||||||
|
@require_bitsandbytes
|
||||||
|
def test_small_model_integration_test_batch_different_vision_types(self):
|
||||||
|
model = LlavaNextVideoForConditionalGeneration.from_pretrained(
|
||||||
|
"llava-hf/LLaVA-NeXT-Video-7B-hf",
|
||||||
|
load_in_4bit=True,
|
||||||
|
cache_dir="./",
|
||||||
|
)
|
||||||
|
|
||||||
|
inputs = self.processor(
|
||||||
|
[self.prompt_image, self.prompt_video],
|
||||||
|
images=self.image,
|
||||||
|
videos=self.video,
|
||||||
|
return_tensors="pt",
|
||||||
|
padding=True,
|
||||||
|
).to(torch_device)
|
||||||
|
|
||||||
|
# check loss when labels are passed
|
||||||
|
inputs["labels"] = inputs["input_ids"].clone()
|
||||||
|
with torch.no_grad():
|
||||||
|
output = model(**inputs)
|
||||||
|
self.assertTrue(output.loss is not None)
|
||||||
|
|
||||||
|
# verify generation
|
||||||
|
output = model.generate(**inputs, do_sample=False, max_new_tokens=50)
|
||||||
|
EXPECTED_DECODED_TEXT = 'USER: \nWhat is shown in this image? ASSISTANT: The image appears to be a graphical representation of a benchmark test for a machine learning model. It shows the performance of various models on a task, with the x-axis representing the number of parameters (measured in millions) and the y' # fmt: skip
|
||||||
|
self.assertEqual(self.processor.decode(output[0], skip_special_tokens=True), EXPECTED_DECODED_TEXT)
|
||||||
|
|
||||||
|
@slow
|
||||||
|
@require_bitsandbytes
|
||||||
|
def test_small_model_integration_test_batch_matches_single(self):
|
||||||
|
model = LlavaNextVideoForConditionalGeneration.from_pretrained(
|
||||||
|
"llava-hf/LLaVA-NeXT-Video-7B-hf", load_in_4bit=True, cache_dir="./"
|
||||||
|
)
|
||||||
|
|
||||||
|
inputs_batched = self.processor(
|
||||||
|
[self.prompt_video, self.prompt_image],
|
||||||
|
images=[self.image],
|
||||||
|
videos=[self.video],
|
||||||
|
return_tensors="pt",
|
||||||
|
padding=True,
|
||||||
|
).to(torch_device)
|
||||||
|
|
||||||
|
inputs_single = self.processor(self.prompt_video, videos=[self.video], return_tensors="pt").to(torch_device)
|
||||||
|
|
||||||
|
# verify generation
|
||||||
|
output_batched = model.generate(**inputs_batched, do_sample=False, max_new_tokens=50)
|
||||||
|
output_single = model.generate(**inputs_single, do_sample=False, max_new_tokens=50)
|
||||||
|
self.assertEqual(
|
||||||
|
self.processor.decode(output_batched[0], skip_special_tokens=True),
|
||||||
|
self.processor.decode(output_single[0], skip_special_tokens=True),
|
||||||
|
)
|
||||||
@@ -223,6 +223,14 @@ class VideoLlavaForConditionalGenerationModelTest(ModelTesterMixin, GenerationTe
|
|||||||
def test_training_gradient_checkpointing_use_reentrant_false(self):
|
def test_training_gradient_checkpointing_use_reentrant_false(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@unittest.skip(reason="Pass because video-LLava requires `attention_mask is not None`")
|
||||||
|
def test_sdpa_can_compile_dynamic(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@unittest.skip(reason="Pass because video-LLava requires `attention_mask is not None`")
|
||||||
|
def test_sdpa_can_dispatch_on_flash(self):
|
||||||
|
pass
|
||||||
|
|
||||||
def test_mixed_input(self):
|
def test_mixed_input(self):
|
||||||
config, inputs = self.model_tester.prepare_config_and_inputs_for_common()
|
config, inputs = self.model_tester.prepare_config_and_inputs_for_common()
|
||||||
for model_class in self.all_model_classes:
|
for model_class in self.all_model_classes:
|
||||||
|
|||||||
@@ -185,6 +185,14 @@ class VipLlavaForConditionalGenerationModelTest(ModelTesterMixin, unittest.TestC
|
|||||||
def test_training_gradient_checkpointing_use_reentrant_false(self):
|
def test_training_gradient_checkpointing_use_reentrant_false(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@unittest.skip(reason="Compile not yet supported because it is not yet supported in LLava")
|
||||||
|
def test_sdpa_can_compile_dynamic(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@unittest.skip(reason="Compile not yet supported because in LLava models")
|
||||||
|
def test_sdpa_can_dispatch_on_flash(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
@require_torch
|
@require_torch
|
||||||
class VipLlavaForConditionalGenerationIntegrationTest(unittest.TestCase):
|
class VipLlavaForConditionalGenerationIntegrationTest(unittest.TestCase):
|
||||||
|
|||||||
@@ -333,7 +333,10 @@ def replace_call_to_super(class_finder: ClassFinder, updated_node: cst.ClassDef,
|
|||||||
original_methods = {f.name.value if hasattr(f, "name") else f: f for f in original_node.body.body}
|
original_methods = {f.name.value if hasattr(f, "name") else f: f for f in original_node.body.body}
|
||||||
updated_methods = {f.name.value if hasattr(f, "name") else f: f for f in updated_node.body.body}
|
updated_methods = {f.name.value if hasattr(f, "name") else f: f for f in updated_node.body.body}
|
||||||
end_meth = []
|
end_meth = []
|
||||||
for name, func in original_methods.items():
|
|
||||||
|
# Iterate directly from node.body as there can be property/setters with same names which are overwritten when we use a dict
|
||||||
|
for func in original_node.body.body:
|
||||||
|
name = func.name.value if hasattr(func, "name") else func
|
||||||
if name in updated_methods and updated_methods[name] is not None:
|
if name in updated_methods and updated_methods[name] is not None:
|
||||||
new_params = updated_methods[name].params
|
new_params = updated_methods[name].params
|
||||||
# Replace the method in the replacement class, preserving decorators
|
# Replace the method in the replacement class, preserving decorators
|
||||||
@@ -347,6 +350,11 @@ def replace_call_to_super(class_finder: ClassFinder, updated_node: cst.ClassDef,
|
|||||||
func = func.with_changes(body=updated_methods[name].body, params=new_params)
|
func = func.with_changes(body=updated_methods[name].body, params=new_params)
|
||||||
end_meth.append(func)
|
end_meth.append(func)
|
||||||
|
|
||||||
|
# Port new methods that are defined only in diff-file and append at the end
|
||||||
|
for name, func in updated_methods.items():
|
||||||
|
if name not in original_methods and func is not None and isinstance(func, cst.FunctionDef):
|
||||||
|
end_meth.append(func)
|
||||||
|
|
||||||
result_node = original_node.with_changes(body=cst.IndentedBlock(body=end_meth))
|
result_node = original_node.with_changes(body=cst.IndentedBlock(body=end_meth))
|
||||||
temp_module = cst.Module(body=[result_node])
|
temp_module = cst.Module(body=[result_node])
|
||||||
new_module = MetadataWrapper(temp_module)
|
new_module = MetadataWrapper(temp_module)
|
||||||
@@ -454,7 +462,7 @@ class DiffConverterTransformer(CSTTransformer):
|
|||||||
)
|
)
|
||||||
|
|
||||||
super_file_name = self.imported_mapping[super_class] # we need to get the parsed tree
|
super_file_name = self.imported_mapping[super_class] # we need to get the parsed tree
|
||||||
model_name = re.search(r"_(\S*)", super_file_name)
|
model_name = re.search(r"models\.\w*?\.\w*?_(\S*)", super_file_name)
|
||||||
if model_name:
|
if model_name:
|
||||||
model_name = model_name.groups()[0]
|
model_name = model_name.groups()[0]
|
||||||
else:
|
else:
|
||||||
|
|||||||
Reference in New Issue
Block a user