Image-Text-to-Text
Transformers
PyTorch
English
doubutsu_next
conversational
custom_code
Inference Endpoints
qtnx commited on
Commit
ec5b76c
1 Parent(s): 6822c0a

Upload folder using huggingface_hub

Browse files
added_tokens.json ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ {
2
+ "<|endoftext|>": 151643,
3
+ "<|im_end|>": 151645,
4
+ "<|im_start|>": 151644
5
+ }
config.json ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "auto_map": {
3
+ "AutoConfig": "configuration_doubutsu_next.DoubutsuNextConfig",
4
+ "AutoModelForCausalLM": "modeling_doubutsu_next.DoubutsuNext"
5
+ },
6
+ "model_type": "doubutsu_next",
7
+ "text_config": {
8
+ "_name_or_path": "Qwen/Qwen2-1.5B-Instruct",
9
+ "architectures": [
10
+ "Qwen2ForCausalLM"
11
+ ],
12
+ "bos_token_id": 151643,
13
+ "eos_token_id": 151645,
14
+ "hidden_size": 1536,
15
+ "intermediate_size": 8960,
16
+ "max_length": 32768,
17
+ "model_type": "qwen2",
18
+ "num_attention_heads": 12,
19
+ "num_hidden_layers": 28,
20
+ "num_key_value_heads": 2,
21
+ "rope_theta": 1000000.0,
22
+ "sliding_window": 32768,
23
+ "tie_word_embeddings": true,
24
+ "torch_dtype": "bfloat16"
25
+ },
26
+ "transformers_version": "4.40.1",
27
+ "vision_config": {
28
+ "_name_or_path": "google/siglip-so400m-patch14-384",
29
+ "hidden_size": 1152,
30
+ "image_size": 384,
31
+ "intermediate_size": 4304,
32
+ "model_type": "siglip_vision_model",
33
+ "num_attention_heads": 16,
34
+ "num_hidden_layers": 27,
35
+ "patch_size": 14
36
+ }
37
+ }
configuration_doubutsu_next.py ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from transformers import PretrainedConfig, Qwen2Config, SiglipVisionConfig
2
+
3
+
4
+ class DoubutsuNextConfig(PretrainedConfig):
5
+ model_type = "doubutsu_next"
6
+
7
+ def __init__(self, **kwargs):
8
+ self.text_config = Qwen2Config(
9
+ **kwargs.pop(
10
+ "text_config",
11
+ {},
12
+ ),
13
+ )
14
+ self.vision_config = SiglipVisionConfig(**kwargs.pop("vision_config", {}))
15
+ super().__init__(**kwargs)
merges.txt ADDED
The diff for this file is too large to render. See raw diff
 
modeling_doubutsu_next.py ADDED
@@ -0,0 +1,151 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ import torch.nn as nn
3
+ from transformers import (
4
+ PreTrainedModel,
5
+ AutoModelForCausalLM,
6
+ AutoModel,
7
+ SiglipImageProcessor,
8
+ )
9
+ from .configuration_doubutsu_next import DoubutsuNextConfig
10
+ from .utils import slice_anyres_image
11
+
12
+
13
+ class ProjectionModule(nn.Module):
14
+ def __init__(self, mm_hidden_size=1152, hidden_size=1536):
15
+ super(ProjectionModule, self).__init__()
16
+
17
+ self.model = nn.Sequential(
18
+ nn.Linear(mm_hidden_size, hidden_size),
19
+ nn.GELU(),
20
+ nn.Linear(hidden_size, hidden_size),
21
+ )
22
+
23
+ def forward(self, x):
24
+ return self.model(x)
25
+
26
+
27
+ class DoubutsuNext(PreTrainedModel):
28
+ config_class = DoubutsuNextConfig
29
+
30
+ def __init__(self, config):
31
+ super().__init__(config)
32
+
33
+ self.vision_model = AutoModel.from_config(self.config.vision_config)
34
+ self.text_model = AutoModelForCausalLM.from_config(self.config.text_config)
35
+ self.processor = SiglipImageProcessor()
36
+ self.mm_projector = ProjectionModule(
37
+ mm_hidden_size=config.vision_config.hidden_size,
38
+ hidden_size=config.text_config.hidden_size,
39
+ )
40
+
41
+ @property
42
+ def device(self):
43
+ return self.text_model.device
44
+
45
+ def encode_image(self, image):
46
+ image_patches = slice_anyres_image(image)
47
+
48
+ encoded_patches = []
49
+ for patch in image_patches:
50
+ patch = patch.convert("RGB")
51
+ processed_patch = self.processor(
52
+ images=patch,
53
+ return_tensors="pt",
54
+ do_resize=True,
55
+ size={"height": 378, "width": 378},
56
+ )["pixel_values"].to(
57
+ device=self.vision_model.device, dtype=self.vision_model.dtype
58
+ )
59
+ with torch.no_grad():
60
+ encoded_patch = self.vision_model(
61
+ processed_patch, output_hidden_states=True
62
+ ).hidden_states[-2]
63
+ encoded_patches.append(encoded_patch)
64
+
65
+ return torch.cat(
66
+ encoded_patches, dim=1
67
+ ) # Concatenate along the sequence dimension
68
+
69
+ def input_embeds(self, prompt, image_embeds, tokenizer):
70
+ def _tokenize(txt):
71
+ return tokenizer(
72
+ txt, return_tensors="pt", add_special_tokens=False
73
+ ).input_ids.to(self.device)
74
+
75
+ text_emb = self.text_model.get_input_embeddings()
76
+ embeds = []
77
+ tokenized_prompt = _tokenize(prompt)
78
+
79
+ # Add BOS token if it exists and isn't already at the start of the prompt
80
+ if tokenizer.bos_token_id is not None:
81
+ if tokenized_prompt[0][0] == tokenizer.bos_token_id:
82
+ tokenized_prompt = tokenized_prompt[:, 1:] # Remove existing BOS
83
+ embeds.append(
84
+ text_emb(torch.tensor([[tokenizer.bos_token_id]], device=self.device))
85
+ )
86
+
87
+ # Add image embeds
88
+ projected_image_embeds = self.mm_projector(image_embeds.to(self.device))
89
+ embeds.append(projected_image_embeds)
90
+
91
+ # Add text embeds
92
+ embeds.append(text_emb(tokenized_prompt))
93
+
94
+ return torch.cat(embeds, dim=1)
95
+
96
+ def get_input_embeddings(self):
97
+ return self.text_model.get_input_embeddings()
98
+
99
+ def generate(
100
+ self,
101
+ image_embeds,
102
+ prompt,
103
+ tokenizer,
104
+ max_new_tokens=128,
105
+ temperature=0.1,
106
+ **kwargs,
107
+ ):
108
+ generate_config = {
109
+ "eos_token_id": tokenizer.eos_token_id,
110
+ "bos_token_id": tokenizer.bos_token_id,
111
+ "pad_token_id": tokenizer.pad_token_id,
112
+ "max_new_tokens": max_new_tokens,
113
+ "temperature": temperature,
114
+ **kwargs,
115
+ }
116
+
117
+ with torch.no_grad():
118
+ inputs_embeds = self.input_embeds(prompt, image_embeds, tokenizer)
119
+ output_ids = self.text_model.generate(
120
+ inputs_embeds=inputs_embeds,
121
+ do_sample=True,
122
+ **generate_config,
123
+ )
124
+ return tokenizer.batch_decode(output_ids, skip_special_tokens=True)
125
+
126
+ def answer_question(self, image, question, tokenizer, **kwargs):
127
+ image_embeds = self.encode_image(image)
128
+
129
+ chat = [
130
+ {
131
+ "role": "system",
132
+ "content": "You are a helpful AI assistant that can see images and answer questions about them.",
133
+ },
134
+ {"role": "user", "content": question},
135
+ ]
136
+ prompt = tokenizer.apply_chat_template(
137
+ chat, tokenize=False, add_generation_prompt=True
138
+ )
139
+
140
+ # Generate the answer
141
+ with torch.no_grad():
142
+ output = self.generate(
143
+ image_embeds=image_embeds,
144
+ prompt=prompt,
145
+ tokenizer=tokenizer,
146
+ **kwargs,
147
+ )[0]
148
+
149
+ # Clean and return the answer
150
+ cleaned_answer = output.strip()
151
+ return cleaned_answer
pytorch_model.bin ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:c10024a70443cf96a47827579df1f55adcdaef649c9e9c1dc33481f64573cb44
3
+ size 3952463074
special_tokens_map.json ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "additional_special_tokens": [
3
+ "<|im_start|>",
4
+ "<|im_end|>"
5
+ ],
6
+ "eos_token": {
7
+ "content": "<|im_end|>",
8
+ "lstrip": false,
9
+ "normalized": false,
10
+ "rstrip": false,
11
+ "single_word": false
12
+ },
13
+ "pad_token": {
14
+ "content": "<|endoftext|>",
15
+ "lstrip": false,
16
+ "normalized": false,
17
+ "rstrip": false,
18
+ "single_word": false
19
+ }
20
+ }
tokenizer.json ADDED
The diff for this file is too large to render. See raw diff
 
tokenizer_config.json ADDED
@@ -0,0 +1,43 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "add_prefix_space": false,
3
+ "added_tokens_decoder": {
4
+ "151643": {
5
+ "content": "<|endoftext|>",
6
+ "lstrip": false,
7
+ "normalized": false,
8
+ "rstrip": false,
9
+ "single_word": false,
10
+ "special": true
11
+ },
12
+ "151644": {
13
+ "content": "<|im_start|>",
14
+ "lstrip": false,
15
+ "normalized": false,
16
+ "rstrip": false,
17
+ "single_word": false,
18
+ "special": true
19
+ },
20
+ "151645": {
21
+ "content": "<|im_end|>",
22
+ "lstrip": false,
23
+ "normalized": false,
24
+ "rstrip": false,
25
+ "single_word": false,
26
+ "special": true
27
+ }
28
+ },
29
+ "additional_special_tokens": [
30
+ "<|im_start|>",
31
+ "<|im_end|>"
32
+ ],
33
+ "bos_token": null,
34
+ "chat_template": "{% for message in messages %}{% if loop.first and messages[0]['role'] != 'system' %}{{ '<|im_start|>system\nYou are a helpful assistant.<|im_end|>\n' }}{% endif %}{{'<|im_start|>' + message['role'] + '\n' + message['content'] + '<|im_end|>' + '\n'}}{% endfor %}{% if add_generation_prompt %}{{ '<|im_start|>assistant\n' }}{% endif %}",
35
+ "clean_up_tokenization_spaces": false,
36
+ "eos_token": "<|im_end|>",
37
+ "errors": "replace",
38
+ "model_max_length": 32768,
39
+ "pad_token": "<|endoftext|>",
40
+ "split_special_tokens": false,
41
+ "tokenizer_class": "Qwen2Tokenizer",
42
+ "unk_token": null
43
+ }
utils.py ADDED
@@ -0,0 +1,127 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import List, Tuple
2
+ from PIL import Image
3
+ import math
4
+
5
+
6
+ def generate_grid_configurations(size: int) -> List[Tuple[int, int]]:
7
+ grid_configs = [
8
+ (2 * size, 2 * size),
9
+ (1 * size, 2 * size),
10
+ (1 * size, 3 * size),
11
+ (1 * size, 4 * size),
12
+ (4 * size, 1 * size),
13
+ (3 * size, 1 * size),
14
+ (2 * size, 1 * size),
15
+ ]
16
+ return grid_configs
17
+
18
+
19
+ def select_best_resolution(original_size, possible_resolutions):
20
+ """
21
+ Selects the best resolution from a list of possible resolutions based on the original size.
22
+
23
+ Args:
24
+ original_size (tuple): The original size of the image in the format (width, height).
25
+ possible_resolutions (list): A list of possible resolutions in the format [(width1, height1), (width2, height2), ...].
26
+
27
+ Returns:
28
+ tuple: The best fit resolution in the format (width, height).
29
+ """
30
+ original_width, original_height = original_size
31
+ best_fit = None
32
+ max_effective_resolution = 0
33
+ min_wasted_resolution = float("inf")
34
+
35
+ for width, height in possible_resolutions:
36
+ scale = min(width / original_width, height / original_height)
37
+ downscaled_width, downscaled_height = (
38
+ int(original_width * scale),
39
+ int(original_height * scale),
40
+ )
41
+ effective_resolution = min(
42
+ downscaled_width * downscaled_height, original_width * original_height
43
+ )
44
+ wasted_resolution = (width * height) - effective_resolution
45
+
46
+ if effective_resolution > max_effective_resolution or (
47
+ effective_resolution == max_effective_resolution
48
+ and wasted_resolution < min_wasted_resolution
49
+ ):
50
+ max_effective_resolution = effective_resolution
51
+ min_wasted_resolution = wasted_resolution
52
+ best_fit = (width, height)
53
+
54
+ return best_fit
55
+
56
+
57
+ def resize_and_pad_image(image, target_resolution):
58
+ """
59
+ Resize and pad an image to a target resolution while maintaining aspect ratio.
60
+
61
+ Args:
62
+ image (PIL.Image.Image): The input image.
63
+ target_resolution (tuple): The target resolution (width, height) of the image.
64
+
65
+ Returns:
66
+ PIL.Image.Image: The resized and padded image.
67
+ """
68
+ original_width, original_height = image.size
69
+ target_width, target_height = target_resolution
70
+
71
+ scale_w = target_width / original_width
72
+ scale_h = target_height / original_height
73
+
74
+ if scale_w < scale_h:
75
+ new_width = target_width
76
+ new_height = min(math.ceil(original_height * scale_w), target_height)
77
+ else:
78
+ new_height = target_height
79
+ new_width = min(math.ceil(original_width * scale_h), target_width)
80
+
81
+ # Resize the image
82
+ resized_image = image.resize((new_width, new_height))
83
+
84
+ new_image = Image.new("RGB", (target_width, target_height), (0, 0, 0))
85
+ paste_x = (target_width - new_width) // 2
86
+ paste_y = (target_height - new_height) // 2
87
+ new_image.paste(resized_image, (paste_x, paste_y))
88
+
89
+ return new_image
90
+
91
+
92
+ def divide_to_patches(image, patch_size):
93
+ """
94
+ Divides an image into patches of a specified size.
95
+
96
+ Args:
97
+ image (PIL.Image.Image): The input image.
98
+ patch_size (int): The size of each patch.
99
+
100
+ Returns:
101
+ list: A list of PIL.Image.Image objects representing the patches.
102
+ """
103
+ patches = []
104
+ width, height = image.size
105
+ for i in range(0, height, patch_size):
106
+ for j in range(0, width, patch_size):
107
+ box = (j, i, j + patch_size, i + patch_size)
108
+ patch = image.crop(box)
109
+ patches.append(patch)
110
+
111
+ return patches
112
+
113
+
114
+ def slice_anyres_image(image, patch_size=378):
115
+ grid_pinpoints = generate_grid_configurations(patch_size)
116
+
117
+ best_resolution = select_best_resolution(image.size, grid_pinpoints)
118
+ image_padded = resize_and_pad_image(image, best_resolution)
119
+
120
+ patches = divide_to_patches(image_padded, patch_size)
121
+
122
+ size = {"shortest_edge": patch_size}
123
+ image_original_resize = image.resize((size["shortest_edge"], size["shortest_edge"]))
124
+
125
+ image_patches = [image_original_resize] + patches
126
+
127
+ return image_patches
vocab.json ADDED
The diff for this file is too large to render. See raw diff