Distillflow 0.2.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- distillflow/common/__init__.py +10 -0
- distillflow/common/common.py +98 -0
- distillflow/common/logger.py +101 -0
- distillflow/config/__init__.py +6 -0
- distillflow/config/config.py +26 -0
- distillflow/config/validator.py +38 -0
- distillflow/datasets/__init__.py +6 -0
- distillflow/datasets/args.py +99 -0
- distillflow/datasets/loader.py +191 -0
- distillflow/datasets/template/__init__.py +12 -0
- distillflow/datasets/template/alpaca.py +85 -0
- distillflow/datasets/template/args.py +56 -0
- distillflow/datasets/template/role.py +10 -0
- distillflow/datasets/template/sharegpt.py +84 -0
- distillflow/datasets/template/template.py +6 -0
- distillflow/evaluation/__init__.py +0 -0
- distillflow/evaluation/rouge.py +30 -0
- distillflow/model/__init__.py +0 -0
- distillflow/model/adapter.py +257 -0
- distillflow/model/args.py +173 -0
- distillflow/model/checkpoint.py +146 -0
- distillflow/model/finetuning_args.py +132 -0
- distillflow/model/generating_args.py +72 -0
- distillflow/model/liger_kernel.py +46 -0
- distillflow/model/loader.py +286 -0
- distillflow/model/quantization.py +193 -0
- distillflow/model/tokenizer.py +55 -0
- distillflow/model/unsloth.py +92 -0
- distillflow/trainer/AdaptationLayer.py +82 -0
- distillflow/trainer/__init__.py +0 -0
- distillflow/trainer/args.py +56 -0
- distillflow/trainer/attention_distillation.py +113 -0
- distillflow/trainer/fine_tuning.py +51 -0
- distillflow/trainer/layers_distillation.py +106 -0
- distillflow/trainer/logits_distillation.py +96 -0
- distillflow-0.2.0.dist-info/LICENSE +201 -0
- distillflow-0.2.0.dist-info/METADATA +162 -0
- distillflow-0.2.0.dist-info/RECORD +39 -0
- distillflow-0.2.0.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from typing import Tuple, List
|
|
3
|
+
|
|
4
|
+
from transformers import is_torch_xpu_available, is_torch_npu_available, PreTrainedModel
|
|
5
|
+
from transformers.utils import is_torch_mps_available, is_torch_cuda_available, is_torch_bf16_gpu_available
|
|
6
|
+
import torch
|
|
7
|
+
from .logger import get_logger
|
|
8
|
+
|
|
9
|
+
logger = get_logger(__name__)
|
|
10
|
+
|
|
11
|
+
def get_current_device() -> torch.device:
|
|
12
|
+
local_rank = os.environ.get("LOCAL_RANK", "0")
|
|
13
|
+
device_map = {
|
|
14
|
+
is_torch_xpu_available: "xpu",
|
|
15
|
+
is_torch_npu_available: "npu",
|
|
16
|
+
is_torch_mps_available: "mps",
|
|
17
|
+
is_torch_cuda_available: "cuda"
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
device = "cpu"
|
|
21
|
+
for b, d in device_map.items():
|
|
22
|
+
if b():
|
|
23
|
+
device = "{}:{}".format(d, local_rank)
|
|
24
|
+
|
|
25
|
+
return torch.device(device)
|
|
26
|
+
|
|
27
|
+
def count_parameters(model: "torch.nn.Module") -> Tuple[int, int]:
|
|
28
|
+
r"""
|
|
29
|
+
Returns the number of trainable parameters and number of all parameters in the model.
|
|
30
|
+
"""
|
|
31
|
+
trainable_params, all_param = 0, 0
|
|
32
|
+
for param in model.parameters():
|
|
33
|
+
num_params = param.numel()
|
|
34
|
+
# if using DS Zero 3 and the weights are initialized empty
|
|
35
|
+
if num_params == 0 and hasattr(param, "ds_numel"):
|
|
36
|
+
num_params = param.ds_numel
|
|
37
|
+
|
|
38
|
+
# Due to the design of 4bit linear layers from bitsandbytes, multiply the number of parameters by itemsize
|
|
39
|
+
if param.__class__.__name__ == "Params4bit":
|
|
40
|
+
if hasattr(param, "quant_storage") and hasattr(param.quant_storage, "itemsize"):
|
|
41
|
+
num_bytes = param.quant_storage.itemsize
|
|
42
|
+
elif hasattr(param, "element_size"): # for older pytorch version
|
|
43
|
+
num_bytes = param.element_size()
|
|
44
|
+
else:
|
|
45
|
+
num_bytes = 1
|
|
46
|
+
|
|
47
|
+
num_params = num_params * 2 * num_bytes
|
|
48
|
+
|
|
49
|
+
all_param += num_params
|
|
50
|
+
if param.requires_grad:
|
|
51
|
+
trainable_params += num_params
|
|
52
|
+
|
|
53
|
+
return trainable_params, all_param
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def find_all_linear_modules(model: PreTrainedModel) -> List[str]:
|
|
57
|
+
r"""
|
|
58
|
+
Finds all available modules to apply lora or galore.
|
|
59
|
+
"""
|
|
60
|
+
model_type = getattr(model.config, "model_type", None)
|
|
61
|
+
forbidden_modules = {"lm_head"}
|
|
62
|
+
if model_type == "chatglm":
|
|
63
|
+
forbidden_modules.add("output_layer")
|
|
64
|
+
elif model_type == "internlm2":
|
|
65
|
+
forbidden_modules.add("output")
|
|
66
|
+
elif model_type in ["llava", "llava_next", "llava_next_video", "paligemma", "video_llava"]:
|
|
67
|
+
forbidden_modules.add("multi_modal_projector")
|
|
68
|
+
elif model_type == "qwen2_vl":
|
|
69
|
+
forbidden_modules.add("merger")
|
|
70
|
+
|
|
71
|
+
module_names = set()
|
|
72
|
+
for name, module in model.named_modules():
|
|
73
|
+
if any(forbidden_module in name for forbidden_module in forbidden_modules):
|
|
74
|
+
continue
|
|
75
|
+
|
|
76
|
+
if "Linear" in module.__class__.__name__ and "Embedding" not in module.__class__.__name__:
|
|
77
|
+
module_names.add(name.split(".")[-1])
|
|
78
|
+
|
|
79
|
+
logger.info("Found linear modules: {}".format(",".join(module_names)))
|
|
80
|
+
return list(module_names)
|
|
81
|
+
|
|
82
|
+
_is_fp16_available = is_torch_npu_available() or is_torch_cuda_available()
|
|
83
|
+
try:
|
|
84
|
+
_is_bf16_available = is_torch_bf16_gpu_available() or (is_torch_npu_available() and torch.npu.is_bf16_supported())
|
|
85
|
+
except Exception:
|
|
86
|
+
_is_bf16_available = False
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def infer_optim_dtype(model_dtype: torch.dtype) -> torch.dtype:
|
|
90
|
+
r"""
|
|
91
|
+
Infers the optimal dtype according to the model_dtype and device compatibility.
|
|
92
|
+
"""
|
|
93
|
+
if _is_bf16_available and model_dtype == torch.bfloat16:
|
|
94
|
+
return torch.bfloat16
|
|
95
|
+
elif _is_fp16_available:
|
|
96
|
+
return torch.float16
|
|
97
|
+
else:
|
|
98
|
+
return torch.float32
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import os
|
|
3
|
+
import sys
|
|
4
|
+
import threading
|
|
5
|
+
from concurrent.futures import ThreadPoolExecutor
|
|
6
|
+
from typing import Optional
|
|
7
|
+
|
|
8
|
+
_thread_lock = threading.RLock()
|
|
9
|
+
_default_handler: Optional["logging.Handler"] = None
|
|
10
|
+
_default_log_level: "logging._Level" = logging.INFO
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class LoggerHandler(logging.Handler):
|
|
14
|
+
r"""
|
|
15
|
+
Redirects the logging output to the logging file for LLaMA Board.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
def __init__(self, output_dir: str) -> None:
|
|
19
|
+
super().__init__()
|
|
20
|
+
formatter = logging.Formatter(
|
|
21
|
+
fmt="%(asctime)s - %(levelname)s - %(name)s - %(message)s", datefmt="%m/%d/%Y %H:%M:%S"
|
|
22
|
+
)
|
|
23
|
+
self.setLevel(logging.INFO)
|
|
24
|
+
self.setFormatter(formatter)
|
|
25
|
+
|
|
26
|
+
os.makedirs(output_dir, exist_ok=True)
|
|
27
|
+
self.running_log = os.path.join(output_dir, "log.txt")
|
|
28
|
+
if os.path.exists(self.running_log):
|
|
29
|
+
os.remove(self.running_log)
|
|
30
|
+
|
|
31
|
+
self.thread_pool = ThreadPoolExecutor(max_workers=1)
|
|
32
|
+
|
|
33
|
+
def _write_log(self, log_entry: str) -> None:
|
|
34
|
+
with open(self.running_log, "a", encoding="utf-8") as f:
|
|
35
|
+
f.write(log_entry + "\n\n")
|
|
36
|
+
|
|
37
|
+
def emit(self, record) -> None:
|
|
38
|
+
if record.name == "httpx":
|
|
39
|
+
return
|
|
40
|
+
|
|
41
|
+
log_entry = self.format(record)
|
|
42
|
+
self.thread_pool.submit(self._write_log, log_entry)
|
|
43
|
+
|
|
44
|
+
def close(self) -> None:
|
|
45
|
+
self.thread_pool.shutdown(wait=True)
|
|
46
|
+
return super().close()
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _get_default_logging_level() -> "logging._Level":
|
|
50
|
+
r"""
|
|
51
|
+
Returns the default logging level.
|
|
52
|
+
"""
|
|
53
|
+
env_level_str = os.environ.get("LLAMAFACTORY_VERBOSITY", None)
|
|
54
|
+
if env_level_str:
|
|
55
|
+
if env_level_str.upper() in logging._nameToLevel:
|
|
56
|
+
return logging._nameToLevel[env_level_str.upper()]
|
|
57
|
+
else:
|
|
58
|
+
raise ValueError("Unknown logging level: {}.".format(env_level_str))
|
|
59
|
+
|
|
60
|
+
return _default_log_level
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def _get_library_name() -> str:
|
|
64
|
+
return __name__.split(".")[0]
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def _get_library_root_logger() -> "logging.Logger":
|
|
68
|
+
return logging.getLogger(_get_library_name())
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _configure_library_root_logger() -> None:
|
|
72
|
+
r"""
|
|
73
|
+
Configures root logger using a stdout stream handler with an explicit format.
|
|
74
|
+
"""
|
|
75
|
+
global _default_handler
|
|
76
|
+
|
|
77
|
+
with _thread_lock:
|
|
78
|
+
if _default_handler:
|
|
79
|
+
return
|
|
80
|
+
|
|
81
|
+
formatter = logging.Formatter(
|
|
82
|
+
fmt="%(asctime)s - %(levelname)s - %(name)s - %(message)s",
|
|
83
|
+
datefmt="%m/%d/%Y %H:%M:%S",
|
|
84
|
+
)
|
|
85
|
+
_default_handler = logging.StreamHandler(sys.stdout)
|
|
86
|
+
_default_handler.setFormatter(formatter)
|
|
87
|
+
library_root_logger = _get_library_root_logger()
|
|
88
|
+
library_root_logger.addHandler(_default_handler)
|
|
89
|
+
library_root_logger.setLevel(_get_default_logging_level())
|
|
90
|
+
library_root_logger.propagate = False
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def get_logger(name: Optional[str] = None) -> "logging.Logger":
|
|
94
|
+
r"""
|
|
95
|
+
Returns a logger with the specified name. It it not supposed to be accessed externally.
|
|
96
|
+
"""
|
|
97
|
+
if name is None:
|
|
98
|
+
name = _get_library_name()
|
|
99
|
+
|
|
100
|
+
_configure_library_root_logger()
|
|
101
|
+
return logging.getLogger(name)
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
from typing import Optional
|
|
2
|
+
|
|
3
|
+
from pydantic import BaseModel, Field
|
|
4
|
+
|
|
5
|
+
from distillflow.datasets.args import DataArgs
|
|
6
|
+
from distillflow.model.args import ModelArgs
|
|
7
|
+
from distillflow.trainer.args import DistillArgs
|
|
8
|
+
|
|
9
|
+
class Config(BaseModel):
|
|
10
|
+
student_model: ModelArgs = Field(
|
|
11
|
+
description="Details about the student model that we want to train"
|
|
12
|
+
)
|
|
13
|
+
teacher_model: ModelArgs = Field(
|
|
14
|
+
description="Details about the teacher model",
|
|
15
|
+
default=None
|
|
16
|
+
)
|
|
17
|
+
data: DataArgs = Field(
|
|
18
|
+
description="The datasets that we want to choose to run the training"
|
|
19
|
+
)
|
|
20
|
+
distill: DistillArgs = Field(
|
|
21
|
+
description="Distillation training parameters"
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
model_config = {
|
|
25
|
+
"extra": "forbid"
|
|
26
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
from pydantic import ValidationError
|
|
2
|
+
|
|
3
|
+
from . import Config
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def print_validation_error(error: ValidationError):
|
|
7
|
+
"""
|
|
8
|
+
Print detailed validation errors including field descriptions and examples
|
|
9
|
+
"""
|
|
10
|
+
print("Validation Error:")
|
|
11
|
+
print("-" * 50)
|
|
12
|
+
|
|
13
|
+
for error in error.errors():
|
|
14
|
+
# Get the field path
|
|
15
|
+
field_path = " -> ".join(str(item) for item in error['loc'])
|
|
16
|
+
|
|
17
|
+
# Find the relevant model and field
|
|
18
|
+
current_model = Config
|
|
19
|
+
field_info = None
|
|
20
|
+
|
|
21
|
+
for item in error['loc']:
|
|
22
|
+
if isinstance(item, int):
|
|
23
|
+
continue
|
|
24
|
+
if hasattr(current_model, 'model_fields') and item in current_model.model_fields:
|
|
25
|
+
field_info = current_model.model_fields[item]
|
|
26
|
+
# If this field is another model, update current_model for nested fields
|
|
27
|
+
if hasattr(field_info.annotation, 'model_fields'):
|
|
28
|
+
current_model = field_info.annotation
|
|
29
|
+
|
|
30
|
+
print(f"Field: {field_path}")
|
|
31
|
+
print(f"Error: {error['msg']}")
|
|
32
|
+
|
|
33
|
+
if field_info:
|
|
34
|
+
if field_info.description:
|
|
35
|
+
print(f"Description: {field_info.description}")
|
|
36
|
+
if field_info.examples:
|
|
37
|
+
print(f"Example: {field_info.examples}")
|
|
38
|
+
print("-" * 50)
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
from pydantic import BaseModel, Field, model_validator
|
|
2
|
+
|
|
3
|
+
from typing import Optional, Literal, List
|
|
4
|
+
|
|
5
|
+
from .template.args import TemplateArgs
|
|
6
|
+
|
|
7
|
+
class DatasetArgs(BaseModel):
|
|
8
|
+
path: str = Field(
|
|
9
|
+
description="The path to huggingface dataset"
|
|
10
|
+
)
|
|
11
|
+
template: TemplateArgs = None
|
|
12
|
+
num_samples: Optional[int] = Field (
|
|
13
|
+
default=None,
|
|
14
|
+
description="Number of samples to pick from the given dataset",
|
|
15
|
+
examples=[1000, 100_000]
|
|
16
|
+
)
|
|
17
|
+
load_from_cache_file: bool = Field(
|
|
18
|
+
default=True,
|
|
19
|
+
description="Should load the dataset from cache if available, if disabled, the dataset will be re-synced from HF"
|
|
20
|
+
)
|
|
21
|
+
split: str = Field(
|
|
22
|
+
default="train",
|
|
23
|
+
description="Split to be used for this dataset (defaults to `train`)",
|
|
24
|
+
examples=["train", "test"]
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
model_config = {
|
|
28
|
+
"extra": "forbid"
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
class DataArgs(BaseModel):
|
|
32
|
+
r"""
|
|
33
|
+
Arguments pertaining to what data we are going to input our model for training and evaluation.
|
|
34
|
+
"""
|
|
35
|
+
seed: Optional[int] = Field(
|
|
36
|
+
default=0,
|
|
37
|
+
description="Seed to use when shuffling the dataset."
|
|
38
|
+
)
|
|
39
|
+
train_datasets: List[DatasetArgs] = Field(
|
|
40
|
+
description="The dataset(s) to use for training. Provide as a list of DatasetArgs."
|
|
41
|
+
)
|
|
42
|
+
eval_datasets: Optional[List[DatasetArgs]] = Field(
|
|
43
|
+
default=None,
|
|
44
|
+
description="The names of dataset(s) to use for evaluation. Provide as a list of DatasetArgs."
|
|
45
|
+
)
|
|
46
|
+
cache_dir: Optional[str] = Field(
|
|
47
|
+
default=None,
|
|
48
|
+
description="Where to store the pre-trained datasets downloaded."
|
|
49
|
+
)
|
|
50
|
+
hf_hub_token: Optional[str] = Field(
|
|
51
|
+
default=None,
|
|
52
|
+
description="Auth token to log in with Hugging Face Hub."
|
|
53
|
+
)
|
|
54
|
+
text_field: Optional[str] = Field(
|
|
55
|
+
default=None,
|
|
56
|
+
description="Name of the field key to convert the dataset."
|
|
57
|
+
)
|
|
58
|
+
streaming: bool = Field(
|
|
59
|
+
default=False,
|
|
60
|
+
description="Enable dataset streaming."
|
|
61
|
+
)
|
|
62
|
+
buffer_size: Optional[int] = Field(
|
|
63
|
+
default=16384,
|
|
64
|
+
description="Size of the buffer to randomly sample examples from in dataset streaming."
|
|
65
|
+
)
|
|
66
|
+
mix_strategy: Optional[Literal["concat", "interleave_under", "interleave_over"]] = Field(
|
|
67
|
+
default="concat",
|
|
68
|
+
description="Strategy to use in dataset mixing (concat/interleave) (undersampling/oversampling)."
|
|
69
|
+
)
|
|
70
|
+
interleave_probs: Optional[str] = Field(
|
|
71
|
+
default=None,
|
|
72
|
+
description="Probabilities to sample data from datasets. Use commas to separate multiple datasets."
|
|
73
|
+
)
|
|
74
|
+
max_samples: Optional[int] = Field(
|
|
75
|
+
default=None,
|
|
76
|
+
description="For debugging purposes, truncate the number of examples for each dataset."
|
|
77
|
+
)
|
|
78
|
+
test_size: float = Field(
|
|
79
|
+
default=0.0,
|
|
80
|
+
description="Size of the development set, should be an integer or a float in range `[0,1)`.",
|
|
81
|
+
examples=[1000, 100_000, 0.2, 0.5]
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
@model_validator(mode='after')
|
|
85
|
+
def validate_args(self) -> 'DataArgs':
|
|
86
|
+
if self.eval_datasets is not None and self.test_size > 0:
|
|
87
|
+
raise ValueError("Cannot specify `val_size` if `eval_dataset` is not None.")
|
|
88
|
+
|
|
89
|
+
if self.streaming and 1e-6 < self.test_size < 1:
|
|
90
|
+
raise ValueError("Streaming mode should have an integer test size.")
|
|
91
|
+
|
|
92
|
+
if self.streaming and self.max_samples is not None:
|
|
93
|
+
raise ValueError("`max_samples` is incompatible with `streaming`.")
|
|
94
|
+
|
|
95
|
+
return self
|
|
96
|
+
|
|
97
|
+
model_config = {
|
|
98
|
+
"extra": "forbid"
|
|
99
|
+
}
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
from functools import partial
|
|
2
|
+
from typing import TypedDict, Optional, Dict, Any, List
|
|
3
|
+
|
|
4
|
+
import numpy as np
|
|
5
|
+
from datasets import DatasetDict, load_dataset, Dataset, concatenate_datasets, interleave_datasets, IterableDataset
|
|
6
|
+
from transformers import PreTrainedTokenizer
|
|
7
|
+
|
|
8
|
+
from .args import DatasetArgs, DataArgs
|
|
9
|
+
from .template import ShareGpt, Alpaca
|
|
10
|
+
from ..common import get_logger
|
|
11
|
+
|
|
12
|
+
logger = get_logger(__name__)
|
|
13
|
+
|
|
14
|
+
class DatasetModule(TypedDict):
|
|
15
|
+
train_dataset: Optional[Dataset]
|
|
16
|
+
eval_dataset: Optional[Dataset]
|
|
17
|
+
|
|
18
|
+
def _load_single_dataset(
|
|
19
|
+
dataset_args: DatasetArgs,
|
|
20
|
+
data_args: DataArgs,
|
|
21
|
+
tokenizer: PreTrainedTokenizer) -> Dataset:
|
|
22
|
+
|
|
23
|
+
if dataset_args is not None:
|
|
24
|
+
logger.info("Loading dataset {}...".format(dataset_args))
|
|
25
|
+
dataset = load_dataset(
|
|
26
|
+
path=dataset_args.path,
|
|
27
|
+
split=dataset_args.split,
|
|
28
|
+
cache_dir=data_args.cache_dir,
|
|
29
|
+
token=data_args.hf_hub_token,
|
|
30
|
+
streaming=data_args.streaming, # and (dataset_attr.load_from != "file")),
|
|
31
|
+
trust_remote_code=True
|
|
32
|
+
)
|
|
33
|
+
# Shuffle dataset with a pre-seed
|
|
34
|
+
dataset = dataset.shuffle(seed=data_args.seed)
|
|
35
|
+
|
|
36
|
+
# if data_args.streaming and (dataset_attr.load_from == "file"): # faster than specifying streaming=True
|
|
37
|
+
# dataset = dataset.to_iterable_dataset() # TODO: add num shards parameter
|
|
38
|
+
|
|
39
|
+
if dataset_args.num_samples is not None:# and not data_args.streaming:
|
|
40
|
+
target_num = dataset_args.num_samples
|
|
41
|
+
indexes = np.random.permutation(len(dataset))[:target_num] # all samples should be included
|
|
42
|
+
target_num -= len(indexes)
|
|
43
|
+
if target_num > 0:
|
|
44
|
+
expand_indexes = np.random.choice(len(dataset), target_num)
|
|
45
|
+
indexes = np.concatenate((indexes, expand_indexes), axis=0)
|
|
46
|
+
|
|
47
|
+
assert len(indexes) == dataset_args.num_samples, "Sample num mismatched."
|
|
48
|
+
dataset = dataset.select(indexes)
|
|
49
|
+
logger.info("Sampled {} examples from dataset {}.".format(dataset_args.num_samples, dataset_args))
|
|
50
|
+
|
|
51
|
+
if data_args.max_samples is not None: # truncate dataset
|
|
52
|
+
max_samples = min(data_args.max_samples, len(dataset))
|
|
53
|
+
dataset = dataset.select(range(max_samples))
|
|
54
|
+
|
|
55
|
+
column_names = list(next(iter(dataset)).keys())
|
|
56
|
+
|
|
57
|
+
template = dataset_args.template
|
|
58
|
+
template_mapping = {
|
|
59
|
+
"sharegpt": ShareGpt(template.args),
|
|
60
|
+
"alpaca": Alpaca(template.args)
|
|
61
|
+
}
|
|
62
|
+
if data_args.streaming:
|
|
63
|
+
dataset = dataset.map(
|
|
64
|
+
partial(template_mapping[template.name].convert),
|
|
65
|
+
batched=False,
|
|
66
|
+
remove_columns=column_names,
|
|
67
|
+
)
|
|
68
|
+
else:
|
|
69
|
+
dataset = dataset.map(
|
|
70
|
+
partial(template_mapping[template.name].convert),
|
|
71
|
+
batched=False,
|
|
72
|
+
remove_columns=column_names,
|
|
73
|
+
load_from_cache_file=dataset_args.load_from_cache_file
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
if data_args.text_field is not None:
|
|
77
|
+
if isinstance(dataset, IterableDataset):
|
|
78
|
+
dataset = dataset.map(partial(to_text, data_args.text_field, tokenizer), batched=False,
|
|
79
|
+
remove_columns=dataset.column_names)
|
|
80
|
+
else:
|
|
81
|
+
dataset = dataset.map(partial(to_text, data_args.text_field, tokenizer), batched=False, remove_columns=dataset.column_names,
|
|
82
|
+
load_from_cache_file=dataset_args.load_from_cache_file)
|
|
83
|
+
return dataset
|
|
84
|
+
|
|
85
|
+
def to_text(field_name, tokenizer: PreTrainedTokenizer, example: Dict[str, Any]) -> Dict[str, Any]:
|
|
86
|
+
system = example["_system"]
|
|
87
|
+
prompt = example["_prompt"]
|
|
88
|
+
response = example["_response"]
|
|
89
|
+
message = system + prompt + response
|
|
90
|
+
return {field_name: tokenizer.apply_chat_template(message, tokenize=False, add_generation_prompt=True)}
|
|
91
|
+
|
|
92
|
+
def split_dataset(dataset: Dataset, data_args: DataArgs, seed: int) -> DatasetDict:
|
|
93
|
+
r"""
|
|
94
|
+
Splits the dataset and returns a dataset dict containing train set and validation set.
|
|
95
|
+
|
|
96
|
+
Supports both map dataset and iterable dataset.
|
|
97
|
+
"""
|
|
98
|
+
if data_args.streaming:
|
|
99
|
+
dataset = dataset.shuffle(buffer_size=data_args.buffer_size, seed=seed)
|
|
100
|
+
val_set = dataset.take(int(data_args.test_size))
|
|
101
|
+
train_set = dataset.skip(int(data_args.test_size))
|
|
102
|
+
return DatasetDict({"train": train_set, "validation": val_set})
|
|
103
|
+
else:
|
|
104
|
+
dataset = dataset.shuffle(seed=seed)
|
|
105
|
+
test_size = int(data_args.test_size) if data_args.test_size > 1 else data_args.test_size
|
|
106
|
+
dataset = dataset.train_test_split(test_size=test_size, seed=seed)
|
|
107
|
+
return DatasetDict({"train": dataset["train"], "validation": dataset["test"]})
|
|
108
|
+
|
|
109
|
+
def get_dataset(data_args: DataArgs,
|
|
110
|
+
tokenizer: PreTrainedTokenizer,
|
|
111
|
+
tokenizer_function=None) -> DatasetModule:
|
|
112
|
+
# Load and preprocess dataset
|
|
113
|
+
# with training_args.main_process_first(desc="load dataset"):
|
|
114
|
+
dataset = _get_merged_dataset(data_args.train_datasets, data_args, tokenizer)
|
|
115
|
+
dataset = dataset.shuffle(seed=data_args.seed)
|
|
116
|
+
eval_dataset = None
|
|
117
|
+
if data_args.eval_datasets:
|
|
118
|
+
eval_dataset = _get_merged_dataset(data_args.eval_datasets, data_args, tokenizer)
|
|
119
|
+
|
|
120
|
+
dataset_dict = {}
|
|
121
|
+
|
|
122
|
+
if eval_dataset is None:
|
|
123
|
+
dataset_dict = split_dataset(dataset, data_args, data_args.seed)
|
|
124
|
+
else:
|
|
125
|
+
if data_args.streaming:
|
|
126
|
+
eval_dataset = eval_dataset.shuffle(buffer_size=data_args.buffer_size, seed=data_args.seed)
|
|
127
|
+
|
|
128
|
+
dataset_dict["validation"] = eval_dataset
|
|
129
|
+
|
|
130
|
+
if dataset is not None:
|
|
131
|
+
if data_args.streaming:
|
|
132
|
+
dataset = dataset.shuffle(buffer_size=data_args.buffer_size, seed=data_args.train_datasets.seed)
|
|
133
|
+
|
|
134
|
+
dataset_dict["train"] = dataset
|
|
135
|
+
|
|
136
|
+
dataset_dict = DatasetDict(dataset_dict)
|
|
137
|
+
|
|
138
|
+
dataset_module = {}
|
|
139
|
+
if "train" in dataset_dict:
|
|
140
|
+
dataset_module["train_dataset"] = dataset_dict["train"]
|
|
141
|
+
|
|
142
|
+
if "validation" in dataset_dict:
|
|
143
|
+
dataset_module["eval_dataset"] = dataset_dict["validation"]
|
|
144
|
+
|
|
145
|
+
if tokenizer_function is not None:
|
|
146
|
+
dataset_module["train_dataset"] = dataset_module["train_dataset"].map(tokenizer_function,
|
|
147
|
+
batched=True, num_proc=32, remove_columns=[data_args.text_field])
|
|
148
|
+
|
|
149
|
+
dataset_module["eval_dataset"] = dataset_module["eval_dataset"].map(tokenizer_function,
|
|
150
|
+
batched=True, num_proc=32, remove_columns=[data_args.text_field])
|
|
151
|
+
return dataset_module
|
|
152
|
+
|
|
153
|
+
def _get_merged_dataset(
|
|
154
|
+
dataset_list: List[DatasetArgs],
|
|
155
|
+
data_args: DataArgs,
|
|
156
|
+
tokenizer: PreTrainedTokenizer
|
|
157
|
+
) -> Optional[Dataset]:
|
|
158
|
+
r"""
|
|
159
|
+
Gets the merged datasets in the standard format.
|
|
160
|
+
"""
|
|
161
|
+
datasets = []
|
|
162
|
+
for dataset_attr in dataset_list:
|
|
163
|
+
datasets.append(_load_single_dataset(dataset_attr, data_args, tokenizer))
|
|
164
|
+
|
|
165
|
+
return merge_dataset(datasets, data_args, data_args.seed)
|
|
166
|
+
|
|
167
|
+
def merge_dataset(all_datasets: List[Dataset], data_args: DataArgs, seed) -> Dataset:
|
|
168
|
+
r"""
|
|
169
|
+
Merges multiple datasets to a unified dataset.
|
|
170
|
+
"""
|
|
171
|
+
if len(all_datasets) == 1:
|
|
172
|
+
return all_datasets[0]
|
|
173
|
+
elif data_args.mix_strategy == "concat":
|
|
174
|
+
if data_args.streaming:
|
|
175
|
+
logger.warning("The samples between different datasets will not be mixed in streaming mode.")
|
|
176
|
+
return concatenate_datasets(all_datasets)
|
|
177
|
+
elif data_args.mix_strategy.startswith("interleave"):
|
|
178
|
+
if not data_args.streaming:
|
|
179
|
+
logger.warning("We recommend using `mix_strategy=concat` in non-streaming mode.")
|
|
180
|
+
|
|
181
|
+
return interleave_datasets(
|
|
182
|
+
datasets=all_datasets,
|
|
183
|
+
probabilities=data_args.interleave_probs,
|
|
184
|
+
seed=seed,
|
|
185
|
+
stopping_strategy="first_exhausted" if data_args.mix_strategy.endswith("under") else "all_exhausted",
|
|
186
|
+
)
|
|
187
|
+
else:
|
|
188
|
+
raise ValueError("Unknown mixing strategy: {}.".format(data_args.mix_strategy))
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from typing import Dict, Any, Optional
|
|
3
|
+
|
|
4
|
+
from .template import Template
|
|
5
|
+
from .role import Role
|
|
6
|
+
|
|
7
|
+
@dataclass
|
|
8
|
+
class AlpacaArgs:
|
|
9
|
+
prompt: Optional[str] = "instruction"
|
|
10
|
+
query: Optional[str] = "input"
|
|
11
|
+
response: Optional[str] = "output"
|
|
12
|
+
history: Optional[str] = None
|
|
13
|
+
system_tag: Optional[str] = None
|
|
14
|
+
tools: Optional[str] = None
|
|
15
|
+
|
|
16
|
+
'''
|
|
17
|
+
Expected data format
|
|
18
|
+
[
|
|
19
|
+
{
|
|
20
|
+
"instruction": "human instruction (required)",
|
|
21
|
+
"input": "human input (optional)",
|
|
22
|
+
"output": "model response (required)",
|
|
23
|
+
"system": "system prompt (optional)",
|
|
24
|
+
"history": [
|
|
25
|
+
["human instruction in the first round (optional)", "model response in the first round (optional)"],
|
|
26
|
+
["human instruction in the second round (optional)", "model response in the second round (optional)"]
|
|
27
|
+
]
|
|
28
|
+
}
|
|
29
|
+
]
|
|
30
|
+
'''
|
|
31
|
+
class Alpaca(Template):
|
|
32
|
+
def __init__(self, args: Optional[AlpacaArgs] = AlpacaArgs()):
|
|
33
|
+
self.args = args
|
|
34
|
+
|
|
35
|
+
def convert(self, example: Dict[str, Any]) -> Dict[str, Any]:
|
|
36
|
+
r"""
|
|
37
|
+
Converts alpaca format dataset to the standard format.
|
|
38
|
+
"""
|
|
39
|
+
prompt = []
|
|
40
|
+
if self.args.history and isinstance(example[self.args.history], list):
|
|
41
|
+
for old_prompt, old_response in example[self.args.history]:
|
|
42
|
+
prompt.append({"role": Role.USER.value, "content": old_prompt})
|
|
43
|
+
prompt.append({"role": Role.ASSISTANT.value, "content": old_response})
|
|
44
|
+
|
|
45
|
+
query = []
|
|
46
|
+
if self.args.prompt and example[self.args.prompt]:
|
|
47
|
+
query.append(example[self.args.prompt])
|
|
48
|
+
|
|
49
|
+
if self.args.query and example[self.args.query]:
|
|
50
|
+
query.append(example[self.args.query])
|
|
51
|
+
|
|
52
|
+
prompt.append({"role": Role.USER.value, "content": "\n".join(query)})
|
|
53
|
+
|
|
54
|
+
# if args.kto_tag and isinstance(example[args.kto_tag], bool): # kto example
|
|
55
|
+
# response = [{"role": Role.ASSISTANT.value, "content": example[args.response]}]
|
|
56
|
+
# if example[self.args.kto_tag]:
|
|
57
|
+
# response = response + [{"role": Role.ASSISTANT.value, "content": ""}]
|
|
58
|
+
# else:
|
|
59
|
+
# response = [{"role": Role.ASSISTANT.value, "content": ""}] + response
|
|
60
|
+
# elif (
|
|
61
|
+
# args.ranking
|
|
62
|
+
# and isinstance(example[args.chosen], str)
|
|
63
|
+
# and isinstance(example[args.rejected], str)
|
|
64
|
+
# ): # pairwise example
|
|
65
|
+
# response = [
|
|
66
|
+
# {"role": Role.ASSISTANT.value, "content": example[args.chosen]},
|
|
67
|
+
# {"role": Role.ASSISTANT.value, "content": example[args.rejected]},
|
|
68
|
+
# ]
|
|
69
|
+
if self.args.response and isinstance(example[self.args.response], str): # normal example
|
|
70
|
+
response = [{"role": Role.ASSISTANT.value, "content": example[self.args.response]}]
|
|
71
|
+
else: # unsupervised
|
|
72
|
+
response = []
|
|
73
|
+
|
|
74
|
+
system = [{"role": Role.SYSTEM.value,
|
|
75
|
+
"content": example[self.args.system_tag]} if self.args.system_tag in example else {
|
|
76
|
+
"role": Role.SYSTEM.value,
|
|
77
|
+
"content": "You are a helpful assistant."}]
|
|
78
|
+
|
|
79
|
+
output = {
|
|
80
|
+
"_prompt": prompt,
|
|
81
|
+
"_response": response,
|
|
82
|
+
"_system": system,
|
|
83
|
+
# "_tools": example[self.args.tools] if self.args.tools else "",
|
|
84
|
+
}
|
|
85
|
+
return output
|