yogkul2000 commited on
Commit
449e004
·
verified ·
1 Parent(s): a1667c0

Update README.md

Browse files
Files changed (1) hide show
  1. README.md +302 -3
README.md CHANGED
@@ -1,3 +1,302 @@
1
- ---
2
- license: apache-2.0
3
- ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ license: apache-2.0
3
+ ---
4
+ # VideoSAVi Checkpoints
5
+
6
+ This repository weights for the **VideoSAVi (Self-Aligned Video Language Model)** introduced in the paper [VideoSAVi: Self-Aligned Video Language Models without Human Supervision](https://arxiv.org/abs/2412.00624).
7
+
8
+ ## Model Overview
9
+
10
+ VideoSAVi is a novel self-training pipeline designed to improve video-language understanding tasks without requiring extensive human annotations or proprietary models. By leveraging self-generated synthetic preference data, VideoSAVi achieves state-of-the-art performance on multiple benchmarks, including multi-choice QA, open-ended QA, and temporal reasoning tasks.
11
+
12
+ ## Usage Instructions
13
+
14
+ Please refer to [LLaVA-NeXT](https://github.com/LLaVA-VL/LLaVA-NeXT) for installing all the requirements. We provide sample inference code below.
15
+
16
+ ```
17
+ import math
18
+ import os
19
+ import argparse
20
+ import json
21
+ import torch
22
+ from tqdm import tqdm
23
+ import numpy as np
24
+ import cv2
25
+ import base64
26
+ from decord import VideoReader, cpu
27
+ from PIL import Image
28
+ from transformers import AutoConfig
29
+
30
+ from llava.conversation import conv_templates, SeparatorStyle
31
+ from llava.constants import (
32
+ IMAGE_TOKEN_INDEX,
33
+ DEFAULT_IMAGE_TOKEN,
34
+ DEFAULT_IM_START_TOKEN,
35
+ DEFAULT_IM_END_TOKEN,
36
+ )
37
+ from llava.mm_utils import (
38
+ process_anyres_image,
39
+ tokenizer_image_token,
40
+ get_model_name_from_path,
41
+ KeywordsStoppingCriteria,
42
+ )
43
+ from llava.model.builder import load_pretrained_model
44
+ from llava.train.train import smart_tokenizer_and_embedding_resize
45
+
46
+
47
+ class VideoProcessor:
48
+ def __init__(self, args):
49
+ self.args = args
50
+
51
+ def load_video(self, video_path):
52
+ """Load and process video frames."""
53
+ if self.args.frame_count == 0:
54
+ return np.zeros((1, 336, 336, 3))
55
+
56
+ vr = VideoReader(video_path, ctx=cpu(0), num_threads=1)
57
+ total_frames = len(vr)
58
+ fps = round(vr.get_avg_fps())
59
+ video_duration = total_frames / fps
60
+
61
+ frame_indices = [i for i in range(0, total_frames, fps)]
62
+ frame_times = [i / fps for i in frame_indices]
63
+
64
+ if len(frame_indices) > self.args.frame_count or self.args.force_sample:
65
+ uniform_samples = np.linspace(
66
+ 0, total_frames - 1, self.args.frame_count, dtype=int
67
+ )
68
+ frame_indices = uniform_samples.tolist()
69
+ frame_times = [i / fps for i in frame_indices]
70
+
71
+ frame_times_str = ",".join([f"{t:.2f}s" for t in frame_times])
72
+ frames = vr.get_batch(frame_indices).asnumpy()
73
+
74
+ return frames, frame_times_str, video_duration
75
+
76
+ @staticmethod
77
+ def load_video_base64(path):
78
+ """Convert video frames to base64 encoding."""
79
+ video = cv2.VideoCapture(path)
80
+ base64_frames = []
81
+
82
+ while video.isOpened():
83
+ success, frame = video.read()
84
+ if not success:
85
+ break
86
+ _, buffer = cv2.imencode(".jpg", frame)
87
+ base64_frames.append(base64.b64encode(buffer).decode("utf-8"))
88
+
89
+ video.release()
90
+ return base64_frames
91
+
92
+
93
+ class VideoInference:
94
+ def __init__(self, model, tokenizer, device="cuda"):
95
+ self.model = model
96
+ self.tokenizer = tokenizer
97
+ self.device = device
98
+
99
+ def generate_response(self, video, prompt):
100
+ """Generate model response for video input."""
101
+ if self.model.config.mm_use_im_start_end:
102
+ prompt = f"{DEFAULT_IM_START_TOKEN}{DEFAULT_IMAGE_TOKEN}{DEFAULT_IM_END_TOKEN}\n{prompt}"
103
+ else:
104
+ prompt = f"{DEFAULT_IMAGE_TOKEN}\n{prompt}"
105
+
106
+ conv = conv_templates["qwen_2"].copy()
107
+ conv.append_message(conv.roles[0], prompt)
108
+ conv.append_message(conv.roles[1], None)
109
+ full_prompt = conv.get_prompt()
110
+
111
+ input_ids = (
112
+ tokenizer_image_token(
113
+ full_prompt, self.tokenizer, IMAGE_TOKEN_INDEX, return_tensors="pt"
114
+ )
115
+ .unsqueeze(0)
116
+ .to(self.device)
117
+ )
118
+
119
+ if self.tokenizer.pad_token_id is None:
120
+ if "qwen" in self.tokenizer.name_or_path.lower():
121
+ self.tokenizer.pad_token_id = 151643
122
+
123
+ attention_mask = (
124
+ input_ids.ne(self.tokenizer.pad_token_id).long().to(self.device)
125
+ )
126
+ stop_str = conv.sep if conv.sep_style != SeparatorStyle.TWO else conv.sep2
127
+ stopping_criteria = KeywordsStoppingCriteria(
128
+ [stop_str], self.tokenizer, input_ids
129
+ )
130
+
131
+ try:
132
+ with torch.inference_mode():
133
+ output_ids = self.model.generate(
134
+ inputs=input_ids,
135
+ images=video,
136
+ attention_mask=attention_mask,
137
+ modalities="video",
138
+ do_sample=True,
139
+ temperature=0.2,
140
+ max_new_tokens=256,
141
+ use_cache=True,
142
+ stopping_criteria=[stopping_criteria],
143
+ )
144
+
145
+ generated_text = self.tokenizer.batch_decode(
146
+ output_ids, skip_special_tokens=True
147
+ )[0].strip()
148
+
149
+ if generated_text.endswith(stop_str):
150
+ generated_text = generated_text[: -len(stop_str)].strip()
151
+
152
+ return generated_text
153
+
154
+ except Exception as e:
155
+ print(f"Generation error: {str(e)}")
156
+ return "Can you describe another aspect of the video?"
157
+
158
+
159
+ ANSWER_PROMPTS = {
160
+ "multi-choice": "\nPlease directly give the best option:",
161
+ "yes_no": "\nPlease answer yes or no:",
162
+ "caption_matching": "\nPlease directly give the best option:",
163
+ "captioning": "",
164
+ }
165
+
166
+
167
+ def setup_model(model_path, args):
168
+ """Setup the model, tokenizer and processors."""
169
+ model_name = get_model_name_from_path(model_path)
170
+
171
+ if args.overwrite:
172
+ config = {
173
+ "mm_spatial_pool_mode": args.mm_spatial_pool_mode,
174
+ "mm_spatial_pool_stride": args.mm_spatial_pool_stride,
175
+ "mm_newline_position": args.mm_newline_position,
176
+ }
177
+
178
+ cfg_pretrained = AutoConfig.from_pretrained(model_path)
179
+
180
+ if "qwen" not in model_path.lower():
181
+ if "224" in cfg_pretrained.mm_vision_tower:
182
+ min_tokens = (
183
+ args.frame_count * (16 // args.mm_spatial_pool_stride) ** 2 + 1000
184
+ )
185
+ else:
186
+ min_tokens = (
187
+ args.frame_count * (24 // args.mm_spatial_pool_stride) ** 2 + 1000
188
+ )
189
+
190
+ scaling = math.ceil(min_tokens / 4096)
191
+ if scaling >= 2:
192
+ if "vicuna" in cfg_pretrained._name_or_path.lower():
193
+ config["rope_scaling"] = {
194
+ "factor": float(scaling),
195
+ "type": "linear",
196
+ }
197
+ config["max_sequence_length"] = 4096 * scaling
198
+ config["tokenizer_model_max_length"] = 4096 * scaling
199
+
200
+ return load_pretrained_model(
201
+ model_path, args.model_base, model_name, overwrite_config=config
202
+ )
203
+
204
+ return load_pretrained_model(model_path, args.model_base, model_name)
205
+
206
+
207
+ def main():
208
+ parser = argparse.ArgumentParser(description="Video LLM Processing")
209
+ parser.add_argument(
210
+ "--video_dir", required=True, help="Directory containing video files"
211
+ )
212
+ parser.add_argument(
213
+ "--output_dir", required=True, help="Directory for output predictions"
214
+ )
215
+ parser.add_argument("--model_path", required=True, help="Path to the model")
216
+ parser.add_argument(
217
+ "--questions_dir", required=True, help="Directory containing question files"
218
+ )
219
+ parser.add_argument(
220
+ "--task_type",
221
+ default="multi-choice",
222
+ choices=["multi-choice", "captioning", "caption_matching", "yes_no"],
223
+ )
224
+ parser.add_argument("--frame_count", type=int, default=4)
225
+ parser.add_argument(
226
+ "--overwrite", type=lambda x: str(x).lower() == "true", default=True
227
+ )
228
+ parser.add_argument(
229
+ "--force_sample", type=lambda x: str(x).lower() == "true", default=False
230
+ )
231
+ parser.add_argument("--model_base", default=None)
232
+ parser.add_argument("--model_max_length", type=int, default=2048)
233
+ parser.add_argument("--mm_spatial_pool_stride", type=int, default=4)
234
+ parser.add_argument("--mm_spatial_pool_out_channels", type=int, default=1024)
235
+ parser.add_argument("--mm_spatial_pool_mode", type=str, default="average")
236
+ parser.add_argument("--mm_newline_position", type=str, default="no_token")
237
+
238
+ args = parser.parse_args()
239
+
240
+ # Ensure output directory exists
241
+ os.makedirs(args.output_dir, exist_ok=True)
242
+
243
+ # Setup model and processors
244
+ tokenizer, model, image_processor, context_len = setup_model(args.model_path, args)
245
+ model = model.to("cuda")
246
+
247
+ # Load questions
248
+ question_file = os.path.join(args.questions_dir, f"{args.task_type}.json")
249
+ with open(question_file, "r") as f:
250
+ questions = json.load(f)
251
+
252
+ # Initialize or load predictions
253
+ pred_file = os.path.join(args.output_dir, f"{args.task_type}.json")
254
+ if os.path.isfile(pred_file):
255
+ with open(pred_file, "r") as f:
256
+ predictions = json.load(f)
257
+ else:
258
+ predictions = {}
259
+
260
+ # Setup processors
261
+ video_processor = VideoProcessor(args)
262
+ inference_engine = VideoInference(model, tokenizer)
263
+
264
+ # Process videos
265
+ for video_id, data in tqdm(questions.items()):
266
+ if video_id not in predictions:
267
+ predictions[video_id] = {}
268
+ video_path = os.path.join(args.video_dir, f"{video_id}.mp4")
269
+
270
+ for dimension, question_list in data.items():
271
+ predictions[video_id][dimension] = []
272
+ for question in question_list:
273
+ prompt = question["question"] + ANSWER_PROMPTS[args.task_type]
274
+ video, _, _ = video_processor.load_video(video_path)
275
+ video = (
276
+ image_processor.preprocess(video, return_tensors="pt")[
277
+ "pixel_values"
278
+ ]
279
+ .half()
280
+ .cuda()
281
+ )
282
+ video = [video]
283
+
284
+ prediction = inference_engine.generate_response(video, prompt)
285
+ predictions[video_id][dimension].append(
286
+ {
287
+ "question": question["question"],
288
+ "answer": question["answer"],
289
+ "prediction": prediction,
290
+ }
291
+ )
292
+
293
+ # Save predictions after each video
294
+ with open(pred_file, "w") as f:
295
+ json.dump(predictions, f, indent=4)
296
+
297
+
298
+ if __name__ == "__main__":
299
+ main()
300
+
301
+ ```
302
+