Files
wehub-resource-sync a203934033
Lint test / lint (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:34:58 +08:00

624 lines
23 KiB
Python

# Copyright (c) ModelScope Contributors. All rights reserved.
import accelerate
import copy
import os
import torch
import torch.distributed as dist
import torch.nn as nn
import torch.nn.functional as F
import transformers
from accelerate.utils import find_device
from contextlib import contextmanager
from functools import wraps
from packaging import version
from peft import PeftModel
from torch.nn import BCEWithLogitsLoss, CrossEntropyLoss, MSELoss
from torch.nn.parallel import DistributedDataParallel as DDP
from transformers import PreTrainedModel, dynamic_module_utils, trainer
from transformers.modeling_outputs import SequenceClassifierOutputWithPast
from types import MethodType
from typing import Any, Dict, List, Optional, Union
from swift.utils import (HfConfigFactory, deep_getattr, get_device_count, get_dist_setting, get_last_valid_indices,
get_logger, get_position_ids_from_cu_seqlens, is_mp, is_mp_ddp, safe_ddp_context, to_device,
to_float_dtype)
logger = get_logger()
transformers_version = version.parse(transformers.__version__)
transformers_5 = transformers_version >= version.parse('5.0.0')
def patch_fixed_float_dtype(module: torch.nn.Module, dtype):
"""Patch the module, to make sure the consisitent dtype."""
def get_float_dtype_hook(dtype):
def _float_dtype_hook(module, input, output):
return to_float_dtype(output, dtype)
return _float_dtype_hook
module.register_forward_hook(get_float_dtype_hook(dtype))
def patch_fixed_device(module: torch.nn.Module, device):
"""Move the output to the specific device"""
def get_device_hook(device):
def _device_hook(module, input, output):
return to_device(output, device)
return _device_hook
module.register_forward_hook(get_device_hook(device))
def patch_output_clone(module: torch.nn.Module):
"""Clone the output, to avoid the inplace problem"""
def _clone_hook(module, input, output):
return output.requires_grad_(True).clone()
module.register_forward_hook(_clone_hook)
def patch_get_input_embeddings(model, embedding_keys: str):
def get_input_embeddings(self) -> nn.Module:
return deep_getattr(model, embedding_keys)
model.get_input_embeddings = MethodType(get_input_embeddings, model)
def patch_output_normalizer(module: torch.nn.Module, model_meta):
def lm_head_forward(self, hidden_states):
return hidden_states
lm_heads = ['lm_head', 'output', 'embed_out', 'output_layer']
lm_head_model = get_lm_head_model(module, model_meta=model_meta, lm_heads=lm_heads)
found = False
for lm_head in lm_heads:
if hasattr(lm_head_model, lm_head):
getattr(lm_head_model, lm_head).forward = MethodType(lm_head_forward, getattr(lm_head_model, lm_head))
found = True
break
assert found, 'Cannot find the proper lm_head name'
def _output_embedding_hook(module, args, kwargs, output):
attention_mask = kwargs.get('attention_mask', None)
hidden_states = output.logits
sequence_lengths = -1 if attention_mask is None else get_last_valid_indices(attention_mask)
embeddings = hidden_states[torch.arange(hidden_states.shape[0], device=hidden_states.device), sequence_lengths]
embeddings = F.normalize(embeddings, p=2, dim=1)
return {
'last_hidden_state': embeddings.contiguous(),
}
lm_head_model.register_forward_hook(_output_embedding_hook, with_kwargs=True)
def patch_output_to_input_device(module: torch.nn.Module):
"""Patch the module, to make sure the output is in the same device with the input.
Args:
module: The module to be patched
"""
def _output_to_input_device_hook(module, args, kwargs, output):
device = find_device(args) or find_device(kwargs)
return to_device(output, device)
module.register_forward_hook(_output_to_input_device_hook, with_kwargs=True)
@contextmanager
def patch_device_map():
if not hasattr(PreTrainedModel, '_get_no_split_modules'):
yield
return
_get_no_split_modules = PreTrainedModel._get_no_split_modules
def _new_get_no_split_modules(self, device_map: str):
for module in self.modules():
if isinstance(module, PreTrainedModel) and module._no_split_modules is None:
module.__class__._no_split_modules = []
return _get_no_split_modules(self, device_map)
PreTrainedModel._get_no_split_modules = _new_get_no_split_modules
try:
yield
finally:
PreTrainedModel._get_no_split_modules = _get_no_split_modules
@contextmanager
def patch_ignore_check_imports():
import transformers.dynamic_module_utils as td
def _check_imports(filename) -> List[str]:
return td.get_relative_imports(filename)
_old_check_imports = td.check_imports
td.check_imports = _check_imports
try:
yield
finally:
td.check_imports = _old_check_imports
def get_lm_head_model(model, model_meta=None, lm_heads=None):
if isinstance(model, PeftModel):
model = model.model
model_meta = model_meta or model.model_meta
if lm_heads is None:
lm_heads = ['lm_head', 'output', 'embed_out', 'output_layer']
llm_prefix_list = getattr(model_meta.model_arch, 'language_model', None)
prefix_list = []
if llm_prefix_list:
prefix_list = llm_prefix_list[0].split('.')
current_model = model
for prefix in prefix_list:
current_model = getattr(current_model, prefix)
for lm_head in lm_heads:
if hasattr(current_model, lm_head):
return current_model
return model
def transformers_seq_cls_forward(self, *args, origin_forward, padding_side=None, **kwargs):
labels = kwargs.pop('labels', None)
return_dict = kwargs.pop('return_dict', None)
return_dict = return_dict if return_dict is not None else self.config.use_return_dict
input_ids = kwargs.get('input_ids')
inputs_embeds = kwargs.get('inputs_embeds')
output = origin_forward(*args, **kwargs)
if hasattr(output, 'logits'):
output.logits = output.logits.to(self.score.weight.dtype)
elif 'last_hidden_state' in output:
output.logits = output['last_hidden_state'].to(self.score.weight.dtype)
logits = self.score(output.logits)
if input_ids is not None:
batch_size = input_ids.shape[0]
else:
batch_size = inputs_embeds.shape[0]
if padding_side == 'left':
pooled_logits = logits[:, -1]
else:
pad_token_id = HfConfigFactory.get_config_attr(self.config, 'pad_token_id')
if pad_token_id is None and batch_size != 1:
raise ValueError('Cannot handle batch sizes > 1 if no padding token is defined.')
if pad_token_id is None:
sequence_lengths = -1
else:
if input_ids is not None:
# if no pad token found, use modulo instead of reverse indexing for ONNX compatibility
sequence_lengths = torch.eq(input_ids, pad_token_id).int().argmax(-1) - 1
sequence_lengths = sequence_lengths % input_ids.shape[-1]
elif kwargs.get('attention_mask') is not None:
sequence_lengths = get_last_valid_indices(kwargs['attention_mask'])
else:
sequence_lengths = -1
if isinstance(sequence_lengths, torch.Tensor):
sequence_lengths = sequence_lengths.to(logits.device)
pooled_logits = logits[torch.arange(batch_size, device=logits.device), sequence_lengths]
loss = None
if labels is not None:
labels = labels.to(logits.device)
if self.config.problem_type is None:
if self.num_labels == 1:
self.config.problem_type = 'regression'
elif self.num_labels > 1 and (labels.dtype == torch.long or labels.dtype == torch.int):
self.config.problem_type = 'single_label_classification'
else:
self.config.problem_type = 'multi_label_classification'
if self.config.problem_type == 'regression':
loss_fct = MSELoss()
if self.num_labels == 1:
loss = loss_fct(pooled_logits.squeeze(), labels.squeeze())
else:
loss = loss_fct(pooled_logits, labels)
elif self.config.problem_type == 'single_label_classification':
loss_fct = CrossEntropyLoss()
loss = loss_fct(pooled_logits.view(-1, self.num_labels), labels.view(-1))
elif self.config.problem_type == 'multi_label_classification':
loss_fct = BCEWithLogitsLoss()
loss = loss_fct(pooled_logits, labels)
if not return_dict:
output = (pooled_logits, ) + output[1:]
return ((loss, ) + output) if loss is not None else output
return SequenceClassifierOutputWithPast(
loss=loss,
logits=pooled_logits,
past_key_values=output.past_key_values,
hidden_states=output.hidden_states,
attentions=output.attentions,
)
def _patch_sequence_classification(model, model_meta):
hidden_size = HfConfigFactory.get_config_attr(model.config, 'hidden_size')
initializer_range = HfConfigFactory.get_config_attr(model.config, 'initializer_range')
lm_heads = ['lm_head', 'output', 'embed_out', 'output_layer']
lm_head_model = get_lm_head_model(model, model_meta, lm_heads)
lm_head_model.num_labels = model.config.num_labels
for lm_head in lm_heads:
if hasattr(lm_head_model, lm_head):
hidden_size = getattr(lm_head_model, lm_head).in_features
setattr(lm_head_model, lm_head, nn.Identity())
break
lm_head_model.score = nn.Linear(hidden_size, lm_head_model.num_labels, bias=False, dtype=lm_head_model.dtype)
if lm_head_model.score.weight.device == torch.device('meta'):
lm_head_model.score.to_empty(device='cpu')
lm_head_model.score.weight.data.normal_(mean=0.0, std=initializer_range)
origin_forward = lm_head_model.forward
@wraps(origin_forward.__func__)
def new_forward(self, *args, **kwargs):
return transformers_seq_cls_forward(self, *args, origin_forward=origin_forward, **kwargs)
lm_head_model.forward = MethodType(new_forward, lm_head_model)
@contextmanager
def patch_automodel_for_sequence_classification(model_info=None,
model_meta=None,
patch_from_pretrained=True,
patch_missing_init=True,
**kwargs):
"""
Context manager for patching AutoModel sequence classification.
Args:
model_info: Model information
model_meta: Model metadata
patch_from_pretrained (bool): Whether to patch PreTrainedModel.from_pretrained
patch_missing_init (bool): Whether to patch missing __init__ methods
**kwargs: Additional keyword arguments
"""
model_config = kwargs.get('model_config', None)
from_pretrained = PreTrainedModel.from_pretrained.__func__
# Patch 1: from_pretrained method
if patch_from_pretrained:
@classmethod
def _new_from_pretrained(cls, *args, **kwargs):
__init__ = cls.__init__
def __new_init__(self, *args, **kwargs):
__init__(self, *args, **kwargs)
_patch_sequence_classification(self, model_meta)
cls.__init__ = __new_init__
if hasattr(cls, '_tp_plan'): # fix tp_plan
cls._tp_plan = cls._tp_plan or {}
res = from_pretrained(cls, *args, **kwargs)
cls.__init__ = __init__
return res
else:
_new_from_pretrained = None
# Patch 2: missing __init__ methods
# https://github.com/modelscope/ms-swift/pull/5820
patched_classes = []
if patch_missing_init:
def get_all_subclasses(cls, include_root=True):
subclass_list = []
def recurse(cl):
for subclass in cl.__subclasses__():
subclass_list.append(subclass)
recurse(subclass)
recurse(cls)
ret = set(subclass_list)
if include_root:
ret.add(cls)
return ret
def create_default_init(cls):
"""Create a default __init__ method that calls super().__init__"""
def default_init(self, *args, **kwargs):
super(cls, self).__init__(*args, **kwargs)
return default_init
if model_config is not None:
# we should import in advance so that get_all_subclasses can find the class
archs = model_config.architectures
for arch in archs:
try:
getattr(transformers, arch)
except AttributeError:
continue
for subclass in get_all_subclasses(torch.nn.modules.module.Module):
if '__init__' not in subclass.__dict__:
subclass.__init__ = create_default_init(subclass)
patched_classes.append(subclass)
if patch_from_pretrained:
PreTrainedModel.from_pretrained = _new_from_pretrained
try:
yield
finally:
# Restore patches
if patch_from_pretrained:
PreTrainedModel.from_pretrained = classmethod(from_pretrained)
if patch_missing_init:
for subclass in patched_classes:
try:
if '__init__' in subclass.__dict__:
del subclass.__init__
except (AttributeError, TypeError):
pass
@contextmanager
def patch_automodel(model_info, model_meta, auto_model_cls, return_dummy_model, **kwargs):
from_pretrained = PreTrainedModel.from_pretrained.__func__
@classmethod
def _new_from_pretrained(cls, *args, **kwargs):
if 'AutoAWQFor' in auto_model_cls.__name__:
kwargs.pop('use_cache', None)
if model_info.quant_method == 'gptq':
cls.main_input_name = 'input_ids'
if hasattr(cls, '_tp_plan'): # fix tp_plan
cls._tp_plan = cls._tp_plan or {}
if return_dummy_model:
origin_torch_dtype = torch.get_default_dtype()
torch.set_default_dtype(kwargs['config'].torch_dtype)
model = cls(copy.deepcopy(kwargs['config']))
torch.set_default_dtype(origin_torch_dtype)
else:
model = from_pretrained(cls, *args, **kwargs)
return model
PreTrainedModel.from_pretrained = _new_from_pretrained
try:
yield
finally:
PreTrainedModel.from_pretrained = classmethod(from_pretrained)
def _get_max_memory(device_ids: List[int]) -> Dict[Union[int, str], int]:
"""add feat in accelerate to support MP + DDP"""
import psutil
# Make sure CUDA is initialized on each GPU to have the right memory info.
for i in device_ids:
_ = torch.tensor([0], device=i)
device_ids_set = set(device_ids)
max_memory = {}
for i in range(get_device_count()):
max_memory[i] = 0
if i in device_ids_set:
max_memory[i] = torch.cuda.mem_get_info(i)[0]
max_memory['cpu'] = psutil.virtual_memory().available
return max_memory
def _sync_max_memory(max_memory: Dict[Union[int, str], int]) -> Dict[Union[int, str], int]:
"""Make sure that the model structure of MP(device_map) is the same, when using DDP."""
max_memory_list = [v for k, v in max_memory.items() if (v > 0 and k != 'cpu')]
_, local_rank, world_size, _ = get_dist_setting()
src_tensor = torch.tensor(max_memory_list).to(local_rank)
tgt_tensor_list = [torch.zeros_like(src_tensor) for _ in range(world_size)]
dist.all_gather(tgt_tensor_list, src_tensor)
tgt_tensor = torch.stack(tgt_tensor_list, dim=0)
new_max_memory_iter = iter(tgt_tensor.min(dim=0)[0].tolist())
new_max_memory = {}
for k, v in max_memory.items():
new_max_memory[k] = v
if v > 0 and k != 'cpu':
new_max_memory[k] = next(new_max_memory_iter)
return new_max_memory
_mp_ddp_patched = False
def patch_mp_ddp():
"""Patch ddp with device_map.
After patching, the ddp can run with the device_map.
This should be called before any training starts.
"""
global _mp_ddp_patched
if _mp_ddp_patched:
return
_mp_ddp_patched = True
if is_mp_ddp():
if transformers_5:
from transformers.integrations import accelerate as tf_accelerate
get_balanced_memory = tf_accelerate.get_balanced_memory
infer_auto_device_map = tf_accelerate.infer_auto_device_map
else:
from accelerate.utils.modeling import get_balanced_memory, infer_auto_device_map
@wraps(infer_auto_device_map)
def _infer_auto_device_map_patch(model: nn.Module,
max_memory: Optional[Dict[Union[int, str], Union[int, str]]] = None,
**kwargs) -> Dict[str, Union[int, str, torch.device]]:
"""The auxiliary function for supports MP + DDP. Monkey Patching.
add feat in accelerate to support MP + DDP"""
verbose = kwargs.pop('verbose', False)
n_gpu = get_device_count()
_, local_rank, _, local_world_size = get_dist_setting()
device_ids = list(range(local_rank, n_gpu, local_world_size))
max_memory = _get_max_memory(device_ids)
max_memory = _sync_max_memory(max_memory)
max_memory = get_balanced_memory(model, max_memory, low_zero=False, **kwargs)
max_memory = {k: v for k, v in max_memory.items() if v > 0}
return infer_auto_device_map(model, max_memory, verbose=verbose, **kwargs)
_old_ddp_init = DDP.__init__
accelerate.accelerator.torch.nn.parallel.DistributedDataParallel.__init__ = (
lambda self, model, device_ids, output_device, *args, **kwargs: _old_ddp_init(self, model, *args, **kwargs))
if transformers_5:
tf_accelerate.infer_auto_device_map = _infer_auto_device_map_patch
else:
transformers.modeling_utils.infer_auto_device_map = _infer_auto_device_map_patch
transformers.modeling_utils.get_balanced_memory = lambda *args, **kwargs: {}
_old_accelerator_init = trainer.Accelerator.__init__
trainer.Accelerator.__init__ = (lambda self, device_placement=False, *args, **kwargs: _old_accelerator_init(
self, device_placement=device_placement, *args, **kwargs))
trainer.Accelerator.verify_device_map = lambda *args, **kwargs: False
@contextmanager
def patch_get_dynamic_module():
origin_get_cached_module_file = dynamic_module_utils.get_cached_module_file
def new_get_cached_module_file(pretrained_model_name_or_path, *args, **kwargs):
with safe_ddp_context(hash_id=str(pretrained_model_name_or_path)):
return origin_get_cached_module_file(pretrained_model_name_or_path, *args, **kwargs)
dynamic_module_utils.get_cached_module_file = new_get_cached_module_file
try:
yield
finally:
dynamic_module_utils.get_cached_module_file = origin_get_cached_module_file
@contextmanager
def patch_tp_plan(load_model: bool):
if not load_model or not is_mp() or transformers_version < version.parse('4.50') or 'WORLD_SIZE' not in os.environ:
yield
return
logger.info_once('Patch tp_plan.')
WORLD_SIZE = os.environ.get('WORLD_SIZE')
os.environ['_PATCH_WORLD_SIZE'] = WORLD_SIZE
os.environ.pop('WORLD_SIZE')
yield
os.environ['WORLD_SIZE'] = WORLD_SIZE
def revert_padding_free(outputs: Dict[str, Any], inputs: Dict[str, Any], padding_side='left'):
hidden_state_key = None
if 'last_hidden_state' in outputs:
hidden_state_key = 'last_hidden_state'
elif 'logits' in outputs:
hidden_state_key = 'logits'
elif 'token_embeddings' in outputs:
hidden_state_key = 'token_embeddings'
if hidden_state_key is None:
raise NotImplementedError()
last_hidden_state = outputs[hidden_state_key]
last_hidden_state = last_hidden_state.squeeze(dim=0)
if 'cu_seq_lens_q' in inputs:
position_ids = get_position_ids_from_cu_seqlens(inputs['cu_seq_lens_q'])
elif 'position_ids' in inputs and inputs['position_ids'].shape[0] == 1:
position_ids = inputs['position_ids']
else:
raise ValueError(
"revert_padding_free requires 'cu_seq_lens_q' or 'position_ids' in inputs, but neither was found.")
seq_lengths = []
pos = position_ids[0]
resets = torch.where(pos[1:] < pos[:-1])[0] + 1
if len(resets) == 0:
# Only one sequence in this batch item
seq_lengths = [pos.max().item() + 1]
else:
# Multiple sequences
start = 0
for end in resets:
seq_lengths.append(end - start)
start = end
seq_lengths.append(pos.shape[0] - start)
max_length = max(seq_lengths)
unpacked_logits = []
start = 0
for length in seq_lengths:
seq_state = last_hidden_state[start:start + length]
padding = torch.zeros(
(max_length - length, last_hidden_state.shape[-1])).to(last_hidden_state.dtype).to(last_hidden_state.device)
# re-padding
if padding_side == 'left':
seq_state = torch.cat((padding, seq_state), dim=0)
else:
seq_state = torch.cat((seq_state, padding), dim=0)
unpacked_logits.append(seq_state)
start += length
outputs[hidden_state_key] = torch.stack(unpacked_logits, dim=0)
return outputs
def gather_sequence_parallel_outputs(
outputs: Dict[str, Any],
tensor_keys: Optional[List[str]] = None,
) -> Dict[str, Any]:
"""
Gather split tensors produced by sequence parallel training so that downstream
components (loss, metrics, etc.) can operate on full-length sequences.
"""
from swift.sequence_parallel import GatherTensor, sequence_parallel
tensor_keys = tensor_keys or ['logits', 'last_hidden_state', 'hidden_states']
position_ids = None
if sequence_parallel.rp_world_size and sequence_parallel.rp_world_size > 1:
position_ids = sequence_parallel.real_position_ids
position_ids = sequence_parallel.pad(position_ids, padding_value=-1, position_ids=position_ids)
for key in tensor_keys:
if key in outputs:
outputs[key] = GatherTensor.apply(outputs[key], 1, position_ids)
return outputs
@contextmanager
def patch_attach_align_device_hook_on_blocks():
from accelerate import big_modeling
origin_attach_align_device_hook_on_blocks = big_modeling.attach_align_device_hook_on_blocks
def attach_align_device_hook_on_blocks(*args, **kwargs):
return
big_modeling.attach_align_device_hook_on_blocks = attach_align_device_hook_on_blocks
try:
yield
finally:
big_modeling.attach_align_device_hook_on_blocks = origin_attach_align_device_hook_on_blocks
def patch_module_forward(module, new_forward):
if getattr(module, '_patched', False):
return
module._patched = True
new_forward_wrapped = MethodType(new_forward, module)
if hasattr(module, '_old_forward'): # device_map
module._old_forward = new_forward_wrapped
else:
module.forward = new_forward_wrapped