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

463 lines
18 KiB
Python

# Copyright (c) ModelScope Contributors. All rights reserved.
import huggingface_hub
import logging
import modelscope
import os
import tempfile
from contextlib import contextmanager
from functools import partial
from huggingface_hub import RepoUrl
from huggingface_hub.hf_api import api, future_compatible
from packaging import version
from pathlib import Path
from requests.exceptions import HTTPError
from transformers import trainer
from transformers.utils import strtobool
from typing import List, Literal, Optional, Union
from swift.utils import get_logger, ms_logger_context, use_hf_hub
logger = get_logger()
class HubOperation:
@classmethod
@contextmanager
def patch_hub(cls):
yield
@classmethod
def try_login(cls, token: Optional[str] = None) -> bool:
"""Try to login to the hub
Args:
token: The hub token to use
Returns:
bool: Whether login is successful
"""
raise NotImplementedError
@classmethod
def create_model_repo(cls, repo_id: str, token: Optional[str] = None, private: bool = False):
"""Create a model repo on the hub
Args:
repo_id: The model id of the hub
token: The hub token to use
private: If is a private repo
"""
raise NotImplementedError
@classmethod
def push_to_hub(cls,
repo_id: str,
folder_path: Union[str, Path],
path_in_repo: Optional[str] = None,
commit_message: Optional[str] = None,
commit_description: Optional[str] = None,
token: Optional[Union[str, bool]] = None,
private: bool = False,
revision: Optional[str] = 'master',
ignore_patterns: Optional[Union[List[str], str]] = None,
**kwargs):
"""Push a model-like folder to the hub
Args:
repo_id: The repo id
folder_path: The local folder path
path_in_repo: Which remote folder to put the local files in
commit_message: The commit message of git
commit_description: The commit description
token: The hub token
private: Private hub or not
revision: The revision to push to
ignore_patterns: The ignore file patterns
"""
raise NotImplementedError
@classmethod
def load_dataset(cls,
dataset_id: str,
subset_name: str,
split: str,
streaming: bool = False,
revision: Optional[str] = None):
"""Load a dataset from the repo
Args:
dataset_id: The dataset id
subset_name: The subset name of the dataset
split: The split info
streaming: Streaming mode
revision: The revision of the dataset
Returns:
The Dataset instance
"""
raise NotImplementedError
@classmethod
def download_model(cls,
model_id_or_path: Optional[str] = None,
revision: Optional[str] = None,
download_model: bool = True,
ignore_patterns: Optional[List[str]] = None,
**kwargs):
"""Download model from the hub
Args:
model_id_or_path: The model id
revision: The model revision
download_model: Whether downloading bin/safetensors files, this is usually useful when only
using tokenizer
ignore_patterns: Custom ignore pattern
**kwargs:
Returns:
The local dir
"""
raise NotImplementedError
class MSHub(HubOperation):
ms_token = None
@staticmethod
def create_repo(repo_id: str,
*,
token: Optional[Union[str, bool]] = None,
private: bool = False,
**kwargs) -> RepoUrl:
"""
Create a new repository on the hub.
Args:
repo_id: The ID of the repository to create.
token: The authentication token to use.
private: Whether the repository should be private.
**kwargs: Additional arguments.
Returns:
RepoUrl: The URL of the created repository.
"""
hub_model_id = MSHub.create_model_repo(repo_id, token, private)
return RepoUrl(url=hub_model_id, )
@staticmethod
@future_compatible
def upload_folder(
self,
*,
repo_id: str,
folder_path: Union[str, Path],
path_in_repo: Optional[str] = None,
commit_message: Optional[str] = None,
commit_description: Optional[str] = None,
token: Optional[Union[str, bool]] = None,
revision: Optional[str] = 'master',
ignore_patterns: Optional[Union[List[str], str]] = None,
**kwargs,
):
from modelscope.utils.repo_utils import CommitInfo
MSHub.push_to_hub(repo_id, folder_path, path_in_repo, commit_message, commit_description, token, True, revision,
ignore_patterns)
return CommitInfo(
commit_url=f'https://www.modelscope.cn/models/{repo_id}/files',
commit_message=commit_message,
commit_description=commit_description,
oid=None,
)
@classmethod
@contextmanager
def patch_hub(cls):
hub_create_repo = huggingface_hub.create_repo
hub_upload_folder = huggingface_hub.upload_folder
trainer_create_repo = getattr(trainer, 'create_repo', None)
trainer_upload_folder = getattr(trainer, 'upload_folder', None)
huggingface_hub.create_repo = cls.create_repo
huggingface_hub.upload_folder = partial(cls.upload_folder, api)
trainer.create_repo = cls.create_repo
trainer.upload_folder = partial(cls.upload_folder, api)
try:
yield
finally:
huggingface_hub.create_repo = hub_create_repo
huggingface_hub.upload_folder = hub_upload_folder
trainer.create_repo = trainer_create_repo
trainer.upload_folder = trainer_upload_folder
@classmethod
def try_login(cls, token: Optional[str] = None) -> bool:
from modelscope import HubApi
if token is None:
token = os.environ.get('MODELSCOPE_API_TOKEN')
if token:
api = HubApi()
api.login(token)
return True
return False
@classmethod
def create_model_repo(cls, repo_id: str, token: Optional[str] = None, private: bool = False) -> str:
from modelscope import HubApi
from modelscope.hub.api import ModelScopeConfig
from modelscope.hub.constants import ModelVisibility
assert repo_id is not None, 'Please enter a valid hub_model_id'
if not cls.try_login(token):
raise ValueError('Please specify a token by `--hub_token` or `MODELSCOPE_API_TOKEN=xxx`')
cls.ms_token = token
visibility = ModelVisibility.PRIVATE if private else ModelVisibility.PUBLIC
api = HubApi()
if '/' not in repo_id:
user_name = ModelScopeConfig.get_user_info()[0]
assert isinstance(user_name, str)
hub_model_id = f'{user_name}/{repo_id}'
logger.info(f"'/' not in hub_model_id, pushing to personal repo {hub_model_id}")
try:
api.create_model(repo_id, visibility)
except HTTPError:
# The remote repository has been created
pass
with tempfile.TemporaryDirectory() as temp_cache_dir:
from modelscope.hub.repository import Repository
repo = Repository(temp_cache_dir, repo_id)
cls.add_patterns_to_gitattributes(repo, ['*.safetensors', '*.bin', '*.pt'])
# Add 'runs/' to .gitignore, ignore tensorboard files
cls.add_patterns_to_gitignore(repo, ['runs/', 'images/'])
cls.add_patterns_to_file(
repo,
'configuration.json', ['{"framework": "pytorch", "task": "text-generation", "allow_remote": true}'],
ignore_push_error=True)
# Add '*.sagemaker' to .gitignore if using SageMaker
if os.environ.get('SM_TRAINING_ENV'):
cls.add_patterns_to_gitignore(repo, ['*.sagemaker-uploading', '*.sagemaker-uploaded'],
'Add `*.sagemaker` patterns to .gitignore')
return repo_id
@classmethod
def push_to_hub(cls,
repo_id: str,
folder_path: Union[str, Path],
path_in_repo: Optional[str] = None,
commit_message: Optional[str] = None,
commit_description: Optional[str] = None,
token: Optional[Union[str, bool]] = None,
private: bool = False,
revision: Optional[str] = 'master',
ignore_patterns: Optional[Union[List[str], str]] = None,
**kwargs):
cls.create_model_repo(repo_id, token, private)
from modelscope import push_to_hub
commit_message = commit_message or 'Upload folder using api'
if commit_description:
commit_message = commit_message + '\n' + commit_description
if not os.path.exists(os.path.join(folder_path, 'configuration.json')):
with open(os.path.join(folder_path, 'configuration.json'), 'w', encoding='utf-8') as f:
f.write('{"framework": "pytorch", "task": "text-generation", "allow_remote": true}')
if ignore_patterns:
ignore_patterns = [p for p in ignore_patterns if p != '_*']
if path_in_repo:
# We don't support part submit for now
path_in_repo = os.path.basename(folder_path)
folder_path = os.path.dirname(folder_path)
ignore_patterns = []
if revision is None or revision == 'main':
revision = 'master'
push_to_hub(
repo_id,
folder_path,
token or cls.ms_token,
private,
commit_message=commit_message,
ignore_file_pattern=ignore_patterns,
revision=revision,
tag=path_in_repo)
@classmethod
def load_dataset(cls,
dataset_id: str,
subset_name: str,
split: str,
streaming: bool = False,
revision: Optional[str] = None,
download_mode: Literal['force_redownload', 'reuse_dataset_if_exists'] = 'reuse_dataset_if_exists',
token: Optional[str] = None,
**kwargs):
from modelscope import MsDataset
cls.try_login(token)
if revision is None or revision == 'main':
revision = 'master'
load_kwargs = {}
if version.parse(modelscope.__version__) >= version.parse('1.29.1'):
load_kwargs['trust_remote_code'] = True
with ms_logger_context(logging.ERROR):
return MsDataset.load(
dataset_id,
subset_name=subset_name,
split=split,
version=revision,
download_mode=download_mode,
use_streaming=streaming,
**load_kwargs,
)
@classmethod
def download_model(cls,
model_id_or_path: Optional[str] = None,
revision: Optional[str] = None,
ignore_patterns: Optional[List[str]] = None,
token: Optional[str] = None,
**kwargs):
cls.try_login(token)
if revision is None or revision == 'main':
revision = 'master'
logger.info(f'Downloading the model from ModelScope Hub, model_id: {model_id_or_path}')
from modelscope import snapshot_download
return snapshot_download(model_id_or_path, revision, ignore_patterns=ignore_patterns, **kwargs)
@staticmethod
def add_patterns_to_file(repo,
file_name: str,
patterns: List[str],
commit_message: Optional[str] = None,
ignore_push_error=False) -> None:
if isinstance(patterns, str):
patterns = [patterns]
if commit_message is None:
commit_message = f'Add `{patterns[0]}` patterns to {file_name}'
# Get current file content
repo_dir = repo.model_dir
file_path = os.path.join(repo_dir, file_name)
if os.path.exists(file_path):
with open(file_path, 'r', encoding='utf-8') as f:
current_content = f.read()
else:
current_content = ''
# Add the patterns to file
content = current_content
for pattern in patterns:
if pattern not in content:
if len(content) > 0 and not content.endswith('\n'):
content += '\n'
content += f'{pattern}\n'
# Write the file if it has changed
if content != current_content:
with open(file_path, 'w', encoding='utf-8') as f:
logger.debug(f'Writing {file_name} file. Content: {content}')
f.write(content)
try:
repo.push(commit_message)
except Exception as e:
if ignore_push_error:
pass
else:
raise e
@staticmethod
def add_patterns_to_gitignore(repo, patterns: List[str], commit_message: Optional[str] = None) -> None:
MSHub.add_patterns_to_file(repo, '.gitignore', patterns, commit_message, ignore_push_error=True)
@staticmethod
def add_patterns_to_gitattributes(repo, patterns: List[str], commit_message: Optional[str] = None) -> None:
new_patterns = []
suffix = 'filter=lfs diff=lfs merge=lfs -text'
for pattern in patterns:
if suffix not in pattern:
pattern = f'{pattern} {suffix}'
new_patterns.append(pattern)
file_name = '.gitattributes'
if commit_message is None:
commit_message = f'Add `{patterns[0]}` patterns to {file_name}'
MSHub.add_patterns_to_file(repo, file_name, new_patterns, commit_message, ignore_push_error=True)
class HFHub(HubOperation):
@classmethod
def try_login(cls, token: Optional[str] = None) -> bool:
pass
@classmethod
def create_model_repo(cls, repo_id: str, token: Optional[str] = None, private: bool = False) -> str:
return api.create_repo(repo_id, token=token, private=private)
@classmethod
def push_to_hub(cls,
repo_id: str,
folder_path: Union[str, Path],
path_in_repo: Optional[str] = None,
commit_message: Optional[str] = None,
commit_description: Optional[str] = None,
token: Optional[Union[str, bool]] = None,
private: bool = False,
revision: Optional[str] = 'master',
ignore_patterns: Optional[Union[List[str], str]] = None,
**kwargs):
cls.create_model_repo(repo_id, token, private)
if revision is None or revision == 'master':
revision = 'main'
return api.upload_folder(
repo_id=repo_id,
folder_path=folder_path,
path_in_repo=path_in_repo,
commit_message=commit_message,
commit_description=commit_description,
token=token,
revision=revision,
ignore_patterns=ignore_patterns,
**kwargs)
@classmethod
def load_dataset(cls,
dataset_id: str,
subset_name: str,
split: str,
streaming: bool = False,
revision: Optional[str] = None,
download_mode: Literal['force_redownload', 'reuse_dataset_if_exists'] = 'reuse_dataset_if_exists',
num_proc: Optional[int] = None,
**kwargs):
from datasets import load_dataset
if revision is None or revision == 'master':
revision = 'main'
return load_dataset(
dataset_id,
name=subset_name,
split=split,
streaming=streaming,
revision=revision,
download_mode=download_mode,
num_proc=num_proc,
trust_remote_code=True)
@classmethod
def download_model(cls,
model_id_or_path: Optional[str] = None,
revision: Optional[str] = None,
ignore_patterns: Optional[List[str]] = None,
**kwargs):
if revision is None or revision == 'master':
revision = 'main'
logger.info(f'Downloading the model from HuggingFace Hub, model_id: {model_id_or_path}')
use_hf_transfer = strtobool(os.environ.get('USE_HF_TRANSFER', 'False'))
if use_hf_transfer:
from huggingface_hub import _snapshot_download
_snapshot_download.HF_HUB_ENABLE_HF_TRANSFER = True
from huggingface_hub import snapshot_download
return snapshot_download(
model_id_or_path, repo_type='model', revision=revision, ignore_patterns=ignore_patterns, **kwargs)
def get_hub(use_hf: Optional[bool] = None):
if use_hf is None:
use_hf = True if use_hf_hub() else False
return {True: HFHub, False: MSHub}[use_hf]