project-llm-trainer 0.4.13__py3-none-any.whl → 0.4.15__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.

Potentially problematic release.


This version of project-llm-trainer might be problematic. Click here for more details.

llm_trainer/checkpoint.py CHANGED
@@ -119,29 +119,6 @@ def load_checkpoint_for_eval(
119
119
  load_checkpoint(model, None, device, suffix=suffix)
120
120
 
121
121
 
122
- def copy_model_params(
123
- _from: nn.Module,
124
- _to: Optional[nn.Module]
125
- ):
126
- """
127
- 必须在所有rank上调用,非rank0, _to可以设置为None
128
- """
129
-
130
- if isinstance(TrainerTools().parallel, DsParallel):
131
- from .ds_checkpoint import get_ds_model_params
132
- state_dict = get_ds_model_params(_from)
133
- elif isinstance(TrainerTools().parallel, FsdpParallel):
134
- from .fsdp_checkpoint import get_fsdp_model_params
135
- state_dict = get_fsdp_model_params(_from)
136
- elif isinstance(_from, DDP):
137
- state_dict = _from.module.state_dict()
138
- else:
139
- state_dict = _from.state_dict()
140
-
141
- if _to and state_dict:
142
- _to.load_state_dict(state_dict)
143
-
144
-
145
122
  def save_steps(global_steps: int, lr_scheduler: Optional[LRScheduler] = None):
146
123
  # 暂时只保存主进程的
147
124
  if TrainerTools().parallel.is_main_process:
@@ -12,13 +12,14 @@ from .dataset import DPODataset
12
12
  from .loss import DPOLoss
13
13
  from .tools import TrainerTools
14
14
  from .utils import get_dpo_collate_fn
15
+ from .model_params import copy_model_params
15
16
 
16
17
  from .checkpoint import (
17
18
  save_checkpoint,
18
- copy_model_params,
19
19
  save_steps,
20
20
  )
21
21
 
22
+
22
23
  class DPOTrainer(Trainer):
23
24
  def __init__(
24
25
  self,
@@ -2,11 +2,7 @@ import os
2
2
  from typing import Optional
3
3
  from glob import glob
4
4
  import shutil
5
- import torch
6
5
  from torch import nn
7
- import torch.distributed as dist
8
-
9
- from .tools import TrainerTools
10
6
 
11
7
  try:
12
8
  import deepspeed
@@ -65,65 +61,3 @@ def load_ds_checkpoint_for_eval(model: nn.Module):
65
61
  ckpt_dir = os.environ.get('DIST_CHECKPOINT_DIR', 'checkpoint')
66
62
  state_dict = get_fp32_state_dict_from_zero_checkpoint(ckpt_dir)
67
63
  model.load_state_dict(state_dict)
68
-
69
-
70
- def _get_ds_full_state_dict_on_rank0(model: DeepSpeedEngine) -> Optional[dict]:
71
- """
72
- 需要在所有rank上调用,然后只有rank0有值
73
- """
74
-
75
- if model.zero_optimization_stage() != 3:
76
- if TrainerTools().parallel.is_main_process:
77
- return {k: v.cpu().clone() for k, v in model.module.state_dict().items()}
78
- return None
79
-
80
- # --- ZeRO-3 ---
81
- # 只调用一次 GatheredParameters,传入所有参数
82
- with deepspeed.zero.GatheredParameters(model.parameters(), modifier_rank=0):
83
- if TrainerTools().parallel.is_main_process:
84
- # 在这个 'with' 代码块内,rank 0 上的 model.module 拥有完整的参数
85
- # 所以我们可以像操作普通模型一样直接调用 state_dict()
86
- full_state_dict = model.module.state_dict()
87
-
88
- # 将其克隆到 CPU 并返回
89
- return {k: v.cpu().clone() for k, v in full_state_dict.items()}
90
-
91
- # 其他 rank 执行到这里时,上下文结束,直接返回 None
92
- return None
93
-
94
- # # ZeRO-3
95
- # state_dict_on_rank_0 = {}
96
- # for param_name, param in model.module.named_parameters():
97
- # if hasattr(param, 'ds_id'):
98
- # with deepspeed.zero.GatheredParameters(param, modifier_rank=0):
99
- # if TrainerTools().parallel.is_main_process:
100
- # state_dict_on_rank_0[param_name] = param.data.to(torch.float32).cpu().clone()
101
- # else:
102
- # if TrainerTools().parallel.is_main_process:
103
- # state_dict_on_rank_0[param_name] = param.data.to(torch.float32).cpu().clone()
104
- #
105
- # return state_dict_on_rank_0 if TrainerTools().parallel.is_main_process else None
106
-
107
-
108
- def get_ds_model_params(model: nn.Module):
109
- """
110
- 从一个正在运行的 DeepSpeedEngine 中高效地提取完整的 FP32 state_dict,
111
- 兼容 ZeRO Stages 0, 1, 2, 3。
112
- 包含了对 ZeRO-3 中分片参数的正确处理。
113
- """
114
-
115
- assert isinstance(model, DeepSpeedEngine)
116
- state_dict = _get_ds_full_state_dict_on_rank0(model)
117
-
118
- # 现在,只有 rank 0 上的 state_dict 是一个有效的字典,其他 rank 上是 None。
119
- # 我们需要将其广播给所有进程。
120
- if TrainerTools().parallel.world_size > 1:
121
- # 准备一个列表,rank 0 有数据,其他 rank 是占位符
122
- object_list = [state_dict] if TrainerTools().parallel.is_main_process else [None]
123
- # 执行广播,这个操作是阻塞的,会同步所有进程
124
- dist.broadcast_object_list(object_list, src=0)
125
- # 所有进程从列表中获取广播后的 state_dict 副本
126
- state_dict = object_list[0]
127
-
128
- return state_dict
129
-
@@ -0,0 +1,72 @@
1
+ from typing import Optional
2
+ from torch import nn
3
+ import torch.distributed as dist
4
+
5
+ from .tools import TrainerTools
6
+
7
+ try:
8
+ import deepspeed
9
+ from deepspeed import DeepSpeedEngine
10
+ from deepspeed.utils.zero_to_fp32 import get_fp32_state_dict_from_zero_checkpoint
11
+ except: ...
12
+
13
+
14
+ def _get_ds_full_state_dict_on_rank0(model: DeepSpeedEngine) -> Optional[dict]:
15
+ """
16
+ 需要在所有rank上调用,然后只有rank0有值
17
+ """
18
+
19
+ if model.zero_optimization_stage() != 3:
20
+ if TrainerTools().parallel.is_main_process:
21
+ return {k: v.cpu().clone() for k, v in model.module.state_dict().items()}
22
+ return None
23
+
24
+ # --- ZeRO-3 ---
25
+ # 只调用一次 GatheredParameters,传入所有参数
26
+ with deepspeed.zero.GatheredParameters(model.parameters(), modifier_rank=0):
27
+ if TrainerTools().parallel.is_main_process:
28
+ # 在这个 'with' 代码块内,rank 0 上的 model.module 拥有完整的参数
29
+ # 所以我们可以像操作普通模型一样直接调用 state_dict()
30
+ full_state_dict = model.module.state_dict()
31
+
32
+ # 将其克隆到 CPU 并返回
33
+ return {k: v.cpu().clone() for k, v in full_state_dict.items()}
34
+
35
+ # 其他 rank 执行到这里时,上下文结束,直接返回 None
36
+ return None
37
+
38
+ # # ZeRO-3
39
+ # state_dict_on_rank_0 = {}
40
+ # for param_name, param in model.module.named_parameters():
41
+ # if hasattr(param, 'ds_id'):
42
+ # with deepspeed.zero.GatheredParameters(param, modifier_rank=0):
43
+ # if TrainerTools().parallel.is_main_process:
44
+ # state_dict_on_rank_0[param_name] = param.data.to(torch.float32).cpu().clone()
45
+ # else:
46
+ # if TrainerTools().parallel.is_main_process:
47
+ # state_dict_on_rank_0[param_name] = param.data.to(torch.float32).cpu().clone()
48
+ #
49
+ # return state_dict_on_rank_0 if TrainerTools().parallel.is_main_process else None
50
+
51
+
52
+ def get_ds_model_params(model: nn.Module, only_rank0=False):
53
+ """
54
+ 从一个正在运行的 DeepSpeedEngine 中高效地提取完整的 FP32 state_dict,
55
+ 兼容 ZeRO Stages 0, 1, 2, 3。
56
+ 包含了对 ZeRO-3 中分片参数的正确处理。
57
+ """
58
+
59
+ assert isinstance(model, DeepSpeedEngine)
60
+ state_dict = _get_ds_full_state_dict_on_rank0(model)
61
+
62
+ # 现在,只有 rank 0 上的 state_dict 是一个有效的字典,其他 rank 上是 None。
63
+ # 我们需要将其广播给所有进程。
64
+ if not only_rank0 and TrainerTools().parallel.world_size > 1:
65
+ # 准备一个列表,rank 0 有数据,其他 rank 是占位符
66
+ object_list = [state_dict] if TrainerTools().parallel.is_main_process else [None]
67
+ # 执行广播,这个操作是阻塞的,会同步所有进程
68
+ dist.broadcast_object_list(object_list, src=0)
69
+ # 所有进程从列表中获取广播后的 state_dict 副本
70
+ state_dict = object_list[0]
71
+
72
+ return state_dict
@@ -4,7 +4,6 @@ import torch
4
4
  from torch import nn
5
5
  from torch.optim import Optimizer
6
6
  from torch.distributed.fsdp import FullyShardedDataParallel as FSDP
7
- import torch.distributed as dist
8
7
 
9
8
  from .tools import TrainerTools
10
9
 
@@ -51,37 +50,3 @@ def load_fsdp_checkpoint(
51
50
 
52
51
  if optimizer:
53
52
  optimizer.load_state_dict(state_dict['optim_state_dict'])
54
-
55
-
56
- def _get_fsdp_full_state_dict_on_rank0(model: nn.Module) -> Optional[dict]:
57
- """
58
- 可以在任意rank上调用,然后只有rank0有值
59
- """
60
-
61
- from torch.distributed.fsdp import FullyShardedDataParallel as FSDP
62
- with FSDP.summon_full_params(model, writeback=False, offload_to_cpu=True):
63
- if TrainerTools().parallel.is_main_process:
64
- return {k: v.clone() for k, v in model.state_dict().items()}
65
-
66
- return None
67
-
68
-
69
- def get_fsdp_model_params(model: nn.Module):
70
- """
71
- 从一个 FSDP 包装的模型中高效地提取完整的 FP32 state_dict。
72
- 这个函数会聚合所有分片的参数,并确保所有 rank 都收到一个完整的副本。
73
- """
74
-
75
- state_dict = _get_fsdp_full_state_dict_on_rank0(model)
76
-
77
- # 现在,只有 rank 0 上的 state_dict 是一个有效的字典,其他 rank 上是 None。
78
- # 我们需要将其广播给所有进程。
79
- if TrainerTools().parallel.world_size > 1:
80
- # 准备一个列表,rank 0 有数据,其他 rank 是占位符
81
- object_list = [state_dict] if TrainerTools().parallel.is_main_process else [None]
82
- # 执行广播,这个操作是阻塞的,会同步所有进程
83
- dist.broadcast_object_list(object_list, src=0)
84
- # 所有进程从列表中获取广播后的 state_dict 副本
85
- state_dict = object_list[0]
86
-
87
- return state_dict
@@ -0,0 +1,39 @@
1
+ from typing import Optional
2
+ from torch import nn
3
+ import torch.distributed as dist
4
+
5
+ from .tools import TrainerTools
6
+
7
+
8
+ def _get_fsdp_full_state_dict_on_rank0(model: nn.Module) -> Optional[dict]:
9
+ """
10
+ 可以在任意rank上调用,然后只有rank0有值
11
+ """
12
+
13
+ from torch.distributed.fsdp import FullyShardedDataParallel as FSDP
14
+ with FSDP.summon_full_params(model, writeback=False, offload_to_cpu=True):
15
+ if TrainerTools().parallel.is_main_process:
16
+ return {k: v.clone() for k, v in model.state_dict().items()}
17
+
18
+ return None
19
+
20
+
21
+ def get_fsdp_model_params(model: nn.Module, only_rank0=False):
22
+ """
23
+ 从一个 FSDP 包装的模型中高效地提取完整的 FP32 state_dict。
24
+ 这个函数会聚合所有分片的参数,并确保所有 rank 都收到一个完整的副本。
25
+ """
26
+
27
+ state_dict = _get_fsdp_full_state_dict_on_rank0(model)
28
+
29
+ # 现在,只有 rank 0 上的 state_dict 是一个有效的字典,其他 rank 上是 None。
30
+ # 我们需要将其广播给所有进程。
31
+ if not only_rank0 and TrainerTools().parallel.world_size > 1:
32
+ # 准备一个列表,rank 0 有数据,其他 rank 是占位符
33
+ object_list = [state_dict] if TrainerTools().parallel.is_main_process else [None]
34
+ # 执行广播,这个操作是阻塞的,会同步所有进程
35
+ dist.broadcast_object_list(object_list, src=0)
36
+ # 所有进程从列表中获取广播后的 state_dict 副本
37
+ state_dict = object_list[0]
38
+
39
+ return state_dict
@@ -15,10 +15,10 @@ from .loss import GRPOLoss
15
15
  from .tools import TrainerTools
16
16
  from .generate_utils import batch_generate
17
17
  from .log import log
18
+ from .model_params import copy_model_params
18
19
 
19
20
  from .checkpoint import (
20
21
  save_checkpoint,
21
- copy_model_params,
22
22
  save_steps,
23
23
  )
24
24
 
@@ -0,0 +1,28 @@
1
+ from typing import Optional
2
+ from torch import nn
3
+ from torch.nn.parallel import DistributedDataParallel as DDP
4
+
5
+ from .tools import TrainerTools
6
+ from .parallel_ds import DsParallel
7
+ from .parallel_fsdp import FsdpParallel
8
+
9
+ def copy_model_params(
10
+ _from: nn.Module,
11
+ _to: Optional[nn.Module]
12
+ ):
13
+ """
14
+ 必须在所有rank上调用,非rank0, _to可以设置为None
15
+ """
16
+ if isinstance(TrainerTools().parallel, DsParallel):
17
+ from .ds_model_params import get_ds_model_params
18
+ state_dict = get_ds_model_params(_from, only_rank0=_to is None)
19
+ elif isinstance(TrainerTools().parallel, FsdpParallel):
20
+ from .fsdp_model_params import get_fsdp_model_params
21
+ state_dict = get_fsdp_model_params(_from, only_rank0=_to is None)
22
+ elif isinstance(_from, DDP):
23
+ state_dict = _from.module.state_dict()
24
+ else:
25
+ state_dict = _from.state_dict()
26
+
27
+ if _to and state_dict:
28
+ _to.load_state_dict(state_dict)
llm_trainer/trainer.py CHANGED
@@ -14,6 +14,8 @@ from .parallel_fsdp import FsdpParallel
14
14
  from .tools import TrainerTools
15
15
  from .loss import LMLoss, KDLoss
16
16
  from .dataset import TextDataset
17
+ from .model_params import copy_model_params
18
+ from .eval import submit_gen_task
17
19
 
18
20
  from .train_configs import (
19
21
  TrainConfig,
@@ -31,10 +33,10 @@ from .scheduler import (
31
33
  from .checkpoint import (
32
34
  load_checkpoint,
33
35
  save_checkpoint,
34
- copy_model_params,
35
36
  load_steps,
36
37
  save_steps,
37
38
  )
39
+
38
40
  from .utils import (
39
41
  set_seed,
40
42
  pretrain_collate_fn,
@@ -45,8 +47,6 @@ from .log import(
45
47
  get_log_dir
46
48
  )
47
49
 
48
- from .eval import submit_gen_task
49
-
50
50
  class Trainer:
51
51
  def __init__(
52
52
  self,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: project_llm_trainer
3
- Version: 0.4.13
3
+ Version: 0.4.15
4
4
  Summary: LLM and VLM trainer
5
5
  Author: qibin
6
6
  Author-email: qibin0506@gmail.com
@@ -1,15 +1,18 @@
1
1
  llm_trainer/__init__.py,sha256=HWgtTEVeQSnZmEyYQm2K6eFEG4X2QAoigMlB5Z2tcXE,260
2
- llm_trainer/checkpoint.py,sha256=yZcExxneN2yzvWxRiK-pstMWs35LV7GiOfqcLq-S6vc,5745
2
+ llm_trainer/checkpoint.py,sha256=AvUC1JLxuahKtg3VNW20VHIE3iIjpaMHIi_pyyDYVJ0,5043
3
3
  llm_trainer/dataset.py,sha256=4QlOo0SFB5816BUYegQjgobUqTUMQvdmZMM_OEAMSjE,4347
4
4
  llm_trainer/dcp.py,sha256=PkD97DyrOtoTKn4FJsfL3VqAy4dxufgjdzJEz8-Cnoc,3635
5
- llm_trainer/dpo_trainer.py,sha256=djBhvI_ixTV1nLNg84tgCpfV--pu6IRiOhO28V-aANQ,11425
6
- llm_trainer/ds_checkpoint.py,sha256=x_tjgJR47P8gVwV4qAnTUCGwx7eVq2Epw0vOVV7fkYo,4925
5
+ llm_trainer/dpo_trainer.py,sha256=o5lYxt6yVMCvoBqW_yTu9l6Ff-xjEu-CwdPVttu3H8E,11447
6
+ llm_trainer/ds_checkpoint.py,sha256=wz48HoLBBt8QGO1tXfvJwrXoiGtPG_gjwHfEqARllso,2175
7
+ llm_trainer/ds_model_params.py,sha256=Nwmv0YcBtO6ynC0dXallAD1rWkN22-elGfVjLaWp2Yg,2988
7
8
  llm_trainer/eval.py,sha256=NDm8PbXLch7xT81xPYPRCNrcrB_Xj5GDJSCxyVwUOp4,1524
8
- llm_trainer/fsdp_checkpoint.py,sha256=lqZFzHyWyfzuCq_81kQNtJd2qaiMeY1N5BCEMnrJTBw,3192
9
+ llm_trainer/fsdp_checkpoint.py,sha256=xsm71s9WeTaBvBvv6CbuGpwkmX3V6i3xmBcMTDfGxKc,1770
10
+ llm_trainer/fsdp_model_params.py,sha256=MRjrs9zmMl-61a1l6188Ij5PSalzztOSp8E4evDvJXo,1541
9
11
  llm_trainer/generate_utils.py,sha256=tSbA_tLqSq5qJGHSOlPv5T3iRDZkbFg5ZvDAgJ_i_SE,17946
10
- llm_trainer/grpo_trainer.py,sha256=bZPrxhyPQLAnFzWhI7hhA6fpuKVNwj7nOm9k0ku9aK4,15977
12
+ llm_trainer/grpo_trainer.py,sha256=1gZXiL1pogLFecFQUGj9zCU_k66ryVjZciYyd8J5ph4,15998
11
13
  llm_trainer/log.py,sha256=LxqTGRNZUGMTSQCePRpk-rYyxSnSIbT4kOdP8Fbzr0M,462
12
14
  llm_trainer/loss.py,sha256=Yv3fsaVuZ5AhnGPJOr5vEMb_tM2urR6mCb4DBbrHHI8,6030
15
+ llm_trainer/model_params.py,sha256=2f2W9KRCjyqSfEwxI3w5f6TPZaqq25WzY-nEc7aJxcs,970
13
16
  llm_trainer/parallel.py,sha256=DQu8GqEFxD99HQ6hKuIxxyKi-05dMO33eMhImYlPuOI,4468
14
17
  llm_trainer/parallel_ddp.py,sha256=Pob9vUlBZnkL4oP1Re11kFob7nufMSE96pn7m7fuOEM,1345
15
18
  llm_trainer/parallel_ds.py,sha256=oy8RRxHud3rACWubFlJqqd0pjPEQhKeAPGPQUSdJX2c,1145
@@ -20,16 +23,16 @@ llm_trainer/sft_trainer.py,sha256=gxQA7T1o1QGUsHp2CX1Qb_fO5LppBJuNbc0H4ixCYUA,17
20
23
  llm_trainer/tokenizer.py,sha256=A7TYYUbtPf75kjCvWP7yBui4xZBObMk2aPem62YpwpY,6776
21
24
  llm_trainer/tools.py,sha256=O45-20wRmh-nyTfU-U-XtjbKAoe7boEIsUvWT_NaKx4,3041
22
25
  llm_trainer/train_configs.py,sha256=HKzH3nfMT1-SW4Htwa0KqYtMd6FAJcthR5IEo6di8us,8168
23
- llm_trainer/trainer.py,sha256=j5fDqMzvU6SYwxHsv9wX0UVX4JXS-8eP1AkHgVxKf9U,27119
26
+ llm_trainer/trainer.py,sha256=95ARdNDfalhZ7Ug-fDj3qIhWEiZQeX9n5WANhijIRLE,27140
24
27
  llm_trainer/utils.py,sha256=-ivhMF0d999va13S1wt2uBvtVw8Nvr3uBzhaUFKL04Q,6826
25
- project_llm_trainer-0.4.13.data/scripts/calc_intermediate_size,sha256=AggpgNHokJiJMbEtVdOnolqr_4bH3i1UYuZNEAzC2Gc,460
26
- project_llm_trainer-0.4.13.data/scripts/ddp_train,sha256=x81AasaN2-9TwARFFF1l7iV1LmfMQ0bLw0i_CGbOwSw,299
27
- project_llm_trainer-0.4.13.data/scripts/ds_train,sha256=qL3qc3TcedBCw98UZUjW07ONcErRawLE1HymW2AmscA,265
28
- project_llm_trainer-0.4.13.data/scripts/plot_loss,sha256=MzFcdJESlVr1srj4Td6-AxPGUKkfB_QEcJwm0Bd-5fU,910
29
- project_llm_trainer-0.4.13.data/scripts/plot_lr,sha256=w_7XR_x3KYYyboeOVAeu_I4fveLFI-C0wBmRrNlmWUI,894
30
- project_llm_trainer-0.4.13.data/scripts/py_train,sha256=tOp9TquORQeU8XN5H7OVIk5O0Ypwi34p_GENxTwgwdk,265
31
- project_llm_trainer-0.4.13.data/scripts/smart_train,sha256=Pmt4Q0to4Hoz82iB9uFPZuz7uahNUbfE7FR1940EBy8,716
32
- project_llm_trainer-0.4.13.dist-info/METADATA,sha256=hiW-7qgWuPizKVz4cU8mEHoqiuT6ZqNlCBb7nwVfFQ4,196
33
- project_llm_trainer-0.4.13.dist-info/WHEEL,sha256=Nw36Djuh_5VDukK0H78QzOX-_FQEo6V37m3nkm96gtU,91
34
- project_llm_trainer-0.4.13.dist-info/top_level.txt,sha256=LtRFg28i0QIG7iBCD2t095oSco99LCtkijibS9cMGik,12
35
- project_llm_trainer-0.4.13.dist-info/RECORD,,
28
+ project_llm_trainer-0.4.15.data/scripts/calc_intermediate_size,sha256=AggpgNHokJiJMbEtVdOnolqr_4bH3i1UYuZNEAzC2Gc,460
29
+ project_llm_trainer-0.4.15.data/scripts/ddp_train,sha256=x81AasaN2-9TwARFFF1l7iV1LmfMQ0bLw0i_CGbOwSw,299
30
+ project_llm_trainer-0.4.15.data/scripts/ds_train,sha256=qL3qc3TcedBCw98UZUjW07ONcErRawLE1HymW2AmscA,265
31
+ project_llm_trainer-0.4.15.data/scripts/plot_loss,sha256=MzFcdJESlVr1srj4Td6-AxPGUKkfB_QEcJwm0Bd-5fU,910
32
+ project_llm_trainer-0.4.15.data/scripts/plot_lr,sha256=w_7XR_x3KYYyboeOVAeu_I4fveLFI-C0wBmRrNlmWUI,894
33
+ project_llm_trainer-0.4.15.data/scripts/py_train,sha256=tOp9TquORQeU8XN5H7OVIk5O0Ypwi34p_GENxTwgwdk,265
34
+ project_llm_trainer-0.4.15.data/scripts/smart_train,sha256=Pmt4Q0to4Hoz82iB9uFPZuz7uahNUbfE7FR1940EBy8,716
35
+ project_llm_trainer-0.4.15.dist-info/METADATA,sha256=5sveZ3kkRMVCz9dI5_NI64o9tFBVsJhHhun9vwzzL9Q,196
36
+ project_llm_trainer-0.4.15.dist-info/WHEEL,sha256=Nw36Djuh_5VDukK0H78QzOX-_FQEo6V37m3nkm96gtU,91
37
+ project_llm_trainer-0.4.15.dist-info/top_level.txt,sha256=LtRFg28i0QIG7iBCD2t095oSco99LCtkijibS9cMGik,12
38
+ project_llm_trainer-0.4.15.dist-info/RECORD,,