Views
No views yet
| model name | LLM | Vision | Max Length | HF Link |
|---|---|---|---|---|
| Eagle2-1B | Qwen2.5-0.5B-Instruct | Siglip | 16K | 🤗 link |
| Eagle2-2B | Qwen2.5-1.5B-Instruct | Siglip | 16K | 🤗 link |
| Eagle2-9B | Qwen2.5-7B-Instruct | Siglip+ConvNext | 16K | 🤗 link |
| Benchmark | LLaVa-One-Vision-0.5B | InternVL2-1B | InternVL2.5-1B | Qwen2-VL-2B | Eagle2-1B |
|---|---|---|---|---|---|
| DocVQAtest | 70.0 | 81.7 | 84.8 | 90.1 | 81.8 |
| ChartQAtest | 61.4 | 72.9 | 75.9 | 73.0 | 77.0 |
| InfoVQAtest | 41.8 | 50.9 | 56.0 | 65.5 | 54.8 |
| TextVQAval | - | 70.0 | 72.0 | 79.7 | 76.6 |
| OCRBench | 565 | 754 | 785 | 809 | 767 |
| MMEsum | 1438.0 | 1794.4 | 1950.5 | 1872.0 | 1790.2 |
| RealWorldQA | 55.6 | 50.3 | 57.5 | 62.6 | 55.4 |
| AI2Dtest | 57.1 | 64.1 | 69.3 | 74.7 | 70.9 |
| MMMUval | 31.4 | 36.7 | 40.9 | 41.1 | 38.8 |
| MMVetGPT-4-Turbo | 32.2 | 32.7 | 48.8 | 49.5 | 40.9 |
| MathVistatestmini | 33.8 | 37.7 | 43.2 | 43.0 | 45.3 |
| MMstar | 37.7 | 45.7 | 50.1 | 48.0 | 48.5 |
1pip install transformers==4.37.2
2pip install flash-attn1
2"""
3A model worker executes the model.
4Copied and modified from https://github.com/OpenGVLab/InternVL/blob/main/streamlit_demo/model_worker.py
5"""
6# Importing torch before transformers can cause `segmentation fault`
7from transformers import AutoModel, AutoTokenizer, TextIteratorStreamer, AutoConfig
8
9import argparse
10import base64
11import json
12import os
13import decord
14import threading
15import time
16from io import BytesIO
17from threading import Thread
18import math
19import requests
20import torch
21import torchvision.transforms as T
22from PIL import Image
23from torchvision.transforms.functional import InterpolationMode
24import numpy as np
25
26
27IMAGENET_MEAN = (0.485, 0.456, 0.406)
28IMAGENET_STD = (0.229, 0.224, 0.225)
29
30SIGLIP_MEAN = (0.5, 0.5, 0.5)
31SIGLIP_STD = (0.5, 0.5, 0.5)
32
33
34def get_seq_frames(total_num_frames, desired_num_frames=-1, stride=-1):
35 """
36 Calculate the indices of frames to extract from a video.
37
38 Parameters:
39 total_num_frames (int): Total number of frames in the video.
40 desired_num_frames (int): Desired number of frames to extract.
41
42 Returns:
43 list: List of indices of frames to extract.
44 """
45
46 assert desired_num_frames > 0 or stride > 0 and not (desired_num_frames > 0 and stride > 0)
47
48 if stride > 0:
49 return list(range(0, total_num_frames, stride))
50
51 # Calculate the size of each segment from which a frame will be extracted
52 seg_size = float(total_num_frames - 1) / desired_num_frames
53
54 seq = []
55 for i in range(desired_num_frames):
56 # Calculate the start and end indices of each segment
57 start = int(np.round(seg_size * i))
58 end = int(np.round(seg_size * (i + 1)))
59
60 # Append the middle index of the segment to the list
61 seq.append((start + end) // 2)
62
63 return seq
64
65def build_video_prompt(meta_list, num_frames, time_position=False):
66 # if time_position is True, the frame_timestamp is used.
67 # 1. pass time_position, 2. use env TIME_POSITION
68 time_position = os.environ.get("TIME_POSITION", time_position)
69 prefix = f"This is a video:\n"
70 for i in range(num_frames):
71 if time_position:
72 frame_txt = f"Frame {i+1} sampled at {meta_list[i]:.2f} seconds: <image>\n"
73 else:
74 frame_txt = f"Frame {i+1}: <image>\n"
75 prefix += frame_txt
76 return prefix
77
78def load_video(video_path, num_frames=64, frame_cache_root=None):
79 if isinstance(video_path, str):
80 video = decord.VideoReader(video_path)
81 elif isinstance(video_path, dict):
82 assert False, 'we not support vidoe: "video_path" as input'
83 fps = video.get_avg_fps()
84 sampled_frames = get_seq_frames(len(video), num_frames)
85 samepld_timestamps = [i / fps for i in sampled_frames]
86 frames = video.get_batch(sampled_frames).asnumpy()
87 images = [Image.fromarray(frame) for frame in frames]
88
89 return images, build_video_prompt(samepld_timestamps, len(images), time_position=True)
90
91def load_image(image):
92 if isinstance(image, str) and os.path.exists(image):
93 return Image.open(image)
94 elif isinstance(image, dict):
95 if 'disk_path' in image:
96 return Image.open(image['disk_path'])
97 elif 'base64' in image:
98 return Image.open(BytesIO(base64.b64decode(image['base64'])))
99 elif 'url' in image:
100 response = requests.get(image['url'])
101 return Image.open(BytesIO(response.content))
102 elif 'bytes' in image:
103 return Image.open(BytesIO(image['bytes']))
104 else:
105 raise ValueError(f'Invalid image: {image}')
106 else:
107 raise ValueError(f'Invalid image: {image}')
108
109def build_transform(input_size, norm_type='imagenet'):
110 if norm_type == 'imagenet':
111 MEAN, STD = IMAGENET_MEAN, IMAGENET_STD
112 elif norm_type == 'siglip':
113 MEAN, STD = SIGLIP_MEAN, SIGLIP_STD
114
115 transform = T.Compose([
116 T.Lambda(lambda img: img.convert('RGB') if img.mode != 'RGB' else img),
117 T.Resize((input_size, input_size), interpolation=InterpolationMode.BICUBIC),
118 T.ToTensor(),
119 T.Normalize(mean=MEAN, std=STD)
120 ])
121 return transform
122
123
124def find_closest_aspect_ratio(aspect_ratio, target_ratios, width, height, image_size):
125 """
126 previous version mainly foucs on ratio.
127 We also consider area ratio here.
128 """
129 best_factor = float('-inf')
130 best_ratio = (1, 1)
131 area = width * height
132 for ratio in target_ratios:
133 target_aspect_ratio = ratio[0] / ratio[1]
134 ratio_diff = abs(aspect_ratio - target_aspect_ratio)
135 area_ratio = (ratio[0]*ratio[1]*image_size*image_size)/ area
136 """
137 new area > 60% of original image area is enough.
138 """
139 factor_based_on_area_n_ratio = min((ratio[0]*ratio[1]*image_size*image_size)/ area, 0.6)* \
140 min(target_aspect_ratio/aspect_ratio, aspect_ratio/target_aspect_ratio)
141
142 if factor_based_on_area_n_ratio > best_factor:
143 best_factor = factor_based_on_area_n_ratio
144 best_ratio = ratio
145
146 return best_ratio
147
148
149def dynamic_preprocess(image, min_num=1, max_num=6, image_size=448, use_thumbnail=False):
150 orig_width, orig_height = image.size
151 aspect_ratio = orig_width / orig_height
152
153 # calculate the existing image aspect ratio
154 target_ratios = set(
155 (i, j) for n in range(min_num, max_num + 1) for i in range(1, n + 1) for j in range(1, n + 1) if
156 i * j <= max_num and i * j >= min_num)
157 target_ratios = sorted(target_ratios, key=lambda x: x[0] * x[1])
158
159 # find the closest aspect ratio to the target
160 target_aspect_ratio = find_closest_aspect_ratio(
161 aspect_ratio, target_ratios, orig_width, orig_height, image_size)
162
163 # calculate the target width and height
164 target_width = image_size * target_aspect_ratio[0]
165 target_height = image_size * target_aspect_ratio[1]
166 blocks = target_aspect_ratio[0] * target_aspect_ratio[1]
167
168 # resize the image
169 resized_img = image.resize((target_width, target_height))
170 processed_images = []
171 for i in range(blocks):
172 box = (
173 (i % (target_width // image_size)) * image_size,
174 (i // (target_width // image_size)) * image_size,
175 ((i % (target_width // image_size)) + 1) * image_size,
176 ((i // (target_width // image_size)) + 1) * image_size
177 )
178 # split the image
179 split_img = resized_img.crop(box)
180 processed_images.append(split_img)
181 assert len(processed_images) == blocks
182 if use_thumbnail and len(processed_images) != 1:
183 thumbnail_img = image.resize((image_size, image_size))
184 processed_images.append(thumbnail_img)
185 return processed_images
186
187def split_model(model_path, device):
188
189 device_map = {}
190 world_size = torch.cuda.device_count()
191 config = AutoConfig.from_pretrained(model_path, trust_remote_code=True)
192 num_layers = config.llm_config.num_hidden_layers
193
194 print('world_size', world_size)
195 num_layers_per_gpu_ = math.floor(num_layers / (world_size - 1))
196 num_layers_per_gpu = [num_layers_per_gpu_] * world_size
197 num_layers_per_gpu[device] = num_layers - num_layers_per_gpu_ * (world_size-1)
198 print(num_layers_per_gpu)
199 layer_cnt = 0
200 for i, num_layer in enumerate(num_layers_per_gpu):
201 for j in range(num_layer):
202 device_map[f'language_model.model.layers.{layer_cnt}'] = i
203 layer_cnt += 1
204 device_map['vision_model'] = device
205 device_map['mlp1'] = device
206 device_map['language_model.model.tok_embeddings'] = device
207 device_map['language_model.model.embed_tokens'] = device
208 device_map['language_model.output'] = device
209 device_map['language_model.model.norm'] = device
210 device_map['language_model.lm_head'] = device
211 device_map['language_model.model.rotary_emb'] = device
212 device_map[f'language_model.model.layers.{num_layers - 1}'] = device
213 return device_map
214
215class ModelWorker:
216 def __init__(self, model_path, model_name,
217 load_8bit, device):
218
219 if model_path.endswith('/'):
220 model_path = model_path[:-1]
221 if model_name is None:
222 model_paths = model_path.split('/')
223 if model_paths[-1].startswith('checkpoint-'):
224 self.model_name = model_paths[-2] + '_' + model_paths[-1]
225 else:
226 self.model_name = model_paths[-1]
227 else:
228 self.model_name = model_name
229
230 print(f'Loading the model {self.model_name}')
231
232 tokenizer = AutoTokenizer.from_pretrained(model_path, trust_remote_code=True, use_fast=False)
233 tokens_to_keep = ['<box>', '</box>', '<ref>', '</ref>']
234 tokenizer.additional_special_tokens = [item for item in tokenizer.additional_special_tokens if item not in tokens_to_keep]
235 self.tokenizer = tokenizer
236 config = AutoConfig.from_pretrained(model_path, trust_remote_code=True)
237 model_type = config.vision_config.model_type
238 self.device = torch.cuda.current_device()
239 if model_type == 'siglip_vision_model':
240 self.norm_type = 'siglip'
241 elif model_type == 'MOB':
242 self.norm_type = 'siglip'
243 else:
244 self.norm_type = 'imagenet'
245
246 if any(x in model_path.lower() for x in ['34b']):
247 device_map = split_model(model_path, self.device)
248 else:
249 device_map = None
250
251 if device_map is not None:
252 self.model = AutoModel.from_pretrained(model_path, torch_dtype=torch.bfloat16,
253 low_cpu_mem_usage=True,
254 device_map=device_map,
255 trust_remote_code=True,
256 load_in_8bit=load_8bit).eval()
257 else:
258 self.model = AutoModel.from_pretrained(model_path, torch_dtype=torch.bfloat16,
259 trust_remote_code=True,
260 load_in_8bit=load_8bit).eval()
261
262 if not load_8bit and device_map is None:
263 self.model = self.model.to(device)
264 self.load_8bit = load_8bit
265
266 self.model_path = model_path
267 self.image_size = self.model.config.force_image_size
268 self.context_len = tokenizer.model_max_length
269 self.per_tile_len = 256
270
271 def reload_model(self):
272 del self.model
273 torch.cuda.empty_cache()
274 if self.device == 'auto':
275 os.environ['CUDA_LAUNCH_BLOCKING'] = '1'
276 # This can make distributed deployment work properly
277 self.model = AutoModel.from_pretrained(
278 self.model_path,
279 load_in_8bit=self.load_8bit,
280 torch_dtype=torch.bfloat16,
281 device_map=self.device_map,
282 trust_remote_code=True).eval()
283 else:
284 self.model = AutoModel.from_pretrained(
285 self.model_path,
286 load_in_8bit=self.load_8bit,
287 torch_dtype=torch.bfloat16,
288 trust_remote_code=True).eval()
289 if not self.load_8bit and not self.device == 'auto':
290 self.model = self.model.cuda()
291
292 @torch.inference_mode()
293 def generate(self, params):
294 system_message = params['prompt'][0]['content']
295 send_messages = params['prompt'][1:]
296 max_input_tiles = params['max_input_tiles']
297 temperature = params['temperature']
298 top_p = params['top_p']
299 max_new_tokens = params['max_new_tokens']
300 repetition_penalty = params['repetition_penalty']
301 video_frame_num = params.get('video_frame_num', 64)
302 do_sample = True if temperature > 0.0 else False
303
304 global_image_cnt = 0
305 history, pil_images, max_input_tile_list = [], [], []
306 for message in send_messages:
307 if message['role'] == 'user':
308 prefix = ''
309 if 'image' in message:
310 for image_data in message['image']:
311 pil_images.append(load_image(image_data))
312 prefix = prefix + f'<image {global_image_cnt + 1}><image>\n'
313 global_image_cnt += 1
314 max_input_tile_list.append(max_input_tiles)
315 if 'video' in message:
316 for video_data in message['video']:
317 video_frames, tmp_prefix = load_video(video_data, num_frames=video_frame_num)
318 pil_images.extend(video_frames)
319 prefix = prefix + tmp_prefix
320 global_image_cnt += len(video_frames)
321 max_input_tile_list.extend([1] * len(video_frames))
322 content = prefix + message['content']
323 history.append([content, ])
324 else:
325 history[-1].append(message['content'])
326 question, history = history[-1][0], history[:-1]
327
328 if global_image_cnt == 1:
329 question = question.replace('<image 1><image>\n', '<image>\n')
330 history = [[item[0].replace('<image 1><image>\n', '<image>\n'), item[1]] for item in history]
331
332
333 try:
334 assert len(max_input_tile_list) == len(pil_images), 'The number of max_input_tile_list and pil_images should be the same.'
335 except Exception as e:
336 from IPython import embed; embed()
337 exit()
338 print(f'Error: {e}')
339 print(f'max_input_tile_list: {max_input_tile_list}, pil_images: {pil_images}')
340 # raise e
341
342 old_system_message = self.model.system_message
343 self.model.system_message = system_message
344
345 transform = build_transform(input_size=self.image_size, norm_type=self.norm_type)
346 if len(pil_images) > 0:
347 max_input_tiles_limited_by_contect = params['max_input_tiles']
348 while True:
349 image_tiles = []
350 for current_max_input_tiles, pil_image in zip(max_input_tile_list, pil_images):
351 if self.model.config.dynamic_image_size:
352 tiles = dynamic_preprocess(
353 pil_image, image_size=self.image_size, max_num=min(current_max_input_tiles, max_input_tiles_limited_by_contect),
354 use_thumbnail=self.model.config.use_thumbnail)
355 else:
356 tiles = [pil_image]
357 image_tiles += tiles
358 if (len(image_tiles) * self.per_tile_len < self.context_len):
359 break
360 else:
361 max_input_tiles_limited_by_contect -= 2
362
363 if max_input_tiles_limited_by_contect < 1:
364 break
365
366 pixel_values = [transform(item) for item in image_tiles]
367 pixel_values = torch.stack(pixel_values).to(self.model.device, dtype=torch.bfloat16)
368 print(f'Split images to {pixel_values.shape}')
369 else:
370 pixel_values = None
371
372 generation_config = dict(
373 num_beams=1,
374 max_new_tokens=max_new_tokens,
375 do_sample=do_sample,
376 temperature=temperature,
377 repetition_penalty=repetition_penalty,
378 max_length=self.context_len,
379 top_p=top_p,
380 )
381
382 response = self.model.chat(
383 tokenizer=self.tokenizer,
384 pixel_values=pixel_values,
385 question=question,
386 history=history,
387 return_history=False,
388 generation_config=generation_config,
389 )
390 self.model.system_message = old_system_message
391 return {'text': response, 'error_code': 0}
392
393
394
395
396
397if __name__ == '__main__':
398 parser = argparse.ArgumentParser()
399 parser.add_argument('--model-path', type=str, default='NVIDIA/Eagle-2-1B')
400 parser.add_argument('--model-name', type=str, default='Eagle-2-1B')
401 parser.add_argument('--device', type=str, default='cuda')
402 parser.add_argument('--load-8bit', action='store_true')
403 args = parser.parse_args()
404 print(f'args: {args}')
405
406 worker = ModelWorker(
407 args.model_path,
408 args.model_name,
409 args.load_8bit,
410 args.device)1prompt = [
2 {'role': 'system', 'content': 'You are a helpful assistant.'},
3 {'role': 'user', 'content': 'Describe this image in details.',
4 'image':[
5 {'url': 'https://www.nvidia.com/content/dam/en-zz/Solutions/about-nvidia/logo-and-brand/01-nvidia-logo-vert-500x200-2c50-d@2x.png'}
6 ],
7 }
8 ]1prompt = [
2 {'role': 'system', 'content': 'You are a helpful assistant.'},
3 {'role': 'user', 'content': 'Describe these two images in details.',
4 'image':[
5 {'url': 'https://www.nvidia.com/content/dam/en-zz/Solutions/about-nvidia/logo-and-brand/01-nvidia-logo-vert-500x200-2c50-d@2x.png'},
6 {'url': 'https://www.nvidia.com/content/dam/en-zz/Solutions/about-nvidia/logo-and-brand/01-nvidia-logo-vert-500x200-2c50-d@2x.png'}
7 ],
8 }
9 ]1prompt = [
2 {'role': 'system', 'content': 'You are a helpful assistant.'},
3 {'role': 'user', 'content': 'Describe this video in details.',
4 'video':[
5 'path/to/your/video.mp4'
6 ],
7 }
8 ]1params = {
2 'prompt': prompt,
3 'max_input_tiles': 24,
4 'temperature': 0.7,
5 'top_p': 1.0,
6 'max_new_tokens': 4096,
7 'repetition_penalty': 1.0,
8 }
9worker.generate(params)