Fix some bugs (#1746)

This commit is contained in:
tastelikefeet
2026-07-07 11:26:14 +08:00
committed by GitHub
parent d5d13c1f25
commit 9379504fd3
3 changed files with 155 additions and 46 deletions

View File

@@ -141,7 +141,7 @@ def check_model_from_owner_group(model_dir: str,
return False
if owner_group is None:
owner_group = ['iic', 'damo']
model_dir = model_dir.rstrip('/').rstrip('\\')
model_dir = os.path.normpath(model_dir.rstrip('/').rstrip('\\'))
parent_dir = os.path.dirname(model_dir)
group = os.path.basename(parent_dir)
if group in owner_group:

View File

@@ -155,6 +155,79 @@ def _decide_allow_file_pattern(module_name, cls=None):
return extra_allow_file_pattern
def _get_class_from_dynamic_module(class_reference, *args, **kwargs):
"""Wrapper that redirects dynamic-module downloads to ModelScope.
When a config's ``auto_map`` references another repo, transformers calls
``get_class_from_dynamic_module`` to fetch it. This wrapper ensures that
fetch goes through ModelScope instead of HuggingFace.
"""
from transformers.dynamic_module_utils import origin_get_class_from_dynamic_module
if 'pretrained_model_name_or_path' in inspect.signature(
origin_get_class_from_dynamic_module).parameters:
pretrained_model_name_or_path = args[0]
if not os.path.exists(pretrained_model_name_or_path):
from modelscope import snapshot_download
args[0] = snapshot_download(pretrained_model_name_or_path)
if '--' in class_reference:
repo_id, class_reference = class_reference.split('--')
if not os.path.exists(repo_id):
download_kwargs = {}
extra_allow_file_pattern = _decide_allow_file_pattern(
class_reference)
if extra_allow_file_pattern is not None:
download_kwargs[
'allow_file_pattern'] = extra_allow_file_pattern
if 'Config' in class_reference or 'Processor' in class_reference or 'Tokenizer' in class_reference:
download_kwargs['ignore_file_pattern'] = ignore_file_pattern
from modelscope import snapshot_download
repo_id = snapshot_download(repo_id, **download_kwargs)
class_reference = repo_id + '--' + class_reference
return origin_get_class_from_dynamic_module(class_reference, *args,
**kwargs)
def _patch_dynamic_module():
"""Globally patch ``get_class_from_dynamic_module`` to redirect to ModelScope."""
from transformers import dynamic_module_utils
if not hasattr(dynamic_module_utils,
'origin_get_class_from_dynamic_module'):
dynamic_module_utils.origin_get_class_from_dynamic_module = dynamic_module_utils.get_class_from_dynamic_module
dynamic_module_utils.get_class_from_dynamic_module = _get_class_from_dynamic_module
from transformers.models.auto import configuration_auto
configuration_auto.get_class_from_dynamic_module = _get_class_from_dynamic_module
def _unpatch_dynamic_module():
from transformers import dynamic_module_utils
if hasattr(dynamic_module_utils, 'origin_get_class_from_dynamic_module'):
dynamic_module_utils.get_class_from_dynamic_module = dynamic_module_utils.origin_get_class_from_dynamic_module
from transformers.models.auto import configuration_auto
configuration_auto.get_class_from_dynamic_module = dynamic_module_utils.origin_get_class_from_dynamic_module
delattr(dynamic_module_utils, 'origin_get_class_from_dynamic_module')
@contextlib.contextmanager
def _dynamic_module_patch_scope():
"""Temporarily patch ``get_class_from_dynamic_module`` for the duration of
the ``with`` block, unless an outer patch (e.g. ``patch_hub()``) is already
in effect.
This ensures that ``auto_map`` references inside a config are resolved via
ModelScope when loading through a modelscope-wrapped class, without
polluting the global transformers state for unrelated callers.
"""
from transformers import dynamic_module_utils
if hasattr(dynamic_module_utils, 'origin_get_class_from_dynamic_module'):
yield
return
_patch_dynamic_module()
try:
yield
finally:
_unpatch_dynamic_module()
def _patch_pretrained_class(all_imported_modules, wrap=False):
"""Patch all class to download from modelscope
@@ -272,8 +345,9 @@ def _patch_pretrained_class(all_imported_modules, wrap=False):
cls=module_class,
**kwargs)
module_obj = module_class.from_pretrained(model, model_dir,
*model_args, **kwargs)
with _dynamic_module_patch_scope():
module_obj = module_class.from_pretrained(
model, model_dir, *model_args, **kwargs)
return module_obj
@@ -286,8 +360,9 @@ def _patch_pretrained_class(all_imported_modules, wrap=False):
model_dir = get_model_dir(pretrained_model_name_or_path,
**kwargs)
module_obj = module_class.from_pretrained(
model_dir, *model_args, **kwargs)
with _dynamic_module_patch_scope():
module_obj = module_class.from_pretrained(
model_dir, *model_args, **kwargs)
if module_class.__name__.startswith('AutoModel'):
module_obj.model_dir = model_dir
@@ -315,8 +390,9 @@ def _patch_pretrained_class(all_imported_modules, wrap=False):
allow_file_pattern=allow_file_pattern,
**kwargs)
module_obj = module_class.get_config_dict(
model_dir, *model_args, **kwargs)
with _dynamic_module_patch_scope():
module_obj = module_class.get_config_dict(
model_dir, *model_args, **kwargs)
return module_obj
def save_pretrained(
@@ -441,39 +517,13 @@ def _patch_pretrained_class(all_imported_modules, wrap=False):
all_available_modules.append(var)
def get_class_from_dynamic_module(class_reference, *args, **kwargs):
from transformers.dynamic_module_utils import origin_get_class_from_dynamic_module
if 'pretrained_model_name_or_path' in inspect.signature(
origin_get_class_from_dynamic_module).parameters:
pretrained_model_name_or_path = args[0]
if not os.path.exists(pretrained_model_name_or_path):
from modelscope import snapshot_download
args[0] = snapshot_download(pretrained_model_name_or_path)
if '--' in class_reference:
repo_id, class_reference = class_reference.split('--')
if not os.path.exists(repo_id):
download_kwargs = {}
extra_allow_file_pattern = _decide_allow_file_pattern(
class_reference)
if extra_allow_file_pattern is not None:
download_kwargs[
'allow_file_pattern'] = extra_allow_file_pattern
if 'Config' in class_reference or 'Processor' in class_reference or 'Tokenizer' in class_reference:
download_kwargs[
'ignore_file_pattern'] = ignore_file_pattern
from modelscope import snapshot_download
repo_id = snapshot_download(repo_id, **download_kwargs)
class_reference = repo_id + '--' + class_reference
return origin_get_class_from_dynamic_module(class_reference, *args,
**kwargs)
from transformers import dynamic_module_utils
if not hasattr(dynamic_module_utils,
'origin_get_class_from_dynamic_module'):
dynamic_module_utils.origin_get_class_from_dynamic_module = dynamic_module_utils.get_class_from_dynamic_module
dynamic_module_utils.get_class_from_dynamic_module = get_class_from_dynamic_module
from transformers.models.auto import configuration_auto
configuration_auto.get_class_from_dynamic_module = get_class_from_dynamic_module
# Only apply the global get_class_from_dynamic_module monkey-patch in
# direct-patch mode (wrap=False). In wrap mode the ClassWrapper scopes
# the patch to each from_pretrained / get_config_dict call via
# _dynamic_module_patch_scope(), so the global state stays clean for
# unrelated ``from transformers import AutoConfig`` callers (issue #1751).
if not wrap:
_patch_dynamic_module()
return all_available_modules
@@ -509,12 +559,7 @@ def _unpatch_pretrained_class(all_imported_modules):
if has_get_config_dict and hasattr(var, '_get_config_dict_origin'):
_restore(var, 'get_config_dict', '_get_config_dict_origin')
from transformers import dynamic_module_utils
if hasattr(dynamic_module_utils, 'origin_get_class_from_dynamic_module'):
dynamic_module_utils.get_class_from_dynamic_module = dynamic_module_utils.origin_get_class_from_dynamic_module
from transformers.models.auto import configuration_auto
configuration_auto.get_class_from_dynamic_module = dynamic_module_utils.origin_get_class_from_dynamic_module
delattr(dynamic_module_utils, 'origin_get_class_from_dynamic_module')
_unpatch_dynamic_module()
def _patch_kernels():

View File

@@ -263,6 +263,70 @@ class HFUtilTest(unittest.TestCase):
f"{[f for f in files if f.endswith('.safetensors') or f.endswith('.bin')]}"
)
def test_import_not_pollute_dynamic_module(self):
"""Importing from modelscope must not globally patch
transformers' get_class_from_dynamic_module (issue #1751).
The patch should be scoped to each from_pretrained / get_config_dict
call via _dynamic_module_patch_scope(), leaving the global state clean
for unrelated ``from transformers import AutoConfig`` callers.
"""
from modelscope import AutoConfig
from modelscope.utils.file_utils import get_modelscope_cache_dir
from transformers import dynamic_module_utils
# 1. Importing AutoConfig from modelscope (wrap=True) must NOT
# globally patch get_class_from_dynamic_module.
self.assertFalse(
hasattr(dynamic_module_utils,
'origin_get_class_from_dynamic_module'),
'Importing AutoConfig from modelscope must not globally patch '
'get_class_from_dynamic_module (issue #1751)')
# 2. The modelscope AutoConfig should still work with
# trust_remote_code, downloading through ModelScope (not HF).
model = 'nomic-ai/nomic-embed-text-v1.5'
config = AutoConfig.from_pretrained(model, trust_remote_code=True)
model_dir = config.name_or_path
# Verify files are in the ModelScope cache, not the HF cache.
ms_cache = get_modelscope_cache_dir()
self.assertTrue(
os.path.realpath(model_dir).startswith(os.path.realpath(ms_cache)),
f'Model files should be in ModelScope cache ({ms_cache}), '
f'but found at {model_dir}')
# 3. After the call the global patch should still NOT be in effect
# (the _dynamic_module_patch_scope was temporary).
self.assertFalse(
hasattr(dynamic_module_utils,
'origin_get_class_from_dynamic_module'),
'Global get_class_from_dynamic_module should not be patched '
'after a modelscope AutoConfig call (issue #1751)')
# 4. Pure transformers AutoConfig (without modelscope patching) must
# NOT be redirected to ModelScope. With the old code the global
# patch would intercept this call and download from ModelScope;
# with the fix the call goes to HuggingFace directly.
from transformers import AutoConfig as HFAutoConfig
try:
hf_config = HFAutoConfig.from_pretrained(
model, trust_remote_code=True)
hf_model_dir = hf_config.name_or_path
# If the download succeeded, files must be outside the
# ModelScope cache (i.e. in the HuggingFace cache).
self.assertFalse(
os.path.realpath(hf_model_dir).startswith(
os.path.realpath(ms_cache)),
f'Transformers AutoConfig should download from HuggingFace, '
f'not ModelScope (issue #1751). Found files at: '
f'{hf_model_dir}')
except Exception:
# If HuggingFace is unreachable the call fails — which is also
# correct: it means the request did NOT go through ModelScope
# (which would have succeeded).
pass
@unittest.skipUnless(test_level() >= 1, 'skip test in current test level')
def test_push_to_hub(self):
with patch_context():