LMFuser 0.0.1__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.
- lmfuser/__init__.py +0 -0
- lmfuser/model_loader.py +33 -0
- lmfuser/optimizers.py +162 -0
- lmfuser/runners/__init__.py +1 -0
- lmfuser/runners/ddp_runner.py +619 -0
- lmfuser/runners/runner.py +40 -0
- lmfuser/schedulers.py +210 -0
- lmfuser/task.py +287 -0
- lmfuser/utils.py +249 -0
- lmfuser-0.0.1.dist-info/METADATA +30 -0
- lmfuser-0.0.1.dist-info/RECORD +13 -0
- lmfuser-0.0.1.dist-info/WHEEL +4 -0
- lmfuser-0.0.1.dist-info/licenses/LICENSE +21 -0
lmfuser/utils.py
ADDED
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from typing import Any, overload, Union, Optional, Literal
|
|
3
|
+
from random import Random
|
|
4
|
+
|
|
5
|
+
import torch
|
|
6
|
+
from torch import distributed as dist
|
|
7
|
+
from torch.utils.data import get_worker_info
|
|
8
|
+
from torch import Tensor
|
|
9
|
+
import atexit
|
|
10
|
+
|
|
11
|
+
from typing import TypeVar
|
|
12
|
+
import random
|
|
13
|
+
import logging
|
|
14
|
+
|
|
15
|
+
logger = logging.getLogger(__name__)
|
|
16
|
+
|
|
17
|
+
T = TypeVar('T')
|
|
18
|
+
|
|
19
|
+
def get_world_size() -> int:
|
|
20
|
+
if 'WORLD_SIZE' in os.environ:
|
|
21
|
+
return int(os.environ['WORLD_SIZE'])
|
|
22
|
+
else:
|
|
23
|
+
return 1
|
|
24
|
+
|
|
25
|
+
def get_global_rank() -> int:
|
|
26
|
+
if 'RANK' in os.environ:
|
|
27
|
+
return int(os.environ['RANK'])
|
|
28
|
+
else:
|
|
29
|
+
return 0
|
|
30
|
+
|
|
31
|
+
def get_local_rank() -> int:
|
|
32
|
+
if "LOCAL_RANK" in os.environ:
|
|
33
|
+
return int(os.environ["LOCAL_RANK"])
|
|
34
|
+
else:
|
|
35
|
+
# Provide a default or handle the case of non-distributed training
|
|
36
|
+
return 0
|
|
37
|
+
|
|
38
|
+
def dist_init() -> None:
|
|
39
|
+
if dist.is_initialized():
|
|
40
|
+
return
|
|
41
|
+
|
|
42
|
+
device_type = get_default_device_type()
|
|
43
|
+
|
|
44
|
+
if device_type == 'cuda':
|
|
45
|
+
dist.init_process_group(backend='nccl')
|
|
46
|
+
torch.cuda.set_device(get_local_rank())
|
|
47
|
+
elif device_type == 'npu':
|
|
48
|
+
dist.init_process_group(backend='hccl')
|
|
49
|
+
else:
|
|
50
|
+
dist.init_process_group(backend='mpi')
|
|
51
|
+
|
|
52
|
+
atexit.register(dist.destroy_process_group)
|
|
53
|
+
|
|
54
|
+
def weighted_random_choice(
|
|
55
|
+
elements: list[T],
|
|
56
|
+
probabilities: list[float],
|
|
57
|
+
rand: Optional[Random] = None
|
|
58
|
+
) -> T:
|
|
59
|
+
"""
|
|
60
|
+
Choose a random element from a list based on specified probabilities.
|
|
61
|
+
|
|
62
|
+
Args:
|
|
63
|
+
elements (List[T]): A list of elements to choose from.
|
|
64
|
+
probabilities (List[float]): A list of probabilities associated with each element. Must sum to 1.
|
|
65
|
+
rand (Optional[Random]): The random number generator.
|
|
66
|
+
|
|
67
|
+
Returns:
|
|
68
|
+
T: A randomly chosen element from the list.
|
|
69
|
+
"""
|
|
70
|
+
if len(elements) != len(probabilities):
|
|
71
|
+
raise ValueError("Elements and probabilities must have the same length.")
|
|
72
|
+
if not all(0 <= p <= 1 for p in probabilities):
|
|
73
|
+
raise ValueError("Probabilities must be non-negative and non-greater than 1.")
|
|
74
|
+
if not abs(sum(probabilities) - 1.0) < 1e-6:
|
|
75
|
+
raise ValueError("Probabilities must sum to 1.")
|
|
76
|
+
|
|
77
|
+
if rand is None:
|
|
78
|
+
index = random.choices(list(range(len(elements))), probabilities)[0]
|
|
79
|
+
else:
|
|
80
|
+
index = rand.choices(list(range(len(elements))), probabilities)[0]
|
|
81
|
+
|
|
82
|
+
return elements[index]
|
|
83
|
+
|
|
84
|
+
def partition_list(lst: list[T], num_shards: int, index: int) -> list[T]:
|
|
85
|
+
# Ensure the number of shards is positive and index is valid
|
|
86
|
+
if num_shards <= 0 or index >= num_shards or index < 0:
|
|
87
|
+
raise ValueError("Invalid number of shards or index, "
|
|
88
|
+
f"number of shards: {num_shards};"
|
|
89
|
+
f"index: {index}.")
|
|
90
|
+
|
|
91
|
+
# Calculate the size of each shard
|
|
92
|
+
shard_size = len(lst) // num_shards
|
|
93
|
+
remainder = len(lst) % num_shards
|
|
94
|
+
|
|
95
|
+
# Calculate the start and end indices for the partition
|
|
96
|
+
start = index * shard_size + min(index, remainder)
|
|
97
|
+
end = (index + 1) * shard_size + min(index + 1, remainder)
|
|
98
|
+
|
|
99
|
+
return lst[start:end]
|
|
100
|
+
|
|
101
|
+
DEVICE_TYPE: Optional[Literal['cuda', 'npu', 'cpu']] = None
|
|
102
|
+
def get_default_device_type() -> Literal['cuda', 'npu', 'cpu']:
|
|
103
|
+
global DEVICE_TYPE
|
|
104
|
+
if DEVICE_TYPE is not None:
|
|
105
|
+
return DEVICE_TYPE
|
|
106
|
+
|
|
107
|
+
device = os.environ.get('HURRICANE_DEVICE')
|
|
108
|
+
if device is not None:
|
|
109
|
+
DEVICE_TYPE = device # type: ignore
|
|
110
|
+
return DEVICE_TYPE # type: ignore
|
|
111
|
+
|
|
112
|
+
if torch.cuda.is_available():
|
|
113
|
+
DEVICE_TYPE = 'cuda'
|
|
114
|
+
return DEVICE_TYPE
|
|
115
|
+
|
|
116
|
+
try:
|
|
117
|
+
__import__('torch_npu')
|
|
118
|
+
if torch_npu.npu.is_available(): # type: ignore
|
|
119
|
+
DEVICE_TYPE = 'npu'
|
|
120
|
+
return DEVICE_TYPE
|
|
121
|
+
except ImportError:
|
|
122
|
+
...
|
|
123
|
+
|
|
124
|
+
DEVICE_TYPE = 'cpu'
|
|
125
|
+
|
|
126
|
+
return DEVICE_TYPE
|
|
127
|
+
|
|
128
|
+
DEVICE: Optional[str] = None
|
|
129
|
+
def get_default_device() -> str | int:
|
|
130
|
+
"""
|
|
131
|
+
Get the default device for the current process.
|
|
132
|
+
"""
|
|
133
|
+
global DEVICE
|
|
134
|
+
if DEVICE is not None:
|
|
135
|
+
return DEVICE
|
|
136
|
+
|
|
137
|
+
device_type = get_default_device_type()
|
|
138
|
+
if device_type == 'cpu':
|
|
139
|
+
return -1
|
|
140
|
+
if device_type == 'cuda':
|
|
141
|
+
return get_local_rank()
|
|
142
|
+
|
|
143
|
+
return f'{device_type}:{get_local_rank()}'
|
|
144
|
+
|
|
145
|
+
@overload
|
|
146
|
+
def dist_avg(value: Tensor) -> Tensor: ...
|
|
147
|
+
@overload
|
|
148
|
+
def dist_avg(value: int) -> float: ...
|
|
149
|
+
@overload
|
|
150
|
+
def dist_avg(value: float) -> float: ...
|
|
151
|
+
def dist_avg(value: Union[torch.Tensor, int, float]) -> Union[torch.Tensor, float]:
|
|
152
|
+
if not dist.is_initialized():
|
|
153
|
+
return value
|
|
154
|
+
|
|
155
|
+
if isinstance(value, torch.Tensor):
|
|
156
|
+
return_tensor = True
|
|
157
|
+
value = value.to(get_default_device())
|
|
158
|
+
else:
|
|
159
|
+
return_tensor = False
|
|
160
|
+
value = torch.tensor(value, device=get_default_device(), dtype=torch.float32)
|
|
161
|
+
|
|
162
|
+
dist.all_reduce(value, dist.ReduceOp.SUM)
|
|
163
|
+
dist.barrier()
|
|
164
|
+
|
|
165
|
+
if return_tensor:
|
|
166
|
+
return value / dist.get_world_size()
|
|
167
|
+
|
|
168
|
+
return value.item() / dist.get_world_size()
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def gather_object(local_object: T) -> list[T]:
|
|
172
|
+
if not dist.is_initialized():
|
|
173
|
+
return [local_object]
|
|
174
|
+
world_size = dist.get_world_size()
|
|
175
|
+
|
|
176
|
+
gathered = [None for _ in range(world_size)]
|
|
177
|
+
dist.all_gather_object(gathered, local_object)
|
|
178
|
+
|
|
179
|
+
if isinstance(gathered[0], list):
|
|
180
|
+
results = []
|
|
181
|
+
for l in gathered:
|
|
182
|
+
results += l # type: ignore
|
|
183
|
+
gathered = results
|
|
184
|
+
|
|
185
|
+
return gathered # type: ignore
|
|
186
|
+
|
|
187
|
+
def tensor_all_gather(tensor: Tensor) -> Tensor:
|
|
188
|
+
if get_world_size() <= 1:
|
|
189
|
+
return tensor
|
|
190
|
+
results = [torch.empty_like(tensor, device=get_default_device()) for _ in range(dist.get_world_size())]
|
|
191
|
+
|
|
192
|
+
dist.all_gather(results, tensor.contiguous().to(get_default_device()))
|
|
193
|
+
|
|
194
|
+
return torch.cat(results, dim=0).to(tensor.device)
|
|
195
|
+
|
|
196
|
+
def batch_all_gather(batch: dict[str, Any]) -> dict[str, Any]:
|
|
197
|
+
"""把各个rank的同一批batch数据汇总到rank0,方便计算metric或者刷库等等。
|
|
198
|
+
不是分布式的话原样返回
|
|
199
|
+
|
|
200
|
+
Args:
|
|
201
|
+
batch (Dict[str, Union[Tensor, List[Any]]]): 一个batch的数据,注意一个key对应一个list
|
|
202
|
+
|
|
203
|
+
Returns:
|
|
204
|
+
Dict[Dict[str, Union[Tensor, List[Any]]]]: 汇总后的batch
|
|
205
|
+
"""
|
|
206
|
+
logger.info(f'Begin all gather on rank {get_global_rank()}')
|
|
207
|
+
if not dist.is_initialized():
|
|
208
|
+
return batch
|
|
209
|
+
|
|
210
|
+
gathered = {}
|
|
211
|
+
for k, v_list in batch.items():
|
|
212
|
+
logger.info(f'Begin gather key {k} on rank {get_global_rank()}')
|
|
213
|
+
if isinstance(v_list, list):
|
|
214
|
+
logger.info(f'Key {k} with length {len(v_list)} on rank {get_global_rank()}')
|
|
215
|
+
logger.info(f'after barrier on rank {get_global_rank()}')
|
|
216
|
+
if isinstance(v_list, Tensor):
|
|
217
|
+
gathered[k] = tensor_all_gather(v_list)
|
|
218
|
+
else:
|
|
219
|
+
if isinstance(v_list[0], Tensor):
|
|
220
|
+
v_tensor = torch.cat(v_list, dim=0)
|
|
221
|
+
gathered[k] = tensor_all_gather(v_tensor)
|
|
222
|
+
else:
|
|
223
|
+
gathered[k] = gather_object(v_list)
|
|
224
|
+
|
|
225
|
+
return gathered
|
|
226
|
+
|
|
227
|
+
def cal_acc_num(batch_size: int, sub_batch_size: int, world_size: int) -> int:
|
|
228
|
+
acc_num = batch_size // sub_batch_size // world_size
|
|
229
|
+
an = batch_size / sub_batch_size / world_size
|
|
230
|
+
if abs(acc_num - an) > 1e-8:
|
|
231
|
+
raise ValueError(
|
|
232
|
+
f'Batchsize无法被平分!请根据显卡数合理设置“BatchSize”与“SubBatchSize”'
|
|
233
|
+
)
|
|
234
|
+
return acc_num
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
class MethodOverideChecker:
|
|
238
|
+
def is_overridden(self, method_name: str) -> bool:
|
|
239
|
+
"""
|
|
240
|
+
Check if the method `method_name` is overridden in this instance's class
|
|
241
|
+
compared to the Parent class.
|
|
242
|
+
"""
|
|
243
|
+
cls = self.__class__
|
|
244
|
+
for parent in self.__class__.__bases__:
|
|
245
|
+
if not hasattr(parent, method_name):
|
|
246
|
+
continue
|
|
247
|
+
return getattr(cls, method_name, None) is not getattr(parent, method_name, None)
|
|
248
|
+
|
|
249
|
+
raise ValueError(f'No such method {method_name} in parent classes')
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: LMFuser
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: The LMFuser training framework.
|
|
5
|
+
Project-URL: Homepage, https://github.com/TYTTYTTYT/LMFuser
|
|
6
|
+
Project-URL: Documentation, https://github.com/TYTTYTTYT/LMFuser
|
|
7
|
+
Project-URL: Repository, https://github.com/TYTTYTTYT/LMFuser
|
|
8
|
+
Project-URL: Bug Tracker, https://github.com/TYTTYTTYT/LMFuser
|
|
9
|
+
Project-URL: Changelog, https://github.com/TYTTYTTYT/LMFuser
|
|
10
|
+
Author-email: Yintao Tai <tai.yintao@gmail.com>
|
|
11
|
+
Maintainer-email: Yintao Tai <tai.yintao@gmail.com>
|
|
12
|
+
License-File: LICENSE
|
|
13
|
+
Keywords: machine learning,pytorch
|
|
14
|
+
Classifier: Intended Audience :: Information Technology
|
|
15
|
+
Classifier: Intended Audience :: Science/Research
|
|
16
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
17
|
+
Classifier: Topic :: Utilities
|
|
18
|
+
Requires-Python: >=3.11
|
|
19
|
+
Requires-Dist: hyperargs>=0.1.3
|
|
20
|
+
Requires-Dist: lmfuser-data
|
|
21
|
+
Requires-Dist: numpy
|
|
22
|
+
Requires-Dist: pandas
|
|
23
|
+
Requires-Dist: pyarrow
|
|
24
|
+
Requires-Dist: torch
|
|
25
|
+
Requires-Dist: tqdm
|
|
26
|
+
Requires-Dist: wandb
|
|
27
|
+
Description-Content-Type: text/markdown
|
|
28
|
+
|
|
29
|
+
# LMFuser
|
|
30
|
+
A robust, multi-task PyTorch framework for training versatile Language Models at scale.
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
lmfuser/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
+
lmfuser/model_loader.py,sha256=Y3Rm_hjdDYvKZuvAnblJ7URcDozOwe-QqBniyXrlzLs,1188
|
|
3
|
+
lmfuser/optimizers.py,sha256=9Mp4-_WuKdEcSweSumm0eGgL8l2nUUlLbcgMPPEbYPU,5476
|
|
4
|
+
lmfuser/schedulers.py,sha256=g5Can4dOOeO9YiK0NLiDZt1dAmGRXN6sFKKIcTFTGR8,8035
|
|
5
|
+
lmfuser/task.py,sha256=3OBLuDnwlqe-ctWqh9WBzS0bbxRoj5gx_PibHBkWhHU,9825
|
|
6
|
+
lmfuser/utils.py,sha256=lIXp1CAtTKqoCMdcU8-bSZxS36AMmBZ8a8j_xDHzoI4,7829
|
|
7
|
+
lmfuser/runners/__init__.py,sha256=azfFi3bGPMDG2SEMFiIxKJgA-JGS6vuRlipkJcB7O90,51
|
|
8
|
+
lmfuser/runners/ddp_runner.py,sha256=y3sIkEtzqrbVQzMy1O5iZbprGTagYCTM98uEiO7vvMU,23995
|
|
9
|
+
lmfuser/runners/runner.py,sha256=L6R1Vp62mpx_73kxAByQrNfIXc-kKv6Vq5eT8kF-fhM,1297
|
|
10
|
+
lmfuser-0.0.1.dist-info/METADATA,sha256=t1ielj85-3NNopKxPvj8G2ovAIBW_Q6c75kRZjLjVPQ,1104
|
|
11
|
+
lmfuser-0.0.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
12
|
+
lmfuser-0.0.1.dist-info/licenses/LICENSE,sha256=4cDSUqshGiBlu4cAQNp8fFvQvy0N5xHX8VdJ0pzbPNU,1067
|
|
13
|
+
lmfuser-0.0.1.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Yintao Tai
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|