Download code/multi_vlm2vec_train/utils/utils.py from WaitXian/liqi: direct link, hf CLI and curl.
- Browser
- Download file 9.85 kB
-
https://huggingface.co/WaitXian/liqi/resolve/main/code/multi_vlm2vec_train/utils/utils.py
- Command line
-
hf download hf://WaitXian/liqi/code/multi_vlm2vec_train/utils/utils.py
-
curl -L -o utils.py https://huggingface.co/WaitXian/liqi/resolve/main/code/multi_vlm2vec_train/utils/utils.py
9.85 kB
| import math | |
| from typing import List, Dict, Optional | |
| from deepspeed import zero | |
| from deepspeed.runtime.zero.partition_parameters import ZeroParamStatus | |
| import os | |
| import torch | |
| import torch.distributed as dist | |
| from torch.utils.data import Sampler | |
| import transformers | |
| from transformers import Trainer | |
| from transformers.trainer import has_length | |
| import numpy as np | |
| from PIL import Image | |
| import base64 | |
| import io | |
| from io import BytesIO | |
| from logger import get_logger | |
| logger = get_logger(__name__) | |
| class NoTextOnlyBatchSampler(Sampler): | |
| r""" | |
| Sampler that tries its best to sample batches such that no batch has only | |
| text (unimodal) data. This is necessary for training with deepspeed. | |
| """ | |
| def __init__( | |
| self, | |
| batch_size: int, | |
| world_size: int, | |
| is_text_only: Optional[List[bool]] = None, | |
| generator=None, | |
| ): | |
| if is_text_only is None: | |
| raise ValueError("`is_text_only` must be provided.") | |
| self.batch_size = batch_size | |
| self.world_size = world_size | |
| self.is_text_only = is_text_only | |
| self.generator = generator | |
| self.mega_batch_size = batch_size * world_size | |
| def __len__(self): | |
| return len(self.is_text_only) | |
| def __iter__(self): | |
| # mm: multimodal, entry that has both text and image/video | |
| # uni: unimodal, entry that has only text | |
| mm_indices = [i for i, is_text_only in enumerate(self.is_text_only) if not is_text_only] | |
| uni_indices = [i for i, is_text_only in enumerate(self.is_text_only) if is_text_only] | |
| num_batches = math.ceil((len(mm_indices) + len(uni_indices)) / self.mega_batch_size) | |
| if len(mm_indices) < num_batches: | |
| raise ValueError( | |
| f"{len(mm_indices)} multimodal entries, {len(num_batches)} batches. " | |
| "Not enough multimodal data in the dataset, or the batch size is too small. " | |
| "There will be at least one batch that is text-only, which doesn't work with deepspeed. " | |
| "Try increasing the batch size first." | |
| ) | |
| # shuffle indices | |
| mm_indices = [mm_indices[i] for i in torch.randperm(len(mm_indices), generator=None).tolist()] | |
| uni_indices = [uni_indices[i] for i in torch.randperm(len(uni_indices), generator=None).tolist()] | |
| # distribute indices into batches | |
| num_uni_indices_in_mega_batch = [len(uni_indices) // num_batches] * num_batches | |
| for i in range(len(uni_indices) % num_batches): | |
| num_uni_indices_in_mega_batch[i] += 1 | |
| mega_batches = [] | |
| cur_uni_index = 0 | |
| cur_mm_index = 0 | |
| for i, num_uni_indices in enumerate(num_uni_indices_in_mega_batch): | |
| mega_batch = [] | |
| mega_batch.extend(uni_indices[cur_uni_index:cur_uni_index + num_uni_indices]) | |
| cur_uni_index += num_uni_indices | |
| assert len(mega_batch) < self.mega_batch_size | |
| if i < num_batches - 1: | |
| increment = self.mega_batch_size - len(mega_batch) | |
| mega_batch.extend( | |
| mm_indices[cur_mm_index:cur_mm_index + increment] | |
| ) | |
| cur_mm_index += increment | |
| else: # last batch | |
| mega_batch.extend(mm_indices[cur_mm_index:]) | |
| assert len(mega_batch) <= self.mega_batch_size, "Last batch is too big." | |
| mega_batches.append(mega_batch) | |
| mega_batch_indices = torch.randperm(len(mega_batches), generator=self.generator) | |
| mega_batches = [mega_batches[i] for i in mega_batch_indices] | |
| indices = [i for mega_batch in mega_batches for i in mega_batch] | |
| return iter(indices) | |
| class TrainerWithCustomSampler(Trainer): | |
| def _get_train_sampler(self) -> Optional[torch.utils.data.Sampler]: | |
| if self.train_dataset is None or not has_length(self.train_dataset): | |
| return None | |
| is_text_only = self.train_dataset.is_text_only | |
| return NoTextOnlyBatchSampler( | |
| self.args.train_batch_size, | |
| world_size=self.args.world_size * self.args.gradient_accumulation_steps, | |
| is_text_only=is_text_only, | |
| ) | |
| def _get_eval_sampler(self, eval_dataset: torch.utils.data.Dataset) -> Optional[torch.utils.data.Sampler]: | |
| is_text_only = eval_dataset.is_text_only | |
| return NoTextOnlyBatchSampler( | |
| self.args.eval_batch_size, | |
| world_size=self.args.world_size, | |
| is_text_only=is_text_only, | |
| ) | |
| def find_all_linear_names(named_modules: Dict, target_modules: List[str]): | |
| cls = torch.nn.Linear | |
| lora_module_names = set() | |
| for name, module in named_modules.items(): | |
| if not any([module_name in name for module_name in target_modules]): | |
| continue | |
| if isinstance(module, cls): | |
| lora_module_names.add(name) | |
| for name in list(lora_module_names): | |
| if 'lm_head' in name: # needed for 16-bit | |
| lora_module_names.remove(name) | |
| if 'aggregate' in name: | |
| lora_module_names.remove(name) | |
| return list(lora_module_names) | |
| def rank0_print(*args): | |
| if dist.is_initialized(): | |
| if dist.get_rank() == 0: | |
| print(*args) | |
| def maybe_zero_3(param): | |
| if hasattr(param, "ds_id"): | |
| with zero.GatheredParameters([param]): | |
| param = param.data.detach().cpu().clone() | |
| else: | |
| param = param.detach().cpu().clone() | |
| return param | |
| # Borrowed from peft.utils.get_peft_model_state_dict | |
| def get_peft_state_maybe_zero_3(named_params, bias): | |
| if bias == "none": | |
| to_return = {k: t for k, t in named_params if "lora_" in k} | |
| elif bias == "all": | |
| to_return = {k: t for k, t in named_params if "lora_" in k or "bias" in k} | |
| elif bias == "lora_only": | |
| to_return = {} | |
| maybe_lora_bias = {} | |
| lora_bias_names = set() | |
| for k, t in named_params: | |
| if "lora_" in k: | |
| to_return[k] = t | |
| bias_name = k.split("lora_")[0] + "bias" | |
| lora_bias_names.add(bias_name) | |
| elif "bias" in k: | |
| maybe_lora_bias[k] = t | |
| for k, t in maybe_lora_bias: | |
| if bias_name in lora_bias_names: | |
| to_return[bias_name] = t | |
| else: | |
| raise NotImplementedError | |
| to_return = {k: maybe_zero_3(v) for k, v in to_return.items()} | |
| return to_return | |
| def safe_save_model_for_hf_trainer(trainer: transformers.Trainer, output_dir: str): | |
| """Collects the state dict and dump to disk.""" | |
| if trainer.deepspeed: | |
| torch.cuda.synchronize() | |
| trainer.save_model(output_dir) | |
| return | |
| state_dict = trainer.model.state_dict() | |
| if trainer.args.should_save: | |
| cpu_state_dict = { | |
| key: value.cpu() | |
| for key, value in state_dict.items() | |
| } | |
| del state_dict | |
| trainer._save(output_dir, state_dict=cpu_state_dict) | |
| def safe_save_model_mlp_for_hf_trainer(trainer: transformers.Trainer, output_dir: str): | |
| """Collects the state dict and dump to disk.""" | |
| ## save partial fc | |
| # save model | |
| if trainer.loss_model: | |
| from models.partial_fc import PFC_NAME, PFC_OPTIMIZER_NAME, PFC_SCHEDULER_NAME | |
| logger.info("start to sync pfc state dict") | |
| trainer.loss_model.sync_state_dict() | |
| logger.info("end sync pfc state dict") | |
| logger.info("start to sync pfc optimizer state dict") | |
| trainer.loss_model_opt.sync_state_dict() | |
| logger.info("end sync pfc optimizer state dict") | |
| trainer.loss_model.save_state_dict(output_dir) | |
| logger.info("save pfc model done") | |
| trainer.loss_model_opt.save_state_dict(output_dir) | |
| logger.info("save pfc optimizer done") | |
| torch.save(trainer.loss_model_scheduler.state_dict(), os.path.join(output_dir, PFC_SCHEDULER_NAME)) | |
| logger.info("save pfc scheduler done") | |
| trainer.loss_model.clear_state_dict() | |
| trainer.loss_model_opt.clear_state_dict() | |
| if trainer.deepspeed: | |
| torch.cuda.synchronize() | |
| trainer.save_model(output_dir) | |
| return | |
| state_dict = trainer.model.state_dict() | |
| if trainer.args.should_save: | |
| cpu_state_dict = { | |
| key: value.cpu() | |
| for key, value in state_dict.items() | |
| } | |
| del state_dict | |
| trainer._save(output_dir, state_dict=cpu_state_dict) | |
| def image_to_base64(image): | |
| # Determine the type of the image and convert it to PIL format if necessary | |
| if isinstance(image, torch.Tensor): | |
| # Convert Torch tensor to PIL Image | |
| if image.is_floating_point(): | |
| image = ((image - image.min()) / (image.max() - image.min()) * 255).byte() ## 归一化之后乘以255 | |
| image = image.permute(1, 2, 0).detach().cpu().numpy() | |
| image = Image.fromarray(image) | |
| elif isinstance(image, np.ndarray): | |
| # Convert NumPy array to PIL Image | |
| image = Image.fromarray(image) | |
| elif isinstance(image, Image.Image): | |
| # Image is already in PIL format | |
| pass | |
| else: | |
| raise TypeError("Unsupported image type. Supported types are: torch.Tensor, np.ndarray, PIL.Image.") | |
| output_buffer = BytesIO() # 创建一个字节流缓冲区 | |
| image.save(output_buffer, format="PNG") # 将图像保存到缓冲区 | |
| byte_data = output_buffer.getvalue() # 获取字节数据 | |
| base64_image = base64.b64encode(byte_data).decode('utf-8') # 将字节数据编码为 Base64 字符串 | |
| return f'data:image/{"PNG"};base64,' + base64_image # 返回带有 MIME 类型的 | |
| def base64_to_pil(base64_string): | |
| # Convert base64 string to bytes | |
| image_data = base64.b64decode(base64_string) | |
| # Use BytesIO to handle the byte data and open it as an image | |
| image = Image.open(io.BytesIO(image_data)) | |
| return image |