Download longvu/train.py from SiPongo/Describe: direct link, hf CLI and curl.
- Browser
- Download file 45.6 kB
-
https://huggingface.co/spaces/SiPongo/Describe/resolve/main/longvu/train.py
- Command line
-
hf download hf://spaces/SiPongo/Describe/longvu/train.py
-
curl -L -o train.py https://huggingface.co/spaces/SiPongo/Describe/resolve/main/longvu/train.py
45.6 kB
| # Adopted from https://github.com/lm-sys/FastChat. Below is the original copyright: | |
| # Adopted from tatsu-lab@stanford_alpaca. Below is the original copyright: | |
| # Make it more memory efficient by monkey patching the LLaMA model with FlashAttn. | |
| # pyre-strict | |
| # Need to call this before importing transformers. | |
| import copy | |
| import datetime | |
| import json | |
| import os | |
| import pathlib | |
| import uuid | |
| from dataclasses import dataclass, field | |
| from logging import Logger | |
| from typing import Dict, List, Optional, Sequence | |
| import numpy as np | |
| import torch | |
| import transformers | |
| from decord import cpu, VideoReader | |
| from longvu import conversation as conversation_lib | |
| from longvu.constants import ( | |
| DEFAULT_IM_END_TOKEN, | |
| DEFAULT_IM_START_TOKEN, | |
| DEFAULT_IMAGE_TOKEN, | |
| IGNORE_INDEX, | |
| IMAGE_TOKEN_INDEX, | |
| ) | |
| from longvu.language_model.cambrian_llama import CambrianLlamaForCausalLM | |
| from longvu.language_model.cambrian_qwen import CambrianQwenForCausalLM | |
| from longvu.mm_datautils import ( | |
| preprocess, | |
| preprocess_multimodal, | |
| safe_save_model_for_hf_trainer, | |
| smart_tokenizer_and_embedding_resize, | |
| ) | |
| from longvu.mm_trainer import LLaVATrainer | |
| from PIL import Image, ImageSequence | |
| from tensorboard.compat.tensorflow_stub.io.gfile import register_filesystem | |
| from torch import distributed as dist | |
| from torch.utils.data import Dataset | |
| from torch.utils.tensorboard import SummaryWriter | |
| from transformers import TrainerCallback | |
| from transformers.integrations import TensorBoardCallback | |
| TENSORBOARD_LOG_DIR_NAME: str = "tensorboard_logs" | |
| class ModelArguments: | |
| input_model_filename: Optional[str] = field(default=None) | |
| output_model_filename: Optional[str] = field(default=None) | |
| model_name_or_path: Optional[str] = field(default="facebook/opt-125m") | |
| version: Optional[str] = field(default="v0") | |
| freeze_backbone: bool = field(default=False) | |
| tune_mm_mlp_adapter: bool = field(default=False) | |
| vision_tower: Optional[str] = field(default=None) | |
| mm_vision_select_layer: Optional[int] = field( | |
| default=-1 | |
| ) # default to the last layer | |
| pretrain_mm_mlp_adapter: Optional[str] = field(default=None) | |
| mm_projector_type: Optional[str] = field(default="linear") | |
| mm_use_im_start_end: bool = field(default=False) | |
| mm_use_im_patch_token: bool = field(default=True) | |
| mm_patch_merge_type: Optional[str] = field(default="flat") | |
| mm_vision_select_feature: Optional[str] = field(default="patch") | |
| grid_size: Optional[int] = field(default=8) | |
| vision_tower_type: Optional[str] = field(default="sam") | |
| mm_hidden_size: Optional[int] = field(default=256) | |
| # cambrian | |
| vision_tower_aux_list: Optional[str] = field( | |
| default='["siglip/CLIP-ViT-SO400M-14-384", "facebook/dinov2-giant-res378"]' | |
| ) | |
| vision_tower_aux_token_len_list: Optional[str] = field(default="[576, 576]") | |
| image_token_len: Optional[int] = field(default=576) | |
| num_query_group: Optional[int] = field(default=1) | |
| query_num_list: Optional[str] = field(default="[576]") | |
| connector_depth: Optional[int] = field(default=3) | |
| vision_hidden_size: Optional[int] = field(default=1024) | |
| connector_only: bool = field(default=True) | |
| num_of_vision_sampler_layers: Optional[int] = field(default=10) | |
| start_of_vision_sampler_layers: Optional[int] = field(default=0) | |
| stride_of_vision_sampler_layers: Optional[int] = field(default=3) | |
| is_st_sampler: bool = field(default=False) | |
| highres_connect: bool = field(default=False) | |
| highres: bool = field(default=False) | |
| connect_layer: Optional[int] = field(default=2) | |
| lowres_token: Optional[int] = field(default=8) | |
| dino_threshold: float = field(default=0.83) | |
| drop_threshold: float = field(default=0.8) | |
| frame_pos: bool = field(default=False) | |
| is_image_newline: bool = field(default=True) | |
| class DataArguments: | |
| data_path: Optional[str] = field(default=None) | |
| lazy_preprocess: bool = False | |
| is_multimodal: bool = False | |
| image_position: Optional[int] = field(default=91) | |
| image_folder: Optional[str] = field(default=None) | |
| uniform_sample: bool = field(default=False) | |
| image_aspect_ratio: str = "square" | |
| num_points: int = field(default=0) | |
| video_fps: float = field(default=1) | |
| use_subtitle: bool = field(default=True) | |
| class TrainingArguments(transformers.TrainingArguments): | |
| remove_unused_columns: bool = field(default=False) | |
| freeze_mm_mlp_adapter: bool = field(default=False) | |
| mpt_attn_impl: Optional[str] = field(default="triton") | |
| tune_text_decoder: bool = field(default=False) | |
| unfreeze_mm_vision_tower: bool = field(default=False) | |
| mm_vision_tower_lr: Optional[float] = None | |
| unfreeze_mm_image_decoder: bool = field(default=False) | |
| mm_vision_sampler_lr: Optional[float] = None | |
| mm_projector_lr: Optional[float] = None | |
| model_max_length: Optional[int] = field(default=8192) | |
| lora_enable: bool = False | |
| lora_r: int = 64 | |
| lora_alpha: int = 16 | |
| lora_dropout: float = 0.05 | |
| lora_weight_path: str = "" | |
| lora_bias: str = "none" | |
| mm_projector_lr: Optional[float] = None | |
| group_by_modality_length: bool = field(default=False) | |
| def get_local_rank() -> int: | |
| if os.environ.get("LOCAL_RANK"): | |
| return int(os.environ["LOCAL_RANK"]) | |
| else: | |
| return torch.distributed.get_rank() | |
| def get_global_rank() -> int: | |
| """ | |
| Get rank using torch.distributed if available. Otherwise, the RANK env var instead if initialized. | |
| Returns 0 if neither condition is met. | |
| """ | |
| if torch.distributed.is_available() and torch.distributed.is_initialized(): | |
| return torch.distributed.get_rank() | |
| environ_rank = os.environ.get("RANK", "") | |
| if environ_rank.isdecimal(): | |
| return int(os.environ["RANK"]) | |
| return 0 | |
| # pyre-fixme[3]: Return type must be annotated. | |
| # pyre-fixme[2]: Parameter must be annotated. | |
| def get_padding_offset(cur_size, original_size): | |
| cur_w, cur_h = cur_size | |
| original_w, original_h = original_size | |
| original_aspect_ratio = original_w / original_h | |
| current_aspect_ratio = cur_w / cur_h | |
| if original_aspect_ratio > current_aspect_ratio: | |
| scale_factor = cur_w / original_w | |
| new_height = int(original_h * scale_factor) | |
| padding = (cur_h - new_height) // 2 | |
| return 0, 0, padding, padding | |
| else: | |
| scale_factor = cur_h / original_h | |
| new_width = int(original_w * scale_factor) | |
| padding = (cur_w - new_width) // 2 | |
| return padding, padding, 0, 0 | |
| # pyre-fixme[3]: Return type must be annotated. | |
| # pyre-fixme[2]: Parameter must be annotated. | |
| def prepare_image_info(image_size, image_token_len, newline=False): | |
| num_tokens_per_side = int(image_token_len**0.5) | |
| if newline: | |
| # for the newline embedding | |
| attention_mask = torch.ones( | |
| num_tokens_per_side, num_tokens_per_side + 1, dtype=torch.bool | |
| ) | |
| else: | |
| attention_mask = torch.ones( | |
| num_tokens_per_side, num_tokens_per_side, dtype=torch.bool | |
| ) | |
| left_offset, right_offset, top_offset, bottom_offset = get_padding_offset( | |
| (num_tokens_per_side, num_tokens_per_side), image_size | |
| ) | |
| if newline: | |
| if left_offset > 0: | |
| attention_mask[:, :left_offset] = 0 | |
| if right_offset > 0: | |
| attention_mask[:, -right_offset - 1 : -1] = 0 | |
| if top_offset > 0: | |
| attention_mask[:top_offset, :] = 0 | |
| if bottom_offset > 0: | |
| attention_mask[-bottom_offset:, :] = 0 | |
| else: | |
| if left_offset > 0: | |
| attention_mask[:, :left_offset] = 0 | |
| if right_offset > 0: | |
| attention_mask[:, -right_offset:] = 0 | |
| if top_offset > 0: | |
| attention_mask[:top_offset, :] = 0 | |
| if bottom_offset > 0: | |
| attention_mask[-bottom_offset:, :] = 0 | |
| attention_mask = attention_mask.flatten() | |
| position_ids = attention_mask.cumsum(0) - 1 | |
| return attention_mask, position_ids | |
| # pyre-fixme[3]: Return type must be annotated. | |
| # pyre-fixme[2]: Parameter must be annotated. | |
| def prepare_multimodal_data( | |
| input_ids, # pyre-fixme[2] | |
| labels, # pyre-fixme[2] | |
| attention_mask, # pyre-fixme[2] | |
| image_sizes, # pyre-fixme[2] | |
| image_token_len=576, # pyre-fixme[2] | |
| image_aux_token_len_list=[192 * 192], # pyre-fixme[2] | |
| max_length=2048, # pyre-fixme[2] | |
| ): | |
| input_ids_im_replaced = [] | |
| labels_im_replaced = [] | |
| attention_mask_im_replaced = [] | |
| position_ids_im_replaced = [] | |
| im_aux_attention_masks_list = [[] for _ in range(len(image_aux_token_len_list))] | |
| base_image_token_len_per_side = int(image_token_len**0.5) | |
| image_aux_token_len_per_side_list = [ | |
| int(image_aux_token_len_per_side**0.5) | |
| for image_aux_token_len_per_side in image_aux_token_len_list | |
| ] | |
| # insert the padding tokens to the places of image so we can embed them together | |
| for batch_idx, cur_input_ids in enumerate(input_ids): | |
| num_images = (cur_input_ids == IMAGE_TOKEN_INDEX).sum() | |
| assert num_images == 1, num_images | |
| image_size = image_sizes[batch_idx] | |
| image_token_indices = ( | |
| [-1] | |
| + torch.where(cur_input_ids == IMAGE_TOKEN_INDEX)[0].tolist() | |
| + [cur_input_ids.shape[0]] | |
| ) | |
| cur_input_ids_im_replaced = [] | |
| cur_labels_im_replaced = [] | |
| cur_attention_mask_im_replaced = [] | |
| cur_position_ids_im_replaced = [] | |
| cur_labels = labels[batch_idx] | |
| cur_attention_mask = attention_mask[batch_idx] | |
| index = 0 | |
| for i in range(len(image_token_indices) - 1): | |
| # still keep the first image token in input_ids for further use | |
| cur_input_ids_im_replaced.append( | |
| cur_input_ids[ | |
| image_token_indices[i] + 1 : image_token_indices[i + 1] + 1 | |
| ] | |
| ) | |
| cur_labels_im_replaced.append( | |
| cur_labels[image_token_indices[i] + 1 : image_token_indices[i + 1]] | |
| ) | |
| cur_attention_mask_im_replaced.append( | |
| cur_attention_mask[ | |
| image_token_indices[i] + 1 : image_token_indices[i + 1] | |
| ] | |
| ) | |
| cur_position_ids_im_replaced.append( | |
| torch.arange( | |
| index, | |
| index + image_token_indices[i + 1] - (image_token_indices[i] + 1), | |
| dtype=torch.long, | |
| device=cur_input_ids.device, | |
| ) | |
| ) | |
| index += image_token_indices[i + 1] - (image_token_indices[i] + 1) | |
| if i < len(image_token_indices) - 2: | |
| num_tokens_per_side = int(image_token_len**0.5) | |
| image_token_len_with_newline = image_token_len + num_tokens_per_side | |
| cur_input_ids_im_replaced.append( | |
| torch.full( | |
| (image_token_len_with_newline - 1,), | |
| 0, | |
| device=cur_input_ids.device, | |
| dtype=cur_input_ids.dtype, | |
| ) | |
| ) | |
| cur_labels_im_replaced.append( | |
| torch.full( | |
| (image_token_len_with_newline,), | |
| IGNORE_INDEX, | |
| device=cur_labels.device, | |
| dtype=cur_labels.dtype, | |
| ) | |
| ) | |
| cur_im_attention_mask, cur_im_position_ids = prepare_image_info( | |
| image_size, image_token_len, newline=True | |
| ) | |
| for aux_i, image_aux_token_len_per_side in enumerate( | |
| image_aux_token_len_per_side_list | |
| ): | |
| assert image_aux_token_len_per_side >= base_image_token_len_per_side | |
| num_base_crops_per_aux_side = ( | |
| image_aux_token_len_per_side // base_image_token_len_per_side | |
| ) | |
| cur_im_aux_attention_mask, _ = prepare_image_info( | |
| image_size, image_aux_token_len_per_side**2 | |
| ) | |
| cur_im_aux_attention_mask = cur_im_aux_attention_mask.view( | |
| base_image_token_len_per_side, | |
| num_base_crops_per_aux_side, | |
| base_image_token_len_per_side, | |
| num_base_crops_per_aux_side, | |
| ) | |
| cur_im_aux_attention_mask = ( | |
| cur_im_aux_attention_mask.permute(0, 2, 1, 3) | |
| .contiguous() | |
| .flatten(0, 1) | |
| .flatten(1, 2) | |
| ) | |
| cur_im_aux_attention_mask[ | |
| cur_im_aux_attention_mask.sum(dim=1) == 0 | |
| ] = True | |
| im_aux_attention_masks_list[aux_i].append(cur_im_aux_attention_mask) | |
| cur_im_position_ids += index | |
| if cur_attention_mask[image_token_indices[i + 1]]: | |
| cur_attention_mask_im_replaced.append(cur_im_attention_mask) | |
| cur_position_ids_im_replaced.append( | |
| cur_im_position_ids.to(torch.long) | |
| ) | |
| index = cur_im_position_ids.max() + 1 | |
| else: | |
| num_tokens_per_side = int(image_token_len**0.5) | |
| image_token_len_with_newline = image_token_len + num_tokens_per_side | |
| cur_attention_mask_im_replaced.append( | |
| torch.full( | |
| (image_token_len_with_newline,), | |
| 0, | |
| device=cur_attention_mask.device, | |
| dtype=cur_attention_mask.dtype, | |
| ) | |
| ) | |
| cur_position_ids_im_replaced.append( | |
| torch.full( | |
| (image_token_len_with_newline,), | |
| 0, | |
| device=cur_input_ids.device, | |
| dtype=torch.long, | |
| ) | |
| ) | |
| input_ids_im_replaced.append(torch.cat(cur_input_ids_im_replaced)) | |
| labels_im_replaced.append(torch.cat(cur_labels_im_replaced)) | |
| attention_mask_im_replaced.append(torch.cat(cur_attention_mask_im_replaced)) | |
| position_ids_im_replaced.append(torch.cat(cur_position_ids_im_replaced)) | |
| # Truncate sequences to max length as image embeddings can make the sequence longer | |
| new_input_ids = [x[0:max_length] for x in input_ids_im_replaced] | |
| new_labels = [x[0:max_length] for x in labels_im_replaced] | |
| new_attention_mask = [x[0:max_length] for x in attention_mask_im_replaced] | |
| new_position_ids = [x[0:max_length] for x in position_ids_im_replaced] | |
| new_input_ids = torch.stack(new_input_ids) | |
| new_labels = torch.stack(new_labels) | |
| new_attention_mask = torch.stack(new_attention_mask) | |
| new_position_ids = torch.stack(new_position_ids) | |
| im_aux_attention_masks_list = [ | |
| torch.stack(im_aux_attention_masks) | |
| for im_aux_attention_masks in im_aux_attention_masks_list | |
| ] | |
| return ( | |
| new_input_ids, | |
| new_labels, | |
| new_attention_mask, | |
| new_position_ids, | |
| im_aux_attention_masks_list, | |
| ) | |
| class LazySupervisedDataset(Dataset): | |
| """Dataset for supervised fine-tuning.""" | |
| def __init__( | |
| self, | |
| data_path: str, | |
| tokenizer: transformers.PreTrainedTokenizer, | |
| # pyre-fixme[2]: Parameter must be annotated. | |
| data_args, | |
| ) -> None: | |
| super(LazySupervisedDataset, self).__init__() | |
| list_data_dict = json.load(open(data_path, "r")) | |
| self.tokenizer = tokenizer | |
| # pyre-fixme[4]: Attribute must be annotated. | |
| self.list_data_dict = list_data_dict | |
| self.data_path = data_path | |
| # pyre-fixme[4]: Attribute must be annotated. | |
| self.data_args = data_args | |
| # pyre-fixme[4]: Attribute must be annotated. | |
| self.length = self._get_length() | |
| # pyre-fixme[3]: Return type must be annotated. | |
| def _get_length(self): | |
| """Calculates the number of samples in the .jsonl file.""" | |
| with open(self.data_path, "r") as file: | |
| for i, _ in enumerate(file): | |
| pass | |
| return i + 1 # pyre-fixme | |
| def __len__(self) -> int: | |
| return len(self.list_data_dict) | |
| # pyre-fixme[3]: Return type must be annotated. | |
| def _compute_lengths(self): | |
| """Compute and cache lengths of conversations in the dataset.""" | |
| if hasattr(self, "length_list") and hasattr(self, "modality_length_list"): | |
| # Return cached values if already computed | |
| return self.length_list, self.modality_length_list # pyre-fixme | |
| self.length_list = [] | |
| self.modality_length_list = [] | |
| for sample in self.list_data_dict: | |
| img_tokens = ( | |
| self.data_args.image_token_len if self._has_image(sample) else 0 | |
| ) | |
| cur_len = sum( | |
| len(conv["value"].split()) for conv in sample["conversations"] | |
| ) | |
| self.length_list.append(cur_len + img_tokens) | |
| modality_len = cur_len if "image" in sample else -cur_len | |
| self.modality_length_list.append(modality_len) | |
| return self.length_list, self.modality_length_list | |
| # pyre-fixme[3]: Return type must be annotated. | |
| def lengths(self): | |
| length_list, _ = self._compute_lengths() | |
| return length_list | |
| # pyre-fixme[3]: Return type must be annotated. | |
| def modality_lengths(self): | |
| _, modality_length_list = self._compute_lengths() | |
| return modality_length_list | |
| def _has_image(self, sample: dict) -> bool: # pyre-fixme | |
| if "image" in sample and not str(sample["image"]) in [ | |
| "", | |
| "None", | |
| "none", | |
| "nan", | |
| ]: | |
| return True | |
| if "video" in sample and not str(sample["video"]) in [ | |
| "", | |
| "None", | |
| "none", | |
| "nan", | |
| ]: | |
| return True | |
| return False | |
| def __getitem__(self, i: int) -> Dict[str, torch.Tensor]: | |
| sources = self.list_data_dict[i] | |
| dat = sources | |
| if isinstance(i, int): | |
| sources = [sources] | |
| assert len(sources) == 1, "Don't know why it is wrapped to a list" # FIXME | |
| has_image = self._has_image(dat) | |
| if has_image: | |
| if "image" in dat: | |
| image_file = dat["image"] | |
| image_folder = self.data_args.image_folder | |
| processor_aux_list = self.data_args.image_processor_aux_list | |
| try: | |
| image = Image.open(os.path.join(image_folder, image_file)).convert( | |
| "RGB" | |
| ) | |
| except: | |
| print( | |
| "Not exist: ", | |
| os.path.join(image_folder, image_file), | |
| flush=True, | |
| ) | |
| return self.__getitem__(0) | |
| image_size = image.size | |
| else: | |
| video_file = dat["video"] | |
| processor_aux_list = self.data_args.image_processor_aux_list | |
| if video_file.endswith(".gif"): | |
| video_file = os.path.join( | |
| self.data_args.image_folder, "gifs", video_file | |
| ) | |
| else: | |
| video_file = os.path.join(self.data_args.image_folder, video_file) | |
| if os.path.exists(video_file): | |
| try: | |
| if video_file.endswith(".npy"): | |
| image = np.load(video_file) | |
| image_size = image[0].shape[:2] | |
| elif video_file.endswith(".gif"): | |
| video = Image.open(video_file) | |
| image = [] | |
| for frame in ImageSequence.Iterator(video): | |
| frame_copy = frame.copy() | |
| image.append(frame_copy.convert("RGB")) | |
| image_size = image[0].size | |
| elif os.path.isdir(video_file): | |
| files = [f for f in sorted(os.listdir(video_file))] | |
| image = [] | |
| for file in files: | |
| image.append( | |
| Image.open(os.path.join(video_file, file)).convert( | |
| "RGB" | |
| ) | |
| ) | |
| image_size = image[0].size | |
| else: | |
| vr = VideoReader(video_file, ctx=cpu(0), num_threads=1) | |
| sample_fps = round( | |
| vr.get_avg_fps() / self.data_args.video_fps | |
| ) | |
| frame_idx = [i for i in range(0, len(vr), sample_fps)] | |
| image = vr.get_batch(frame_idx).asnumpy() | |
| image_size = image[0].shape[:2] | |
| if self.data_args.uniform_sample: | |
| num_sample = 100 | |
| if len(image) > num_sample: | |
| interval = len(image) / float(num_sample) | |
| indices = [int(interval * i) for i in range(num_sample)] | |
| image = [image[idx] for idx in indices] | |
| except: | |
| print("fail to load video: ", video_file, flush=True) | |
| return self.__getitem__(0) | |
| else: | |
| print("Not exist: ", video_file, flush=True) | |
| return self.__getitem__(0) | |
| # pyre-fixme[3]: Return type must be annotated. | |
| # pyre-fixme[2]: Parameter must be annotated. | |
| def expand2square(pil_img, background_color): | |
| width, height = pil_img.size | |
| if width == height: | |
| return pil_img | |
| elif width > height: | |
| result = Image.new(pil_img.mode, (width, width), background_color) | |
| result.paste(pil_img, (0, (width - height) // 2)) | |
| # result.paste(pil_img, (0, 0)) | |
| return result | |
| else: | |
| result = Image.new(pil_img.mode, (height, height), background_color) | |
| result.paste(pil_img, ((height - width) // 2, 0)) | |
| # result.paste(pil_img, (0, 0)) | |
| return result | |
| if self.data_args.image_aspect_ratio != "pad": | |
| raise NotImplementedError("Only pad is supported for now.") | |
| image_aux_list = [] | |
| for processor_aux in processor_aux_list: | |
| image_aux = image | |
| try: | |
| target_resolution = processor_aux.crop_size["height"] | |
| except: | |
| target_resolution = processor_aux.size["height"] | |
| if not isinstance(image_aux, Image.Image): | |
| frame_list = [] | |
| for frame in image_aux: | |
| if not isinstance(frame, Image.Image): | |
| frame = Image.fromarray(frame) | |
| frame_aux = expand2square( | |
| frame, tuple(int(x * 255) for x in processor_aux.image_mean) | |
| ).resize((target_resolution, target_resolution)) | |
| frame_aux = processor_aux.preprocess( | |
| frame_aux, return_tensors="pt" | |
| )["pixel_values"][0] | |
| frame_list.append(frame_aux) | |
| image_aux = torch.stack(frame_list) | |
| else: | |
| image_aux = expand2square( | |
| image_aux, tuple(int(x * 255) for x in processor_aux.image_mean) | |
| ).resize((target_resolution, target_resolution)) | |
| image_aux = processor_aux.preprocess( | |
| image_aux, return_tensors="pt" | |
| )["pixel_values"][0] | |
| image_aux_list.append(image_aux) | |
| sources = preprocess_multimodal( | |
| copy.deepcopy([e["conversations"] for e in sources]), self.data_args | |
| ) | |
| else: | |
| sources = copy.deepcopy([e["conversations"] for e in sources]) | |
| data_dict = preprocess(sources, self.tokenizer, has_image=has_image) # pyre-fixme | |
| if isinstance(i, int): | |
| data_dict = dict( | |
| input_ids=data_dict["input_ids"][0], labels=data_dict["labels"][0] | |
| ) | |
| if (data_dict["labels"] != IGNORE_INDEX).sum() == 0: | |
| return self.__getitem__(0) | |
| # image exist in the data | |
| if has_image: | |
| data_dict["image_aux_list"] = image_aux_list # pyre-fixme | |
| elif self.data_args.is_multimodal: | |
| # image does not exist in the data, but the model is multimodal | |
| crop_size = 336 | |
| processor_aux_list = self.data_args.image_processor_aux_list | |
| image_list = [] | |
| for processor_aux in processor_aux_list: | |
| try: | |
| target_resolution = processor_aux.crop_size["height"] | |
| except: | |
| target_resolution = processor_aux.size["height"] | |
| image_list.append( | |
| torch.zeros( | |
| 3, | |
| target_resolution, | |
| target_resolution, | |
| ) | |
| ) | |
| data_dict["image_aux_list"] = image_list | |
| image_size = (crop_size, crop_size) | |
| data_dict["image_size"] = image_size # pyre-fixme | |
| return data_dict | |
| class DataCollatorForSupervisedDataset(object): | |
| """Collate examples for supervised fine-tuning.""" | |
| tokenizer: transformers.PreTrainedTokenizer | |
| image_token_len: int | |
| image_aux_token_len_list: list # pyre-fixme | |
| image_position: int | |
| def __call__(self, instances: Sequence[Dict]) -> Dict[str, torch.Tensor]: # pyre-fixme | |
| image_token_len = self.image_token_len | |
| image_aux_token_len_list = self.image_aux_token_len_list | |
| image_position = self.image_position | |
| input_ids, labels = tuple( | |
| [instance[key] for instance in instances] for key in ("input_ids", "labels") | |
| ) | |
| max_length = self.tokenizer.model_max_length | |
| padding_side = self.tokenizer.padding_side | |
| # print_rank0("Pad token id is", self.tokenizer.pad_token_id) | |
| if padding_side == "left": | |
| input_ids = [ | |
| ( | |
| t[:max_length] | |
| if t.shape[0] >= max_length | |
| else torch.nn.functional.pad( | |
| t, | |
| (max_length - t.shape[0], 0), | |
| "constant", | |
| self.tokenizer.pad_token_id, | |
| ) | |
| ) | |
| for t in input_ids | |
| ] | |
| labels = [ | |
| ( | |
| t[:max_length] | |
| if t.shape[0] >= max_length | |
| else torch.nn.functional.pad( | |
| t, (max_length - t.shape[0], 0), "constant", IGNORE_INDEX | |
| ) | |
| ) | |
| for t in labels | |
| ] | |
| else: | |
| input_ids = [ | |
| ( | |
| t[:max_length] | |
| if t.shape[0] >= max_length | |
| else torch.nn.functional.pad( | |
| t, | |
| (0, max_length - t.shape[0]), | |
| "constant", | |
| self.tokenizer.pad_token_id, | |
| ) | |
| ) | |
| for t in input_ids | |
| ] | |
| labels = [ | |
| ( | |
| t[:max_length] | |
| if t.shape[0] >= max_length | |
| else torch.nn.functional.pad( | |
| t, (0, max_length - t.shape[0]), "constant", IGNORE_INDEX | |
| ) | |
| ) | |
| for t in labels | |
| ] | |
| input_ids = torch.stack(input_ids) | |
| labels = torch.stack(labels) | |
| attention_mask = input_ids.ne(self.tokenizer.pad_token_id) # pyre-fixme | |
| # insert dummy image | |
| for i in range(len(input_ids)): | |
| if (input_ids[i] == IMAGE_TOKEN_INDEX).sum() == 0: | |
| cur_input_ids_tmp = input_ids[i].clone() | |
| cur_input_ids_tmp[image_position + 1 :] = input_ids[ | |
| i, image_position:-1 | |
| ] | |
| cur_input_ids_tmp[image_position] = IMAGE_TOKEN_INDEX | |
| input_ids[i] = cur_input_ids_tmp | |
| cur_labels_tmp = labels[i].clone() | |
| cur_labels_tmp[image_position + 1 :] = labels[i, image_position:-1] | |
| cur_labels_tmp[image_position] = IGNORE_INDEX | |
| labels[i] = cur_labels_tmp | |
| cur_attention_mask_tmp = attention_mask[i].clone() | |
| cur_attention_mask_tmp[image_position + 1 :] = attention_mask[ | |
| i, image_position:-1 | |
| ] | |
| cur_attention_mask_tmp[image_position] = False | |
| attention_mask[i] = cur_attention_mask_tmp | |
| image_sizes = [instance["image_size"] for instance in instances] | |
| ( | |
| new_input_ids, | |
| new_labels, | |
| new_attention_mask, | |
| new_position_ids, | |
| im_aux_attention_masks_list, | |
| ) = prepare_multimodal_data( | |
| input_ids, | |
| labels, | |
| attention_mask, | |
| image_sizes, | |
| image_token_len, | |
| image_aux_token_len_list, | |
| max_length, | |
| ) | |
| batch = dict( | |
| input_ids=new_input_ids, | |
| labels=new_labels, | |
| attention_mask=new_attention_mask, | |
| position_ids=new_position_ids, | |
| image_aux_attention_masks_list=im_aux_attention_masks_list, | |
| ) | |
| batch["image_sizes"] = image_sizes | |
| if "image_aux_list" in instances[0]: | |
| image_aux_list = [instance["image_aux_list"] for instance in instances] | |
| image_aux_list = [ | |
| list(batch_image_aux) for batch_image_aux in zip(*image_aux_list) | |
| ] | |
| if all( | |
| x is not None and x.shape == image_aux_list[0][0].shape | |
| for x in image_aux_list[0] | |
| ): | |
| batch["images"] = [ | |
| torch.stack(image_aux) for image_aux in image_aux_list | |
| ] | |
| else: | |
| batch["images"] = image_aux_list | |
| return batch | |
| def make_supervised_data_module( | |
| tokenizer: transformers.PreTrainedTokenizer, data_args # pyre-fixme | |
| ) -> Dict: # pyre-fixme | |
| """Make dataset and collator for supervised fine-tuning.""" | |
| train_dataset = LazySupervisedDataset( | |
| tokenizer=tokenizer, data_path=data_args.data_path, data_args=data_args | |
| ) | |
| data_collator_kwargs = { | |
| "tokenizer": tokenizer, | |
| } | |
| if hasattr(data_args, "image_token_len"): | |
| data_collator_kwargs["image_token_len"] = data_args.image_token_len | |
| if hasattr(data_args, "vision_tower_aux_token_len_list"): | |
| data_collator_kwargs["image_aux_token_len_list"] = ( | |
| data_args.vision_tower_aux_token_len_list | |
| ) | |
| else: | |
| data_collator_kwargs["image_aux_token_len_list"] = [data_args.image_token_len] | |
| if hasattr(data_args, "image_position"): | |
| data_collator_kwargs["image_position"] = data_args.image_position | |
| data_collator = DataCollatorForSupervisedDataset(**data_collator_kwargs) # pyre-fixme | |
| return dict( | |
| train_dataset=train_dataset, eval_dataset=None, data_collator=data_collator | |
| ) | |
| def train() -> None: | |
| dist.init_process_group(backend="nccl", timeout=datetime.timedelta(hours=8)) | |
| parser = transformers.HfArgumentParser( | |
| (ModelArguments, DataArguments, TrainingArguments) | |
| ) | |
| model_args, data_args, training_args = parser.parse_args_into_dataclasses() | |
| global_rank = get_global_rank() | |
| local_rank = get_local_rank() | |
| torch.distributed.barrier() | |
| # pyre-fixme[16]: `DataClass` has no attribute `output_model_local_path`. | |
| training_args.output_dir = model_args.output_model_filename | |
| # pyre-fixme[16]: `DataClass` has no attribute `local_dir`. | |
| model_args.local_dir = model_args.output_model_filename | |
| bnb_model_from_pretrained_args = {} | |
| # pyre-fixme[16]: `DataClass` has no attribute `vision_tower`. | |
| if model_args.vision_tower_aux_list is not None: | |
| if "cambrian" in model_args.input_model_filename.lower(): | |
| if "qwen" in model_args.input_model_filename.lower(): | |
| model = CambrianQwenForCausalLM.from_pretrained( # pyre-fixme | |
| model_args.input_model_filename, # pyre-fixme | |
| torch_dtype=(torch.bfloat16 if training_args.bf16 else None), # pyre-fixme | |
| **bnb_model_from_pretrained_args, | |
| ) | |
| else: | |
| # pyre-fixme[16]: `CambrianLlamaForCausalLM` has no attribute | |
| # `from_pretrained`. | |
| model = CambrianLlamaForCausalLM.from_pretrained( | |
| # pyre-fixme[16]: `DataClass` has no attribute `input_model_local_path`. | |
| model_args.input_model_filename, | |
| **bnb_model_from_pretrained_args, | |
| ) | |
| else: | |
| raise NotImplementedError( | |
| f"{model_args.model_name_or_path} is not supported yet" | |
| ) | |
| else: | |
| model = transformers.LlamaForCausalLM.from_pretrained( | |
| model_args.input_model_filename, | |
| **bnb_model_from_pretrained_args, | |
| ) | |
| model.config.use_cache = False | |
| # pyre-fixme[16]: `DataClass` has no attribute `freeze_backbone`. | |
| if model_args.freeze_backbone: | |
| model.model.requires_grad_(False) | |
| # pyre-fixme[16]: `DataClass` has no attribute `gradient_checkpointing`. | |
| if training_args.gradient_checkpointing: | |
| if hasattr(model, "enable_input_require_grads"): | |
| model.enable_input_require_grads() | |
| else: | |
| # pyre-fixme[3]: Return type must be annotated. | |
| # pyre-fixme[2]: Parameter must be annotated. | |
| def make_inputs_require_grad(module, input, output): | |
| output.requires_grad_(True) | |
| model.get_input_embeddings().register_forward_hook(make_inputs_require_grad) | |
| tokenizer = transformers.AutoTokenizer.from_pretrained( | |
| model_args.input_model_filename, | |
| # pyre-fixme[16]: `DataClass` has no attribute `model_max_length`. | |
| model_max_length=training_args.model_max_length, | |
| padding_side="right", | |
| use_fast=False, | |
| ) | |
| # pyre-fixme[16]: `DataClass` has no attribute `version`. | |
| if model_args.version == "v0": | |
| if tokenizer.pad_token is None: | |
| smart_tokenizer_and_embedding_resize( | |
| special_tokens_dict=dict(pad_token="[PAD]"), | |
| tokenizer=tokenizer, | |
| model=model, | |
| ) | |
| elif model_args.version == "v0.5": | |
| tokenizer.pad_token = tokenizer.unk_token | |
| elif model_args.version == "v1": | |
| tokenizer.pad_token = tokenizer.unk_token | |
| if model_args.version in conversation_lib.conv_templates: | |
| conversation_lib.default_conversation = conversation_lib.conv_templates[ | |
| model_args.version | |
| ] | |
| else: | |
| conversation_lib.default_conversation = conversation_lib.conv_templates[ | |
| "vicuna_v1" | |
| ] | |
| elif model_args.version == "phi3": | |
| tokenizer.pad_token = tokenizer.unk_token | |
| if model_args.version in conversation_lib.conv_templates: | |
| conversation_lib.default_conversation = conversation_lib.conv_templates[ | |
| model_args.version | |
| ] | |
| else: | |
| conversation_lib.default_conversation = conversation_lib.conv_templates[ | |
| "vicuna_v1" | |
| ] | |
| elif ( | |
| model_args.version == "llama3" | |
| or model_args.version == "llama3_1" | |
| or model_args.version == "llama3_2" | |
| ): | |
| tokenizer.pad_token = "<|reserved_special_token_0|>" | |
| tokenizer.pad_token_id = 128002 | |
| if model_args.version in conversation_lib.conv_templates: | |
| conversation_lib.default_conversation = conversation_lib.conv_templates[ | |
| model_args.version | |
| ] | |
| else: | |
| conversation_lib.default_conversation = conversation_lib.conv_templates[ | |
| "vicuna_v1" | |
| ] | |
| elif model_args.version == "qwen": | |
| if tokenizer.unk_token is not None: | |
| tokenizer.pad_token = tokenizer.unk_token | |
| conversation_lib.default_conversation = conversation_lib.conv_templates[ | |
| model_args.version | |
| ] | |
| else: | |
| if tokenizer.pad_token is None: | |
| print(f"Adding pad token as '<pad>'") | |
| smart_tokenizer_and_embedding_resize( | |
| special_tokens_dict=dict(pad_token="<pad>"), | |
| tokenizer=tokenizer, | |
| model=model, | |
| ) | |
| if model_args.version in conversation_lib.conv_templates: | |
| conversation_lib.default_conversation = conversation_lib.conv_templates[ | |
| model_args.version | |
| ] | |
| else: | |
| conversation_lib.default_conversation = conversation_lib.conv_templates[ | |
| "vicuna_v1" | |
| ] | |
| print(f"Using conversation format: {conversation_lib.default_conversation.version}") | |
| # pyre-fixme[16]: `DataClass` has no attribute `vision_tower_aux_list`. | |
| if model_args.vision_tower_aux_list is not None: | |
| # pyre-fixme[16]: `DataClass` has no attribute `unfreeze_mm_vision_tower`. | |
| model_args.unfreeze_mm_vision_tower = training_args.unfreeze_mm_vision_tower | |
| model_args.vision_tower_aux_list = json.loads(model_args.vision_tower_aux_list) | |
| # pyre-fixme[16]: `DataClass` has no attribute `vision_tower_aux_token_len_list`. | |
| model_args.vision_tower_aux_token_len_list = json.loads( | |
| model_args.vision_tower_aux_token_len_list | |
| ) | |
| # pyre-fixme[16]: `DataClass` has no attribute `query_num_list`. | |
| model_args.query_num_list = json.loads(model_args.query_num_list) | |
| model.get_model().initialize_vision_modules( | |
| model_args=model_args, | |
| fsdp=None, # FSDP or not, flag should be the same as None to avoid creation error | |
| ) | |
| model.config.unfreeze_mm_vision_tower = training_args.unfreeze_mm_vision_tower | |
| vision_tower_aux_list = None | |
| if model_args.vision_tower_aux_list is not None: | |
| vision_tower_aux_list = model.get_vision_tower_aux_list() | |
| if not training_args.unfreeze_mm_vision_tower: | |
| # vision_tower.to(dtype=torch.bfloat16, device=training_args.device) | |
| if vision_tower_aux_list is not None: | |
| for vision_tower_aux in vision_tower_aux_list: | |
| vision_tower_aux.to( | |
| dtype=torch.bfloat16, device=training_args.device # pyre-fixme | |
| ) | |
| else: | |
| # vision_tower.to(device=training_args.device) | |
| if vision_tower_aux_list is not None: | |
| for vision_tower_aux in vision_tower_aux_list: | |
| vision_tower_aux.to(device=training_args.device) | |
| # vision_tower_aux.to(dtype=torch.bfloat16, device=training_args.device) | |
| # data_args.image_processor = vision_tower.image_processor | |
| if vision_tower_aux_list is not None: | |
| data_args.image_processor_aux_list = [ # pyre-fixme | |
| vision_tower_aux.image_processor | |
| for vision_tower_aux in vision_tower_aux_list | |
| ] | |
| data_args.is_multimodal = True # pyre-fixme | |
| model.config.image_aspect_ratio = data_args.image_aspect_ratio # pyre-fixme | |
| model.config.tokenizer_padding_side = tokenizer.padding_side | |
| model.config.tokenizer_model_max_length = tokenizer.model_max_length | |
| model.config.image_position = data_args.image_position # pyre-fixme | |
| data_args.mm_use_im_start_end = model_args.mm_use_im_start_end # pyre-fixme | |
| data_args.mm_use_im_patch_token = model_args.mm_use_im_patch_token # pyre-fixme | |
| # pyre-fixme | |
| model.config.tune_mm_mlp_adapter = training_args.tune_mm_mlp_adapter = ( | |
| model_args.tune_mm_mlp_adapter | |
| ) | |
| if model_args.tune_mm_mlp_adapter: | |
| model.requires_grad_(False) | |
| # for p in model.get_model().mm_projector.parameters(): | |
| # p.requires_grad = True | |
| tune_modules = [ | |
| "mm_projector", | |
| "pos_emb", | |
| "vision_sampler", | |
| "vision_sampler_layers", | |
| "vision_query", | |
| "image_newline", | |
| ] | |
| for name, param in model.named_parameters(): | |
| if any(listed_name in name for listed_name in tune_modules): | |
| param.requires_grad = True | |
| model.config.freeze_mm_mlp_adapter = training_args.freeze_mm_mlp_adapter # pyre-fixme | |
| if training_args.freeze_mm_mlp_adapter: | |
| for p in model.get_model().mm_projector.parameters(): | |
| p.requires_grad = False | |
| if training_args.unfreeze_mm_vision_tower: | |
| if vision_tower_aux_list is not None: | |
| for vision_tower_aux in vision_tower_aux_list: | |
| for p in vision_tower_aux.parameters(): | |
| p.requires_grad = True | |
| model.config.mm_use_im_start_end = model_args.mm_use_im_start_end = ( | |
| model_args.mm_use_im_start_end | |
| ) | |
| model.config.image_token_len = model_args.image_token_len = ( # pyre-fixme | |
| model_args.image_token_len | |
| ) | |
| model.config.mm_projector_lr = training_args.mm_projector_lr # pyre-fixme | |
| model.config.mm_vision_sampler_lr = training_args.mm_vision_sampler_lr # pyre-fixme | |
| model.config.mm_vision_tower_lr = training_args.mm_vision_tower_lr # pyre-fixme | |
| training_args.use_im_start_end = model_args.mm_use_im_start_end # pyre-fixme | |
| model.config.mm_use_im_patch_token = model_args.mm_use_im_patch_token | |
| model.config.vision_tower_aux_token_len_list = ( | |
| data_args.vision_tower_aux_token_len_list | |
| ) = model_args.vision_tower_aux_token_len_list | |
| model.config.image_token_len = model_args.image_token_len | |
| model.config.is_st_sampler = model_args.is_st_sampler # pyre-fixme | |
| data_args.image_token_len = model_args.image_token_len | |
| model.initialize_vision_tokenizer(model_args, tokenizer=tokenizer) | |
| total_params = sum(p.numel() for p in model.get_model().parameters()) | |
| trainable_params = sum( | |
| p.numel() for p in model.get_model().parameters() if p.requires_grad | |
| ) | |
| model.to(torch.bfloat16) | |
| # pyre-fixme | |
| def convert_bn_to_float(model): | |
| if isinstance(model, torch.nn.modules.batchnorm._BatchNorm): | |
| return model.float() | |
| for child_name, child in model.named_children(): | |
| model.add_module(child_name, convert_bn_to_float(child)) | |
| return model | |
| model = convert_bn_to_float(model) | |
| os.environ[f"FSDP_USE_ORIG_PARAMS"] = "true" | |
| # pyre-fixme[16]: `DataClass` has no attribute `fsdp_config`. | |
| training_args.fsdp_config["use_orig_params"] = True | |
| data_module = make_supervised_data_module(tokenizer=tokenizer, data_args=data_args) | |
| callbacks = [] | |
| # configure TensorboardCallback to upload to manifold | |
| callbacks.append( | |
| TensorBoardCallback( | |
| SummaryWriter( | |
| log_dir=os.path.join( | |
| # pyre-fixme[16]: `DataClass` has no attribute | |
| # `output_model_filename`. | |
| model_args.output_model_filename, | |
| TENSORBOARD_LOG_DIR_NAME, | |
| ), | |
| comment="", | |
| purge_step=None, | |
| max_queue=10, | |
| flush_secs=120, | |
| filename_suffix=str(uuid.uuid4()), | |
| ) | |
| ) | |
| ) | |
| trainer = LLaVATrainer( | |
| model=model, | |
| tokenizer=tokenizer, | |
| args=training_args, | |
| callbacks=callbacks, | |
| **data_module, | |
| ) | |
| # pyre-fixme[16]: `DataClass` has no attribute `output_dir`. | |
| if list(pathlib.Path(training_args.output_dir).glob("checkpoint-*")): | |
| # pyre-fixme[16]: `LLaVATrainer` has no attribute `train`. | |
| trainer.train(resume_from_checkpoint=True) | |
| else: | |
| trainer.train() | |
| # pyre-fixme[16]: `LLaVATrainer` has no attribute `save_state`. | |
| trainer.save_state() | |
| safe_save_model_for_hf_trainer( | |
| trainer=trainer, | |
| # pyre-fixme[16]: `DataClass` has no attribute `output_model_local_path`. | |
| output_dir=model_args.output_model_filename, | |
| ) | |
| if __name__ == "__main__": | |
| train() | |