This commit is contained in:
@@ -0,0 +1,338 @@
|
||||
# Copyright (c) ModelScope Contributors. All rights reserved.
|
||||
import os
|
||||
import shutil
|
||||
import torch
|
||||
import torch.nn.functional as F
|
||||
from accelerate.utils import find_device
|
||||
from functools import wraps
|
||||
from packaging import version
|
||||
from peft import PeftModel
|
||||
from torch import nn
|
||||
from transformers import PretrainedConfig, PreTrainedModel
|
||||
from transformers.integrations import is_deepspeed_zero3_enabled
|
||||
from transformers.utils import (is_torch_bf16_gpu_available, is_torch_cuda_available, is_torch_mps_available,
|
||||
is_torch_npu_available, strtobool)
|
||||
from types import MethodType
|
||||
from typing import List, Optional, TypeVar, Union
|
||||
|
||||
from swift.utils import (HfConfigFactory, Processor, deep_getattr, get_dist_setting, get_env_args, get_logger, is_mp,
|
||||
to_device)
|
||||
|
||||
logger = get_logger()
|
||||
|
||||
_T = TypeVar('_T')
|
||||
|
||||
|
||||
class AttnImpl:
|
||||
attn_impl_keys = ['_attn_implementation', 'attn_implementation', 'llm_attn_implementation']
|
||||
use_flash_attn_keys = ['_flash_attn_2_enabled', 'use_flash_attn', '_use_flash_attention_2']
|
||||
|
||||
@staticmethod
|
||||
def to_use_flash_attn(attn_impl: Optional[str], auto_value: _T = None) -> Union[bool, _T]:
|
||||
if attn_impl is None:
|
||||
return auto_value
|
||||
return attn_impl in {'flash_attn', 'flash_attention_2'}
|
||||
|
||||
@staticmethod
|
||||
def update_attn_impl(config: PretrainedConfig,
|
||||
attn_impl: Optional[str],
|
||||
attn_impl_keys: Optional[List[str]] = None) -> None:
|
||||
if attn_impl is None:
|
||||
return
|
||||
logger.info(f'attn_impl: {attn_impl}')
|
||||
use_flash_attn = AttnImpl.to_use_flash_attn(attn_impl)
|
||||
if use_flash_attn:
|
||||
attn_impl = 'flash_attention_2'
|
||||
if isinstance(attn_impl_keys, str):
|
||||
attn_impl_keys = [attn_impl_keys]
|
||||
attn_impl_keys = attn_impl_keys or AttnImpl.attn_impl_keys
|
||||
for key in attn_impl_keys:
|
||||
HfConfigFactory.set_config_attr(config, key, attn_impl, include_vit=True, ensure_set=False)
|
||||
for key in AttnImpl.use_flash_attn_keys:
|
||||
HfConfigFactory.set_config_attr(config, key, use_flash_attn, include_vit=True, ensure_set=False)
|
||||
|
||||
|
||||
def get_llm_model(model: torch.nn.Module, model_meta=None, inner_backbone=True):
|
||||
"""Get LLM model, this function can be used to get the llm module from a multi-modal model.
|
||||
|
||||
Args:
|
||||
model: The model instance
|
||||
model_meta: The model_meta information
|
||||
inner_backbone: Get inner backbone model, like `QwenModel` or `LlamaModel`
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
from accelerate.utils import extract_model_from_parallel
|
||||
|
||||
from swift.tuners import SwiftModel
|
||||
model = extract_model_from_parallel(model)
|
||||
|
||||
if isinstance(model, (SwiftModel, PeftModel)):
|
||||
model = model.model
|
||||
if model_meta is None:
|
||||
model_meta = model.model_meta
|
||||
|
||||
llm_prefix = getattr(model_meta.model_arch, 'language_model', None)
|
||||
if llm_prefix:
|
||||
llm_model = deep_getattr(model, llm_prefix[0])
|
||||
else:
|
||||
llm_model = model
|
||||
|
||||
if inner_backbone:
|
||||
if hasattr(llm_model, 'thinker'):
|
||||
llm_model = llm_model.thinker.model
|
||||
elif hasattr(llm_model, 'model'):
|
||||
llm_model = llm_model.model
|
||||
return llm_model
|
||||
|
||||
|
||||
def use_submodel_func(model, submodel_name: str, func_list: Optional[List[str]] = None) -> None:
|
||||
if func_list is None:
|
||||
func_list = ['generate', 'get_input_embeddings', 'gradient_checkpointing_enable', 'forward']
|
||||
submodel = getattr(model, submodel_name)
|
||||
|
||||
def _get_new_func(func_name: str):
|
||||
# Please ensure the patch to submodel.forward is applied before this function.
|
||||
_old_func = getattr(submodel, func_name).__func__
|
||||
|
||||
@wraps(_old_func)
|
||||
def _new_func(self, *args, **kwargs):
|
||||
res = _old_func(submodel, *args, **kwargs)
|
||||
if func_name == 'forward':
|
||||
device = find_device(args)
|
||||
if device is None:
|
||||
device = find_device(kwargs)
|
||||
if hasattr(res, 'logits'):
|
||||
res.logits = to_device(res.logits, device)
|
||||
if hasattr(res, 'loss'):
|
||||
res.loss = to_device(res.loss, device)
|
||||
if isinstance(res, dict) and 'last_hidden_state' in res:
|
||||
res['last_hidden_state'] = to_device(res['last_hidden_state'], device)
|
||||
return res
|
||||
|
||||
return _new_func
|
||||
|
||||
for key in func_list:
|
||||
setattr(model, key, MethodType(_get_new_func(key), model))
|
||||
if key == 'generate' and model.device != submodel.device:
|
||||
submodel.__class__.device = model.device
|
||||
if key == 'forward' and 'generate' in func_list:
|
||||
setattr(submodel, key, MethodType(_get_new_func(key), submodel)) # fix device_map
|
||||
|
||||
|
||||
class InitModelStrategy:
|
||||
|
||||
@staticmethod
|
||||
def is_uninitialized(param: torch.Tensor) -> bool:
|
||||
"""
|
||||
Check if a parameter is uninitialized or has numerically unstable values.
|
||||
Criteria:
|
||||
- Tensor has NaN or Inf values
|
||||
- Tensor stats (mean or std) are outside reasonable range
|
||||
"""
|
||||
if param.numel() == 0:
|
||||
return False
|
||||
|
||||
with torch.no_grad():
|
||||
mean_abs = param.abs().mean()
|
||||
std = param.std()
|
||||
|
||||
# NaN or Inf
|
||||
if not torch.isfinite(mean_abs) or not torch.isfinite(std):
|
||||
return True
|
||||
|
||||
# Use empirically safe threshold
|
||||
MAX_THRESHOLD = 1e7
|
||||
if mean_abs > MAX_THRESHOLD or std > MAX_THRESHOLD:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def constant_init(param: torch.Tensor, c: float = 0) -> None:
|
||||
nn.init.constant_(param, c)
|
||||
|
||||
@staticmethod
|
||||
def uniform_init(param: torch.Tensor, a: float = -0.1, b: float = 0.1) -> None:
|
||||
nn.init.uniform_(param, a, b)
|
||||
|
||||
@staticmethod
|
||||
def normal_init(param: torch.Tensor, mean: float = 0.0, std: float = 0.01) -> None:
|
||||
nn.init.normal_(param, mean, std)
|
||||
|
||||
@staticmethod
|
||||
def _init_high_dim(param: torch.Tensor, init_func, *args, **kwargs) -> None:
|
||||
"""Helper for high-dimensional initialization methods."""
|
||||
if param.dim() > 1:
|
||||
init_func(param, *args, **kwargs)
|
||||
elif param.dim() == 1 and param.size(0) > 0:
|
||||
InitModelStrategy.constant_init(param)
|
||||
|
||||
@staticmethod
|
||||
def xavier_uniform_init(param: torch.Tensor) -> None:
|
||||
InitModelStrategy._init_high_dim(param, nn.init.xavier_uniform_)
|
||||
|
||||
@staticmethod
|
||||
def xavier_normal_init(param: torch.Tensor) -> None:
|
||||
InitModelStrategy._init_high_dim(param, nn.init.xavier_normal_)
|
||||
|
||||
@staticmethod
|
||||
def kaiming_uniform_init(param: torch.Tensor) -> None:
|
||||
InitModelStrategy._init_high_dim(
|
||||
param, nn.init.kaiming_uniform_, mode='fan_out', nonlinearity='leaky_relu', a=0.1)
|
||||
|
||||
@staticmethod
|
||||
def kaiming_normal_init(param: torch.Tensor) -> None:
|
||||
InitModelStrategy._init_high_dim(param, nn.init.kaiming_normal_, mode='fan_in', nonlinearity='relu')
|
||||
|
||||
@staticmethod
|
||||
def orthogonal_init(param: torch.Tensor) -> None:
|
||||
nn.init.orthogonal_(param, gain=1.0)
|
||||
|
||||
_INIT_STRATEGY_MAP = {
|
||||
'zero': constant_init,
|
||||
'uniform': uniform_init,
|
||||
'normal': normal_init,
|
||||
'xavier_uniform': xavier_uniform_init,
|
||||
'xavier_normal': xavier_normal_init,
|
||||
'kaiming_uniform': kaiming_uniform_init,
|
||||
'kaiming_normal': kaiming_normal_init,
|
||||
'orthogona': orthogonal_init,
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def init_parameters(model: nn.Module, init_strategy: str) -> None:
|
||||
"""Initialize model parameters using the specified strategy.
|
||||
Args:
|
||||
model: The model whose parameters to initialize
|
||||
init_strategy: Name of initialization strategy
|
||||
"""
|
||||
if init_strategy not in InitModelStrategy._INIT_STRATEGY_MAP:
|
||||
raise ValueError(f'Unknown initialization strategy: {init_strategy}')
|
||||
|
||||
logger.info(f'initialization strategy: {init_strategy}')
|
||||
|
||||
init_func = InitModelStrategy._INIT_STRATEGY_MAP[init_strategy]
|
||||
|
||||
for name, param in model.named_parameters():
|
||||
if InitModelStrategy.is_uninitialized(param):
|
||||
logger.info(f'Initializing parameters: {name}.')
|
||||
init_func(param)
|
||||
|
||||
|
||||
def get_default_device_map():
|
||||
if is_deepspeed_zero3_enabled() or os.environ.get('ACCELERATE_USE_FSDP', 'False') == 'true':
|
||||
return None
|
||||
local_rank = get_dist_setting()[1]
|
||||
if local_rank == -1:
|
||||
local_rank = 0
|
||||
if is_torch_npu_available():
|
||||
return 'auto' if is_mp() else f'npu:{local_rank}'
|
||||
elif is_torch_mps_available():
|
||||
return f'mps:{local_rank}'
|
||||
elif is_torch_cuda_available():
|
||||
return 'auto' if is_mp() else f'cuda:{local_rank}'
|
||||
else:
|
||||
return 'cpu'
|
||||
|
||||
|
||||
def get_default_torch_dtype(torch_dtype: Optional[torch.dtype]):
|
||||
# torch_dtype: torch_dtype in config.json
|
||||
if torch_dtype is not None:
|
||||
return torch_dtype
|
||||
|
||||
try:
|
||||
is_bf16_available = is_torch_bf16_gpu_available() or (is_torch_npu_available()
|
||||
and torch.npu.is_bf16_supported())
|
||||
except Exception: # noqa
|
||||
is_bf16_available = False
|
||||
|
||||
if is_torch_cuda_available() or is_torch_npu_available():
|
||||
if is_bf16_available:
|
||||
return torch.bfloat16
|
||||
else:
|
||||
return torch.float16
|
||||
else:
|
||||
# cpu
|
||||
return torch.float32
|
||||
|
||||
|
||||
def _patch_conv3d():
|
||||
if hasattr(nn.Conv3d, '_original_forward'):
|
||||
return
|
||||
nn.Conv3d._original_forward = nn.Conv3d.forward
|
||||
|
||||
def forward(self, x):
|
||||
if any(s != k for s, k in zip(self.stride, self.kernel_size)) or any(p != 0 for p in self.padding) or any(
|
||||
d != 1 for d in self.dilation) or self.groups != 1:
|
||||
raise NotImplementedError(
|
||||
'Patched Conv3d only supports stride=kernel_size, padding=0, dilation=1, groups=1')
|
||||
N = x.shape[0]
|
||||
K = self.kernel_size
|
||||
x = x.unfold(2, K[0], K[0]).unfold(3, K[1], K[1]).unfold(4, K[2], K[2])
|
||||
D_out, H_out, W_out = x.shape[2:5]
|
||||
x = x.permute(0, 2, 3, 4, 1, 5, 6, 7).reshape(-1, self.in_channels * K[0] * K[1] * K[2])
|
||||
x = F.linear(x, self.weight.view(self.out_channels, -1), self.bias)
|
||||
x = x.view(N, D_out, H_out, W_out, self.out_channels).permute(0, 4, 1, 2, 3)
|
||||
return x
|
||||
|
||||
nn.Conv3d.forward = forward
|
||||
logger.info('Conv3d patched successfully')
|
||||
|
||||
|
||||
requires_patch = version.parse('2.9.0') <= version.parse(torch.__version__) < version.parse('2.10.0')
|
||||
if requires_patch:
|
||||
_patch_conv3d()
|
||||
|
||||
|
||||
def save_checkpoint(model: Optional[PreTrainedModel],
|
||||
processor: Processor,
|
||||
output_dir: str,
|
||||
*,
|
||||
safe_serialization: bool = True,
|
||||
max_shard_size: Union[int, str] = '5GB',
|
||||
model_dirs: List[str] = None,
|
||||
additional_saved_files: Optional[List[str]] = None) -> None:
|
||||
if model is not None:
|
||||
if model.__class__.__name__ != 'SentenceTransformer':
|
||||
model.save_pretrained(output_dir, safe_serialization=safe_serialization, max_shard_size=max_shard_size)
|
||||
else:
|
||||
model.save_pretrained(output_dir, safe_serialization=safe_serialization)
|
||||
# copy sentencetransformers files
|
||||
from swift.utils import copy_files_by_pattern
|
||||
copy_files_by_pattern(model.model_dir, output_dir, '*.py')
|
||||
copy_files_by_pattern(model.model_dir, output_dir, '*.json')
|
||||
processor.save_pretrained(output_dir)
|
||||
|
||||
if model_dirs is None:
|
||||
model_dirs = []
|
||||
else:
|
||||
model_dirs = model_dirs.copy()
|
||||
if model and model.model_dir and model.model_dir not in model_dirs:
|
||||
model_dirs.append(model.model_dir)
|
||||
for src_file in (additional_saved_files or []) + ['preprocessor_config.json', 'args.json']:
|
||||
tgt_path = os.path.join(output_dir, src_file)
|
||||
if os.path.exists(tgt_path) and src_file == 'args.json':
|
||||
continue
|
||||
for model_dir in model_dirs:
|
||||
src_path: str = os.path.join(model_dir, src_file)
|
||||
if os.path.isfile(src_path):
|
||||
shutil.copy(src_path, tgt_path)
|
||||
break
|
||||
elif os.path.isdir(src_path):
|
||||
shutil.copytree(src_path, tgt_path)
|
||||
break
|
||||
|
||||
|
||||
def get_ckpt_dir(model_dir: str, adapters_dir: Optional[List[str]]) -> str:
|
||||
model_dirs = (adapters_dir or []).copy()
|
||||
if model_dir:
|
||||
model_dirs.append(model_dir)
|
||||
# The adapter takes higher priority.
|
||||
ckpt_dir = None
|
||||
for model_dir in model_dirs:
|
||||
if os.path.exists(os.path.join(model_dir, 'args.json')):
|
||||
ckpt_dir = model_dir
|
||||
break
|
||||
return ckpt_dir
|
||||
Reference in New Issue
Block a user