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

385 lines
18 KiB
Python

# Copyright (c) ModelScope Contributors. All rights reserved.
import numpy as np
import os
from contextlib import nullcontext
from datasets import Dataset as HfDataset
from datasets import IterableDataset as HfIterableDataset
from datasets import load_dataset as hf_load_dataset
from functools import partial
from modelscope.hub.utils.utils import get_cache_dir
from typing import Dict, List, Literal, Optional, Tuple, Union
from swift.hub import get_hub
from swift.utils import get_logger, get_seed, safe_ddp_context, use_hf_hub
from .dataset_meta import DATASET_TYPE, BaseDatasetLoader
from .dataset_syntax import DatasetSyntax
from .preprocessor import RowPreprocessor
from .register import DATASET_MAPPING, DatasetMeta, SubsetDataset
logger = get_logger()
class DatasetLoader(BaseDatasetLoader):
def __init__(
self,
num_proc: int = 1,
load_from_cache_file: bool = True,
streaming: bool = False,
hub_token: Optional[str] = None,
strict: bool = False,
download_mode: Literal['force_redownload', 'reuse_dataset_if_exists'] = 'reuse_dataset_if_exists',
columns: Optional[Dict[str, str]] = None,
remove_unused_columns: bool = True,
disable_auto_column_mapping: bool = False,
):
self.num_proc = num_proc
self.load_from_cache_file = load_from_cache_file
self.streaming = streaming
self.hub_token = hub_token
self.strict = strict
self.download_mode = download_mode
self.columns = columns
self.remove_unused_columns = remove_unused_columns
self.disable_auto_column_mapping = disable_auto_column_mapping
def _load_dataset_path(
self,
dataset_path: str,
dataset_meta: DatasetMeta,
) -> HfDataset:
ext = os.path.splitext(dataset_path)[1].lstrip('.')
file_type = {'jsonl': 'json', 'txt': 'text'}.get(ext) or ext
kwargs = {'split': 'train', 'streaming': self.streaming, 'num_proc': self.num_proc}
if file_type == 'csv':
kwargs['na_filter'] = False
with safe_ddp_context(None, True):
kwargs['cache_dir'] = os.path.join(get_cache_dir(), 'datasets')
dataset = hf_load_dataset(file_type, data_files=dataset_path, **kwargs)
if self.columns:
dataset = RowPreprocessor.safe_rename_columns(dataset, self.columns)
dataset = dataset_meta.preprocess_func(
dataset,
num_proc=self.num_proc,
load_from_cache_file=self.load_from_cache_file,
strict=self.strict,
enable_auto_mapping=not self.disable_auto_column_mapping)
if self.remove_unused_columns:
dataset = RowPreprocessor.remove_useless_columns(dataset)
return dataset
def _load_repo_dataset(
self,
dataset_id: str,
subset: SubsetDataset,
*,
use_hf: Optional[bool] = None,
revision: Optional[str] = None,
) -> HfDataset:
datasets = []
if os.path.isdir(dataset_id):
retry = 1
load_context = nullcontext
use_hf = True
dataset_str = f'Use local folder, dataset_dir: {dataset_id}'
# The dataset downloaded from modelscope will have an additional dataset_infos.json file.
with safe_ddp_context('dataset_infos_rename'):
dataset_infos_path = os.path.join(dataset_id, 'dataset_infos.json')
if os.path.isfile(dataset_infos_path):
os.rename(dataset_infos_path, f'{dataset_infos_path}_bak')
elif dataset_id.startswith('/'):
raise ValueError(f'The local path does not exist, dataset_id: `{dataset_id}`. '
f'os.path.exists(dataset_id): {os.path.exists(dataset_id)}')
else:
retry = 3
load_context = partial(safe_ddp_context, hash_id=dataset_id, use_barrier=True)
dataset_str_f = 'Downloading the dataset from {hub}, dataset_id: {dataset_id}'
if use_hf:
dataset_str = dataset_str_f.format(hub='HuggingFace', dataset_id=dataset_id)
else:
dataset_str = dataset_str_f.format(hub='ModelScope', dataset_id=dataset_id)
logger.info(dataset_str)
hub = get_hub(use_hf)
for split in subset.split:
i = 1
with load_context():
while True:
try:
dataset = hub.load_dataset(
dataset_id,
subset.subset,
split,
streaming=self.streaming,
revision=revision,
download_mode=self.download_mode,
hub_token=self.hub_token,
num_proc=self.num_proc)
except Exception as e:
if i == retry:
raise
i += 1
logger.error(f'Dataset {dataset_id} load failed: subset_name={subset.subset},'
f'split={split} with error: {e}')
else:
break
if hasattr(dataset, '_hf_ds'):
dataset = dataset._hf_ds
if self.streaming and isinstance(dataset, HfDataset):
dataset = dataset.to_iterable_dataset()
if self.columns:
dataset = RowPreprocessor.safe_rename_columns(dataset, self.columns)
dataset = subset.preprocess_func(
dataset,
num_proc=self.num_proc,
load_from_cache_file=self.load_from_cache_file,
strict=self.strict,
enable_auto_mapping=not self.disable_auto_column_mapping)
if self.remove_unused_columns:
dataset = RowPreprocessor.remove_useless_columns(dataset)
datasets.append(dataset)
return self.concat_datasets(datasets)
@staticmethod
def _select_subsets(subsets: List[str], dataset_meta: DatasetMeta) -> List[SubsetDataset]:
subset_mapping = {subset.name: subset for subset in dataset_meta.subsets}
subset_names = list(subset_mapping.keys())
if not subsets:
if len(subset_names) <= 1:
subsets = subset_names
elif 'default' in subset_names:
subsets = ['default']
else:
raise ValueError(f'Please provide subsets. available subsets: {subset_names}')
elif len(subsets) == 1 and subsets[0] == 'all' and 'all' not in subset_names:
subsets = [subset_name for subset_name in subset_names if not subset_mapping[subset_name].is_weak_subset]
subsets = [
subset_mapping[subset_name] if subset_name in subset_mapping else SubsetDataset(subset=subset_name)
for subset_name in subsets
]
return [subset.set_default(dataset_meta) for subset in subsets]
def load(
self,
dataset_syntax: Optional[DatasetSyntax] = None,
dataset_meta: Optional[DatasetMeta] = None,
*,
use_hf: Optional[bool] = None,
) -> HfDataset:
if dataset_syntax.dataset_type == 'path':
dataset = self._load_dataset_path(
dataset_syntax.dataset,
dataset_meta=dataset_meta,
)
else:
subsets: List[SubsetDataset] = self._select_subsets(dataset_syntax.subsets, dataset_meta)
revision = dataset_meta.hf_revision if use_hf else dataset_meta.ms_revision
datasets = []
for subset in subsets:
dataset = self._load_repo_dataset(
dataset_syntax.dataset,
subset,
use_hf=use_hf,
revision=revision,
)
datasets.append(dataset)
dataset = self.concat_datasets(datasets)
return dataset
def init_self_cognition_preprocessor(
dataset_meta: Optional[DatasetMeta],
model_name: Optional[Union[Tuple[str, str], List[str]]] = None,
model_author: Optional[Union[Tuple[str, str], List[str]]] = None,
) -> None:
from .dataset.llm import SelfCognitionPreprocessor
if dataset_meta is None or model_name is None and model_author is None:
return
kwargs = {}
# zh, en
for key in ['name', 'author']:
val = locals()[f'model_{key}']
if isinstance(val, str):
val = [val]
if val is not None and val[0] is not None and (len(val) == 1 or val[1] is None):
val = (val[0], val[0])
kwargs[key] = val
preprocess_funcs = [dataset_meta.preprocess_func]
preprocess_funcs += [subset.preprocess_func for subset in dataset_meta.subsets if isinstance(subset, SubsetDataset)]
for preprocess_func in preprocess_funcs:
if isinstance(preprocess_func, SelfCognitionPreprocessor):
preprocess_func.set_name_author(**kwargs)
logger.info_once(f"SelfCognitionPreprocessor has been successfully configured with name: {kwargs['name']}, "
f"author: {kwargs['author']}.")
def _inject_dataset_routing_tag(dataset: DATASET_TYPE, ds_name: str) -> DATASET_TYPE:
"""Inject ``dataset`` column for multi-teacher routing (constant per source dataset)."""
if isinstance(dataset, HfIterableDataset):
return dataset.map(lambda example: {**example, 'dataset': ds_name})
return dataset.add_column('dataset', [ds_name] * len(dataset))
def load_dataset(
datasets: Union[List[str], str],
*,
split_dataset_ratio: float = 0.,
seed: Union[int, np.random.RandomState, None] = 42,
num_proc: int = 1,
load_from_cache_file: bool = True,
shuffle: bool = False,
streaming: bool = False,
interleave_prob: Optional[List[float]] = None,
stopping_strategy: Literal['first_exhausted', 'all_exhausted'] = 'first_exhausted',
shuffle_buffer_size: int = 1000,
use_hf: Optional[bool] = None,
hub_token: Optional[str] = None,
strict: bool = False,
download_mode: Literal['force_redownload', 'reuse_dataset_if_exists'] = 'reuse_dataset_if_exists',
columns: Optional[Dict[str, str]] = None, # columns_mapping
remove_unused_columns: bool = True,
disable_auto_column_mapping: bool = False,
# self-cognition
model_name: Optional[Union[Tuple[str, str], List[str]]] = None, # zh, en
model_author: Optional[Union[Tuple[str, str], List[str]]] = None,
) -> Tuple[DATASET_TYPE, Optional[DATASET_TYPE]]:
"""Load and preprocess datasets.
This function provides a unified interface to load datasets from various sources (HuggingFace,
ModelScope, or local paths), with support for splitting, shuffling, streaming, and interleaving
multiple datasets. It also handles self-cognition dataset preprocessing for model training.
Args:
datasets: Single dataset name or list of dataset names to load. Can use special syntax
for advanced configurations (e.g., 'dataset_name#1000' for sampling).
split_dataset_ratio: Ratio for splitting dataset into train/validation sets.
Value between 0 and 1. If 0, no validation split is created. Default: 0.
seed: Random seed for reproducibility. Can be an integer or numpy RandomState object.
If None, results will be non-deterministic. Default: 42.
num_proc: Number of processes to use for dataset preprocessing. Set to None for
streaming mode. Default: 1.
load_from_cache_file: Whether to load preprocessed data from cache if available.
Default: True.
shuffle: Whether to shuffle the dataset(s) after loading. Default: False.
streaming: Enable streaming mode for large datasets that don't fit in memory.
When True, num_proc is automatically set to None. Default: False.
interleave_prob: Probability weights for interleaving multiple datasets. Must have
same length as datasets list. If None, datasets are concatenated instead. Default: None.
stopping_strategy: Strategy when interleaving datasets of different lengths:
- 'first_exhausted': Stop when shortest dataset is exhausted
- 'all_exhausted': Continue until all datasets are exhausted
Default: 'first_exhausted'.
shuffle_buffer_size: Buffer size for shuffling in streaming mode. Larger values
provide better randomization but use more memory. Default: 1000.
use_hf: Force using HuggingFace Hub (True) or ModelScope (False). If None,
it is controlled by the environment variable `USE_HF`, which defaults to '0'.
Default: None.
hub_token: Authentication token for accessing private datasets on the hub. Default: None.
strict: If True, raise exceptions when encountering malformed data rows.
If False, skip invalid rows with warnings. Default: False.
download_mode: How to handle existing cached datasets:
- 'reuse_dataset_if_exists': Use cached version if available
- 'force_redownload': Always download fresh copy
Default: 'reuse_dataset_if_exists'.
columns: Manual column name mapping for datasets. Dictionary mapping source column
names to target column names (e.g., {'text': 'content'}). Default: None.
remove_unused_columns: Whether to remove columns not used in preprocessing.
Helps reduce memory usage. Default: True.
disable_auto_column_mapping: By default, column names in the dataset are automatically
mapped. This parameter disables that behavior
(the `columns` parameter remains effective), defaulting to `False`.
model_name: Model name for self-cognition task preprocessing. Can be a tuple of
(Chinese_name, English_name) or list of names. Default: None.
model_author: Model author for self-cognition task preprocessing. Can be a tuple of
(Chinese_author, English_author) or list of authors. Default: None.
Returns:
A tuple of (train_dataset, val_dataset):
- train_dataset: The training dataset
- val_dataset: The validation dataset if split_dataset_ratio > 0, otherwise None
Examples:
>>> # Load single dataset
>>> train_ds, val_ds = load_dataset('AI-ModelScope/alpaca-gpt4-data-zh', split_dataset_ratio=0.1)
>>> # Load multiple datasets
>>> train_ds, _ = load_dataset(
... ['AI-ModelScope/alpaca-gpt4-data-zh#500', 'swift/self-cognition#500'],
... model_name=('我的模型', 'MyModel'),
... model_author=('作者', 'Author')
... )
"""
init_self_cognition_preprocessor(DATASET_MAPPING.get('self-cognition'), model_name, model_author)
if isinstance(datasets, str):
datasets = [datasets]
if not isinstance(seed, np.random.RandomState):
seed = np.random.RandomState(seed)
if streaming:
num_proc = None
train_datasets = []
val_datasets = []
use_hf_default = use_hf
if use_hf_default is None:
use_hf_default = True if use_hf_hub() else False
for dataset in datasets:
dataset_syntax = DatasetSyntax.parse(dataset)
use_hf = dataset_syntax.use_hf or use_hf_default
# compat dataset_name
if dataset_syntax.dataset in DATASET_MAPPING:
dataset_meta = DATASET_MAPPING[dataset_syntax.dataset]
if dataset_syntax.use_hf is None and dataset_meta.dataset_path is not None:
dataset_syntax.dataset = dataset_meta.dataset_path
dataset_syntax.dataset_type = 'path'
else:
dataset_syntax.dataset = dataset_meta.hf_dataset_id if use_hf else dataset_meta.ms_dataset_id
else:
dataset_meta = dataset_syntax.get_dataset_meta(use_hf)
loader = dataset_meta.loader(
num_proc=num_proc,
load_from_cache_file=load_from_cache_file,
streaming=streaming,
hub_token=hub_token,
strict=strict,
download_mode=download_mode,
columns=columns, # columns_mapping
remove_unused_columns=remove_unused_columns,
disable_auto_column_mapping=disable_auto_column_mapping,
)
train_dataset = loader.load(dataset_syntax, dataset_meta, use_hf=use_hf)
train_dataset, val_dataset = loader.post_process(
train_dataset,
dataset_sample=dataset_syntax.dataset_sample,
split_dataset_ratio=split_dataset_ratio,
streaming=streaming,
shuffle=shuffle,
random_state=seed,
)
if train_dataset is not None:
# Inject dataset_syntax.dataset as routing tag for multi-teacher
ds_name = dataset_syntax.dataset
train_dataset = _inject_dataset_routing_tag(train_dataset, ds_name)
train_datasets.append(train_dataset)
if val_dataset is not None:
ds_name = dataset_syntax.dataset
val_dataset = _inject_dataset_routing_tag(val_dataset, ds_name)
val_datasets.append(val_dataset)
if interleave_prob is None:
train_datasets = loader.concat_datasets(train_datasets)
val_datasets = loader.concat_datasets(val_datasets)
else:
train_datasets = loader.interleave_datasets(
train_datasets, interleave_prob, seed=get_seed(seed), stopping_strategy=stopping_strategy)
val_datasets = loader.interleave_datasets(
val_datasets, interleave_prob, seed=get_seed(seed), stopping_strategy=stopping_strategy)
if shuffle:
if train_datasets:
train_datasets = loader.shuffle_dataset(
train_datasets, seed=get_seed(seed), buffer_size=shuffle_buffer_size)
if val_datasets:
val_datasets = loader.shuffle_dataset(val_datasets, seed=get_seed(seed), buffer_size=shuffle_buffer_size)
return train_datasets, val_datasets