fakeVLM / utils.py
liu123-2's picture
Upload folder using huggingface_hub
4ce9939 verified
Raw
History Blame Contribute Delete
6.91 kB
import math
from typing import List, Dict, Optional
from deepspeed import zero
from deepspeed.runtime.zero.partition_parameters import ZeroParamStatus
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
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)
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)