szxllm commited on
Commit
9158a7e
·
verified ·
1 Parent(s): eda6efe

Update posttrain.py

Browse files
Files changed (1) hide show
  1. posttrain.py +607 -339
posttrain.py CHANGED
@@ -1,31 +1,69 @@
1
  import os
2
  import torch
3
  import torch.nn.functional as F
4
- from transformers import AutoTokenizer
 
 
 
5
  from pathlib import Path
6
  import logging
7
  from tqdm import tqdm
8
  import json
9
  from datetime import datetime
10
- import copy
11
  from model import MultiModalDenseTransformer
12
-
13
  from data_loader import (
14
  create_posttrain_dataloader,
15
  create_preference_dataloader
16
  )
17
- from data_config import POSTTRAIN_MIX
18
- from reward_model import RewardModel, RewardModelTrainer
19
- from grpo import GRPOTrainer
20
  from typing import Optional
 
 
 
 
 
 
 
 
 
 
 
21
 
 
 
22
  logging.basicConfig(
23
- level=logging.INFO,
24
- format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
25
  )
26
  logger = logging.getLogger(__name__)
27
  os.environ["HF_ENDPOINT"] = "https://hf-mirror.com"
28
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
29
  class PostTrainer:
30
  def __init__(
31
  self,
@@ -38,79 +76,125 @@ class PostTrainer:
38
  max_grad_norm: float = 1.0,
39
  log_interval: int = 10,
40
  eval_interval: int = 500,
41
- save_interval: int = 1000,
42
- checkpoint_dir: str = "checkpoints/posttrain"
 
 
 
 
43
  ):
44
- self.model = model
 
 
 
 
 
45
  self.tokenizer = tokenizer
46
- self.device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
47
-
48
- self.model.to(self.device)
49
-
50
- # 优化器
51
  self.optimizer = torch.optim.AdamW(
52
- model.parameters(),
53
  lr=learning_rate,
54
  weight_decay=weight_decay,
55
  betas=(0.9, 0.95),
56
  eps=1e-8
57
  )
58
-
59
- # 混合精度
60
- self.use_amp = torch.cuda.is_available()
61
  self.scaler = torch.amp.GradScaler('cuda', enabled=self.use_amp)
62
-
63
- # 训练参数
64
  self.num_epochs = num_epochs
65
  self.gradient_accumulation_steps = gradient_accumulation_steps
66
  self.max_grad_norm = max_grad_norm
67
  self.log_interval = log_interval
68
  self.eval_interval = eval_interval
69
  self.save_interval = save_interval
70
-
71
- # Checkpoint管理
72
  self.checkpoint_dir = Path(checkpoint_dir)
73
- self.checkpoint_dir.mkdir(parents=True, exist_ok=True)
74
-
75
- # 训练状态
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
76
  self.global_step = 0
77
  self.best_eval_loss = float('inf')
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
78
 
79
- logger.info(f"PostTrainer initialized:")
80
- logger.info(f" Device: {self.device}")
81
- logger.info(f" Learning Rate: {learning_rate}")
82
- logger.info(f" Num Epochs: {num_epochs}")
83
- logger.info(f" Gradient Accumulation: {gradient_accumulation_steps}")
84
 
85
  def train_step(self, batch: dict) -> dict:
86
- """单步训练"""
87
  instruction_ids = batch['instruction'].to(self.device)
88
  response_ids = batch['response'].to(self.device)
89
-
90
  instruction_mask = batch['instruction_mask'].to(self.device)
91
  response_mask = batch['response_mask'].to(self.device)
92
-
93
  input_ids = torch.cat([instruction_ids, response_ids], dim=1)
94
  attention_mask = torch.cat([instruction_mask, response_mask], dim=1)
95
-
96
- batch_size , seq_len = input_ids.shape
97
- position_ids=torch.zeros_like(input_ids)
98
-
99
  for i in range(batch_size):
100
  non_pad_mask = attention_mask[i].bool()
101
  if non_pad_mask.any():
102
- positions=torch.cumsum(non_pad_mask.long(), dim=0) -1
103
  position_ids[i] = positions * non_pad_mask.long()
104
- labels = input_ids.clone()
105
 
106
- # 屏蔽 Instruction 部分
107
  instr_len = instruction_ids.shape[1]
108
  labels[:, :instr_len] = -100
109
-
110
  labels[attention_mask == 0] = -100
111
-
112
 
113
- # 准备输入数据
114
  input_data = {
115
  'segments': [{
116
  'type': 'text',
@@ -118,18 +202,14 @@ class PostTrainer:
118
  'modality_id': 0
119
  }]
120
  }
121
-
122
- # 前向传播
123
  with torch.amp.autocast('cuda', enabled=self.use_amp):
124
- outputs = self.model(input_data, attention_mask=attention_mask,
125
- position_ids = position_ids)
126
-
127
  logits = outputs['logits']
128
-
129
- # 计算损失
130
  shift_logits = logits[:, :-1, :].contiguous()
131
  shift_labels = labels[:, 1:].contiguous()
132
-
133
  loss = F.cross_entropy(
134
  shift_logits.view(-1, shift_logits.size(-1)),
135
  shift_labels.view(-1),
@@ -137,399 +217,587 @@ class PostTrainer:
137
  )
138
  raw_loss = loss.item()
139
  loss = loss / self.gradient_accumulation_steps
140
-
141
- # 反向传播
142
  self.scaler.scale(loss).backward()
143
-
144
- return {
145
- 'loss': raw_loss
146
- }
147
 
148
  def optimizer_step(self):
149
- """优化器步骤"""
150
  self.scaler.unscale_(self.optimizer)
151
  grad_norm = torch.nn.utils.clip_grad_norm_(
152
  self.model.parameters(),
153
  self.max_grad_norm
154
  )
155
-
156
  self.scaler.step(self.optimizer)
157
  self.scaler.update()
 
 
 
158
  self.optimizer.zero_grad(set_to_none=True)
159
  self.global_step += 1
160
  return grad_norm.item()
161
 
162
  @torch.no_grad()
163
  def evaluate(self, dataloader, max_batches: int = 50) -> float:
164
- """评估"""
165
  self.model.eval()
166
  total_loss = 0.0
167
  num_batches = 0
168
-
169
  for i, batch in enumerate(dataloader):
170
- if i >= max_batches:
171
- break
172
-
173
- if batch is None:
174
- continue
175
-
176
  instruction_ids = batch['instruction'].to(self.device)
177
  response_ids = batch['response'].to(self.device)
178
  input_ids = torch.cat([instruction_ids, response_ids], dim=1)
 
 
 
 
 
 
 
 
 
 
179
 
180
  labels = input_ids.clone()
181
  labels[:, :instruction_ids.shape[1]] = -100
182
- labels[input_ids == self.tokenizer.pad_token_id] = -100
183
-
184
- input_data = {
185
- 'segments': [{
186
- 'type': 'text',
187
- 'data': input_ids,
188
- 'modality_id': 0
189
- }]
190
- }
191
-
192
  with torch.amp.autocast('cuda', enabled=self.use_amp):
193
- outputs = self.model(input_data)
194
  logits = outputs['logits']
195
-
196
  shift_logits = logits[:, :-1, :].contiguous()
197
  shift_labels = labels[:, 1:].contiguous()
198
-
199
- loss = F.cross_entropy(
200
- shift_logits.view(-1, shift_logits.size(-1)),
201
- shift_labels.view(-1),
202
- ignore_index=-100
203
- )
204
-
205
  total_loss += loss.item()
206
  num_batches += 1
207
-
208
  self.model.train()
209
- return total_loss / max(num_batches, 1)
 
 
 
 
 
 
 
210
 
211
- def train(
212
- self,
213
- train_dataloader,
214
- eval_dataloader=None,
215
- resume_from: Optional[str] = None
216
- ):
217
- """训练循环"""
218
- logger.info("\n" + "="*80)
219
- logger.info("Starting Post-Training (SFT)")
220
- logger.info("="*80 + "\n")
221
-
 
 
222
  if resume_from:
223
  self.load_checkpoint(resume_from)
 
 
 
 
224
 
225
  self.model.train()
226
-
227
- for epoch in range(self.num_epochs):
228
- logger.info(f"\nEpoch {epoch+1}/{self.num_epochs}")
229
-
230
- progress_bar = tqdm(train_dataloader, desc=f"Epoch {epoch+1}")
 
 
 
 
 
231
  running_loss = 0.0
232
  step_in_accumulation = 0
233
-
234
- for batch_idx, batch in enumerate(progress_bar):
235
- if batch is None:
 
 
 
 
236
  continue
237
 
238
- # 训练步骤
239
  stats = self.train_step(batch)
240
  running_loss += stats['loss']
241
  step_in_accumulation += 1
242
-
243
- # 优化器更新
244
  if step_in_accumulation == self.gradient_accumulation_steps:
245
  grad_norm = self.optimizer_step()
246
  step_in_accumulation = 0
 
 
247
 
248
- # 更新进度条
249
- progress_bar.set_postfix({'loss': f"{stats['loss']:.4f}"})
250
-
251
- # 日志
 
 
252
  if self.global_step % self.log_interval == 0:
253
- avg_loss = running_loss / self.log_interval
254
- logger.info(
255
- f"Step {self.global_step} | "
256
- f"Epoch {epoch+1} | "
257
- f"Loss: {avg_loss:.4f}"
258
- )
 
 
 
 
 
 
 
259
  running_loss = 0.0
260
-
261
- # 评估
262
  if eval_dataloader and self.global_step % self.eval_interval == 0:
263
  eval_loss = self.evaluate(eval_dataloader)
264
- logger.info(f"Eval Loss: {eval_loss:.4f}")
265
-
266
- if eval_loss < self.best_eval_loss:
267
- self.best_eval_loss = eval_loss
268
- self.save_checkpoint(
269
- self.checkpoint_dir / "best_model.pt",
270
- is_best=True
271
- )
272
-
273
- # 保存
274
- if self.global_step % self.save_interval == 0:
275
- self.save_checkpoint(
276
- self.checkpoint_dir / f"step_{self.global_step}.pt"
277
- )
278
-
279
- # Epoch结束评估
280
  if eval_dataloader:
281
  eval_loss = self.evaluate(eval_dataloader)
282
- logger.info(f"\nEpoch {epoch+1} Eval Loss: {eval_loss:.4f}")
283
-
284
- logger.info("\n" + "="*80)
285
- logger.info("Post-Training Complete!")
286
- logger.info(f" Best Eval Loss: {self.best_eval_loss:.4f}")
287
- logger.info("="*80 + "\n")
288
-
289
- self.save_checkpoint(self.checkpoint_dir / "final_model.pt")
290
 
291
  def save_checkpoint(self, path: Path, is_best: bool = False):
292
- """保存checkpoint"""
 
293
  checkpoint = {
294
- 'model_state_dict': self.model.state_dict(),
295
  'optimizer_state_dict': self.optimizer.state_dict(),
 
296
  'scaler_state_dict': self.scaler.state_dict() if self.use_amp else None,
297
  'global_step': self.global_step,
298
  'best_eval_loss': self.best_eval_loss,
299
  'timestamp': datetime.now().isoformat()
300
  }
301
-
302
  torch.save(checkpoint, path)
303
  logger.info(f"Checkpoint saved to {path}" + (" (BEST)" if is_best else ""))
304
 
305
  def load_checkpoint(self, path: str):
306
- """加载checkpoint"""
307
  checkpoint = torch.load(path, map_location=self.device)
308
-
309
- self.model.load_state_dict(checkpoint['model_state_dict'])
 
310
  self.optimizer.load_state_dict(checkpoint['optimizer_state_dict'])
 
 
 
311
 
312
  if self.use_amp and checkpoint.get('scaler_state_dict'):
313
  self.scaler.load_state_dict(checkpoint['scaler_state_dict'])
314
-
315
  self.global_step = checkpoint['global_step']
316
- self.best_eval_loss = checkpoint['best_eval_loss']
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
317
 
318
- logger.info(f"Checkpoint loaded from {path}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
319
 
320
  def main():
321
- """主函数"""
322
- # 配置
323
  config = {
324
- # 模型配置
325
  'model_dim': 1536,
326
  'vocab_size': 151665,
327
  'n_layers': 12,
328
  'n_heads': 12,
329
  'n_kv_heads': 4,
330
- 'max_seq_len': 512,
331
  'dropout': 0.0,
332
  'use_moe': False,
333
- # 训练配置
334
- 'batch_size': 2,
335
- 'gradient_accumulation_steps': 8,
336
  'learning_rate': 1e-5,
337
  'weight_decay': 0.01,
338
- 'num_epochs': 3,
339
  'max_grad_norm': 1.0,
340
 
341
- # 数据配置
342
- 'data_mix': 'simple_instruct',
343
- 'max_samples_train': 20000,
 
 
 
344
  'max_samples_eval': 1000,
345
- 'max_length': 512,
346
- 'num_workers': 4,
347
-
348
- # RLHF配置
349
  'do_rlhf': False,
350
- 'preference_dataset': 'hh_rlhf',
351
- 'grpo_iterations': 3,
 
352
  'grpo_kl_coef': 0.04,
353
  'grpo_group_size': 4,
354
-
355
- # 路径
356
- 'pretrain_checkpoint': '/root/multimodal/checkpoints/pretrain_fixed/step_10000.pt',
357
- 'checkpoint_dir': 'checkpoints/posttrain',
 
 
 
 
 
358
  'log_interval': 50,
359
- 'eval_interval': 500,
360
- 'save_interval': 1000,
361
  }
362
-
363
- logger.info("Configuration:")
364
- logger.info(json.dumps(config, indent=2))
365
-
366
- # 初始化tokenizer
 
367
  logger.info("\nInitializing tokenizer...")
368
  tokenizer = AutoTokenizer.from_pretrained(
369
  "Qwen/Qwen2.5-7B-Instruct",
370
  use_fast=True,
371
  trust_remote_code=True
372
  )
373
-
374
  if tokenizer.pad_token is None:
375
  tokenizer.pad_token = tokenizer.eos_token
376
  tokenizer.pad_token_id = tokenizer.eos_token_id
377
-
378
  config['vocab_size'] = len(tokenizer)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
379
 
380
- # 初始化或加载模型
381
- logger.info("\nInitializing model...")
382
- model = MultiModalDenseTransformer(
383
- model_dim=config['model_dim'],
384
- vocab_size=config['vocab_size'],
385
- n_layers=config['n_layers'],
386
- n_heads=config['n_heads'],
387
- n_kv_heads=config['n_kv_heads'],
388
- max_seq_len=config['max_seq_len'],
389
- dropout=config['dropout'],
390
- use_moe=config['use_moe'],
391
- use_gradient_checkpointing=False,
392
- rope_scaling_type="yarn",
393
- use_multimodal_fusion=False,
394
- use_contrastive=False
395
- )
396
-
397
- if config['pretrain_checkpoint']:
398
- logger.info(f"Loading pretrain checkpoint: {config['pretrain_checkpoint']}")
399
- checkpoint = torch.load(config['pretrain_checkpoint'])
400
- model.load_state_dict(checkpoint['model_state_dict'])
401
-
402
- logger.info("\n" + "="*80)
403
- logger.info("PHASE 1: Supervised Fine-Tuning")
404
- logger.info("="*80)
405
-
406
- # 创建数据加载器
407
- train_dataloader = create_posttrain_dataloader(
408
- mix_name=config['data_mix'],
409
- tokenizer=tokenizer,
410
- batch_size=config['batch_size'],
411
- num_workers=config['num_workers'],
412
- max_length=config['max_length'],
413
- max_samples=config['max_samples_train'],
414
- split='train',
415
- shuffle=True
416
- )
417
-
418
- eval_dataloader = create_posttrain_dataloader(
419
- mix_name=config['data_mix'],
420
- tokenizer=tokenizer,
421
- batch_size=config['batch_size'] * 2,
422
- num_workers=config['num_workers'],
423
- max_length=config['max_length'],
424
- max_samples=config['max_samples_eval'],
425
- split='train', # 使用train的后部分作为验证
426
- shuffle=False
427
- )
428
-
429
- # 创建训练器
430
- trainer = PostTrainer(
431
- model=model,
432
- tokenizer=tokenizer,
433
- learning_rate=config['learning_rate'],
434
- weight_decay=config['weight_decay'],
435
- num_epochs=config['num_epochs'],
436
- gradient_accumulation_steps=config['gradient_accumulation_steps'],
437
- max_grad_norm=config['max_grad_norm'],
438
- log_interval=config['log_interval'],
439
- eval_interval=config['eval_interval'],
440
- save_interval=config['save_interval'],
441
- checkpoint_dir=config['checkpoint_dir']
442
- )
443
-
444
- trainer.train(train_dataloader, eval_dataloader)
445
-
446
- if config['do_rlhf']:
447
- logger.info("\n" + "="*80)
448
- logger.info("PHASE 2: RLHF with GRPO")
449
- logger.info("="*80)
450
 
451
- try:
452
- # 训练奖励模型
453
- logger.info("\nTraining Reward Model...")
454
-
455
- reward_base_model = copy.deepcopy(model)
456
- reward_model = RewardModel(reward_base_model, use_value_head=True)
457
-
458
- preference_dataloader = create_preference_dataloader(
459
- dataset_name=config['preference_dataset'],
460
- tokenizer=tokenizer,
461
- batch_size=config['batch_size'],
462
- num_workers=config['num_workers'],
463
- max_samples=5000,
464
- split='train'
465
- )
466
-
467
- reward_trainer = RewardModelTrainer(
468
- reward_model=reward_model,
469
- learning_rate=1e-5
470
- )
471
-
472
- reward_trainer.train(preference_dataloader, num_epochs=1)
473
-
474
- # GRPO训练
475
- logger.info("\nStarting GRPO Training...")
476
-
477
- ref_model = copy.deepcopy(model)
478
- ref_model.eval()
479
-
480
- grpo_trainer = GRPOTrainer(
481
- actor_model=model,
482
- reward_model=reward_model,
483
- ref_model=ref_model,
484
- tokenizer=tokenizer,
485
- learning_rate=1e-6,
486
- kl_coef=config['grpo_kl_coef'],
487
- group_size=config['grpo_group_size'],
488
- update_batch_size=2,
489
- use_amp=True
490
- )
491
-
492
- # 准备prompts
493
- prompt_dataloader = create_posttrain_dataloader(
494
- mix_name=config['data_mix'],
495
- tokenizer=tokenizer,
496
- batch_size=4,
497
- num_workers=2,
498
- max_samples=1000,
499
- split='train'
500
- )
501
-
502
- # 提取prompts
503
- prompts = []
504
- for batch in prompt_dataloader:
505
- if batch and batch.get('instruction') is not None:
506
- prompts.append(batch['instruction'])
507
- if len(prompts) >= 200:
508
- break
509
-
510
- if prompts:
511
- prompt_tensor = torch.cat(prompts[:200], dim=0)
512
- from torch.utils.data import TensorDataset, DataLoader
513
- prompt_loader = DataLoader(
514
- TensorDataset(prompt_tensor),
515
- batch_size=4
516
- )
517
-
518
- grpo_trainer.train(
519
- prompt_loader,
520
- num_iterations=config['grpo_iterations'],
521
- max_gen_len=50,
522
- save_path=config['checkpoint_dir'] + "/grpo"
523
- )
524
-
525
- except Exception as e:
526
- logger.error(f"Error in RLHF: {e}")
527
- import traceback
528
- traceback.print_exc()
529
-
530
- logger.info("\n" + "="*80)
531
- logger.info("All Training Complete!")
532
- logger.info("="*80)
533
 
534
  if __name__ == "__main__":
535
  main()
 
1
  import os
2
  import torch
3
  import torch.nn.functional as F
4
+ import torch.distributed as dist
5
+ from torch.nn.parallel import DistributedDataParallel as DDP
6
+ from torch.utils.data import DataLoader, DistributedSampler
7
+ from transformers import AutoTokenizer, get_cosine_schedule_with_warmup # 添加 scheduler
8
  from pathlib import Path
9
  import logging
10
  from tqdm import tqdm
11
  import json
12
  from datetime import datetime
13
+ import gc
14
  from model import MultiModalDenseTransformer
15
+ from grpo_dataloader import create_grpo_prompt_dataloader
16
  from data_loader import (
17
  create_posttrain_dataloader,
18
  create_preference_dataloader
19
  )
20
+ from reward_model import RewardModel
21
+ from grpo import GRPOZeroTrainer
 
22
  from typing import Optional
23
+ def setup_distributed():
24
+ if "RANK" in os.environ and "WORLD_SIZE" in os.environ:
25
+ dist.init_process_group(backend="nccl")
26
+ rank = int(os.environ["RANK"])
27
+ local_rank = int(os.environ["LOCAL_RANK"])
28
+ world_size = int(os.environ["WORLD_SIZE"])
29
+ torch.cuda.set_device(local_rank)
30
+ return rank, local_rank, world_size
31
+ else:
32
+ print("Not running in distributed mode. Fallback to single GPU.")
33
+ return 0, 0, 1
34
 
35
+ RANK, LOCAL_RANK, WORLD_SIZE = setup_distributed()
36
+ IS_MAIN_PROCESS = RANK == 0
37
  logging.basicConfig(
38
+ level=logging.INFO if IS_MAIN_PROCESS else logging.WARNING,
39
+ format=f'%(asctime)s - [Rank {RANK}] - %(name)s - %(levelname)s - %(message)s'
40
  )
41
  logger = logging.getLogger(__name__)
42
  os.environ["HF_ENDPOINT"] = "https://hf-mirror.com"
43
 
44
+ def force_cleanup():
45
+ gc.collect()
46
+ if torch.cuda.is_available():
47
+ torch.cuda.empty_cache()
48
+ if WORLD_SIZE > 1:
49
+ dist.barrier()
50
+
51
+ def get_distributed_dataloader(original_loader, batch_size, num_workers):
52
+ dataset = original_loader.dataset
53
+ collate_fn = original_loader.collate_fn
54
+
55
+ sampler = DistributedSampler(dataset, shuffle=True) if WORLD_SIZE > 1 else None
56
+
57
+ return DataLoader(
58
+ dataset,
59
+ batch_size=batch_size,
60
+ num_workers=num_workers,
61
+ pin_memory=True,
62
+ sampler=sampler,
63
+ shuffle=(sampler is None),
64
+ collate_fn=collate_fn
65
+ )
66
+
67
  class PostTrainer:
68
  def __init__(
69
  self,
 
76
  max_grad_norm: float = 1.0,
77
  log_interval: int = 10,
78
  eval_interval: int = 500,
79
+ save_interval: int = 1300,
80
+ checkpoint_dir: str = "checkpoints/posttrain",
81
+ warmup_steps: int = 100,
82
+ scheduler_type: str = "cosine",
83
+ min_lr_ratio: float = 0.1,
84
+ total_steps: Optional[int] = None
85
  ):
86
+ self.device = torch.device(f'cuda:{LOCAL_RANK}')
87
+ self.model = model.to(self.device)
88
+
89
+ if WORLD_SIZE > 1:
90
+ self.model = DDP(self.model, device_ids=[LOCAL_RANK], output_device=LOCAL_RANK)
91
+
92
  self.tokenizer = tokenizer
93
+
 
 
 
 
94
  self.optimizer = torch.optim.AdamW(
95
+ self.model.parameters(),
96
  lr=learning_rate,
97
  weight_decay=weight_decay,
98
  betas=(0.9, 0.95),
99
  eps=1e-8
100
  )
101
+
102
+ self.use_amp = True
 
103
  self.scaler = torch.amp.GradScaler('cuda', enabled=self.use_amp)
104
+
 
105
  self.num_epochs = num_epochs
106
  self.gradient_accumulation_steps = gradient_accumulation_steps
107
  self.max_grad_norm = max_grad_norm
108
  self.log_interval = log_interval
109
  self.eval_interval = eval_interval
110
  self.save_interval = save_interval
 
 
111
  self.checkpoint_dir = Path(checkpoint_dir)
112
+
113
+ self.warmup_steps = warmup_steps
114
+ self.scheduler_type = scheduler_type
115
+ self.min_lr_ratio = min_lr_ratio
116
+ self.learning_rate = learning_rate
117
+ self.total_steps = total_steps
118
+ self.scheduler = None
119
+
120
+ if IS_MAIN_PROCESS:
121
+ self.checkpoint_dir.mkdir(parents=True, exist_ok=True)
122
+ log_file_name = f"train_{datetime.now().strftime('%Y%m%d_%H%M%S')}.log"
123
+ log_path = self.checkpoint_dir / log_file_name
124
+ file_handler = logging.FileHandler(log_path, encoding='utf-8')
125
+ file_handler.setLevel(logging.INFO)
126
+ formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
127
+ file_handler.setFormatter(formatter)
128
+ logger.addHandler(file_handler)
129
+
130
  self.global_step = 0
131
  self.best_eval_loss = float('inf')
132
+
133
+ def _create_scheduler(self, total_steps: int):
134
+ if self.scheduler_type == "cosine":
135
+ from transformers import get_cosine_schedule_with_warmup
136
+ scheduler = get_cosine_schedule_with_warmup(
137
+ self.optimizer,
138
+ num_warmup_steps=self.warmup_steps,
139
+ num_training_steps=total_steps,
140
+ num_cycles=0.5
141
+ )
142
+ elif self.scheduler_type == "linear":
143
+ from transformers import get_linear_schedule_with_warmup
144
+ scheduler = get_linear_schedule_with_warmup(
145
+ self.optimizer,
146
+ num_warmup_steps=self.warmup_steps,
147
+ num_training_steps=total_steps
148
+ )
149
+ elif self.scheduler_type == "constant":
150
+ from transformers import get_constant_schedule_with_warmup
151
+ scheduler = get_constant_schedule_with_warmup(
152
+ self.optimizer,
153
+ num_warmup_steps=self.warmup_steps
154
+ )
155
+ elif self.scheduler_type == "cosine_with_min_lr":
156
+ from transformers import get_cosine_schedule_with_warmup
157
+ scheduler = get_cosine_schedule_with_warmup(
158
+ self.optimizer,
159
+ num_warmup_steps=self.warmup_steps,
160
+ num_training_steps=total_steps,
161
+ num_cycles=0.5
162
+ )
163
+ scheduler = MinLRSchedulerWrapper(
164
+ scheduler,
165
+ self.optimizer,
166
+ min_lr=self.learning_rate * self.min_lr_ratio
167
+ )
168
+ else:
169
+ raise ValueError(f"Unknown scheduler type: {self.scheduler_type}")
170
+
171
+ if IS_MAIN_PROCESS:
172
+ logger.info(f"Created {self.scheduler_type} scheduler with {self.warmup_steps} warmup steps and {total_steps} total steps")
173
 
174
+ return scheduler
 
 
 
 
175
 
176
  def train_step(self, batch: dict) -> dict:
 
177
  instruction_ids = batch['instruction'].to(self.device)
178
  response_ids = batch['response'].to(self.device)
 
179
  instruction_mask = batch['instruction_mask'].to(self.device)
180
  response_mask = batch['response_mask'].to(self.device)
181
+
182
  input_ids = torch.cat([instruction_ids, response_ids], dim=1)
183
  attention_mask = torch.cat([instruction_mask, response_mask], dim=1)
184
+
185
+ batch_size, _ = input_ids.shape
186
+ position_ids = torch.zeros_like(input_ids)
 
187
  for i in range(batch_size):
188
  non_pad_mask = attention_mask[i].bool()
189
  if non_pad_mask.any():
190
+ positions = torch.cumsum(non_pad_mask.long(), dim=0) - 1
191
  position_ids[i] = positions * non_pad_mask.long()
 
192
 
193
+ labels = input_ids.clone()
194
  instr_len = instruction_ids.shape[1]
195
  labels[:, :instr_len] = -100
 
196
  labels[attention_mask == 0] = -100
 
197
 
 
198
  input_data = {
199
  'segments': [{
200
  'type': 'text',
 
202
  'modality_id': 0
203
  }]
204
  }
205
+
 
206
  with torch.amp.autocast('cuda', enabled=self.use_amp):
207
+ outputs = self.model(input_data, attention_mask=attention_mask, position_ids=position_ids)
 
 
208
  logits = outputs['logits']
209
+
 
210
  shift_logits = logits[:, :-1, :].contiguous()
211
  shift_labels = labels[:, 1:].contiguous()
212
+
213
  loss = F.cross_entropy(
214
  shift_logits.view(-1, shift_logits.size(-1)),
215
  shift_labels.view(-1),
 
217
  )
218
  raw_loss = loss.item()
219
  loss = loss / self.gradient_accumulation_steps
220
+
 
221
  self.scaler.scale(loss).backward()
222
+ return {'loss': raw_loss}
 
 
 
223
 
224
  def optimizer_step(self):
 
225
  self.scaler.unscale_(self.optimizer)
226
  grad_norm = torch.nn.utils.clip_grad_norm_(
227
  self.model.parameters(),
228
  self.max_grad_norm
229
  )
 
230
  self.scaler.step(self.optimizer)
231
  self.scaler.update()
232
+ if self.scheduler is not None:
233
+ self.scheduler.step()
234
+
235
  self.optimizer.zero_grad(set_to_none=True)
236
  self.global_step += 1
237
  return grad_norm.item()
238
 
239
  @torch.no_grad()
240
  def evaluate(self, dataloader, max_batches: int = 50) -> float:
 
241
  self.model.eval()
242
  total_loss = 0.0
243
  num_batches = 0
244
+
245
  for i, batch in enumerate(dataloader):
246
+ if i >= max_batches: break
247
+ if batch is None: continue
248
+
 
 
 
249
  instruction_ids = batch['instruction'].to(self.device)
250
  response_ids = batch['response'].to(self.device)
251
  input_ids = torch.cat([instruction_ids, response_ids], dim=1)
252
+
253
+ instruction_mask = batch['instruction_mask'].to(self.device)
254
+ response_mask = batch['response_mask'].to(self.device)
255
+ attention_mask = torch.cat([instruction_mask, response_mask], dim=1)
256
+
257
+ position_ids = torch.zeros_like(input_ids)
258
+ for i in range(input_ids.shape[0]):
259
+ non_pad = attention_mask[i].bool()
260
+ if non_pad.any():
261
+ position_ids[i] = (torch.cumsum(non_pad.long(), dim=0) - 1) * non_pad.long()
262
 
263
  labels = input_ids.clone()
264
  labels[:, :instruction_ids.shape[1]] = -100
265
+ labels[attention_mask == 0] = -100
266
+ input_data = {'segments': [{'type': 'text', 'data': input_ids, 'modality_id': 0}]}
267
+
 
 
 
 
 
 
 
268
  with torch.amp.autocast('cuda', enabled=self.use_amp):
269
+ outputs = self.model(input_data, attention_mask=attention_mask, position_ids=position_ids)
270
  logits = outputs['logits']
 
271
  shift_logits = logits[:, :-1, :].contiguous()
272
  shift_labels = labels[:, 1:].contiguous()
273
+ loss = F.cross_entropy(shift_logits.view(-1, shift_logits.size(-1)), shift_labels.view(-1), ignore_index=-100)
 
 
 
 
 
 
274
  total_loss += loss.item()
275
  num_batches += 1
276
+
277
  self.model.train()
278
+ avg_loss = total_loss / max(num_batches, 1)
279
+
280
+ if WORLD_SIZE > 1:
281
+ loss_tensor = torch.tensor(avg_loss).to(self.device)
282
+ dist.all_reduce(loss_tensor, op=dist.ReduceOp.AVG)
283
+ avg_loss = loss_tensor.item()
284
+
285
+ return avg_loss
286
 
287
+ def train(self, train_dataloader, eval_dataloader=None, resume_from: Optional[str] = None):
288
+ if IS_MAIN_PROCESS:
289
+ logger.info("Starting Post-Training (SFT) with LR Scheduler - DDP Mode")
290
+
291
+ if self.total_steps is None:
292
+ steps_per_epoch = len(train_dataloader) // self.gradient_accumulation_steps
293
+ self.total_steps = steps_per_epoch * self.num_epochs
294
+ if IS_MAIN_PROCESS:
295
+ logger.info(f"Calculated total training steps: {self.total_steps}")
296
+
297
+ self.scheduler = self._create_scheduler(self.total_steps)
298
+
299
+ start_epoch = 0
300
  if resume_from:
301
  self.load_checkpoint(resume_from)
302
+ steps_per_epoch = len(train_dataloader) // self.gradient_accumulation_steps
303
+ start_epoch = self.global_step // steps_per_epoch
304
+ if IS_MAIN_PROCESS:
305
+ logger.info(f"Resuming training from epoch {start_epoch}, global step {self.global_step}")
306
 
307
  self.model.train()
308
+
309
+ for epoch in range(start_epoch, self.num_epochs):
310
+ if hasattr(train_dataloader.sampler, 'set_epoch'):
311
+ train_dataloader.sampler.set_epoch(epoch)
312
+
313
+ if IS_MAIN_PROCESS:
314
+ logger.info(f"\nEpoch {epoch+1}/{self.num_epochs}")
315
+
316
+ iterator = tqdm(train_dataloader, desc=f"Epoch {epoch+1}", disable=not IS_MAIN_PROCESS)
317
+
318
  running_loss = 0.0
319
  step_in_accumulation = 0
320
+
321
+ for batch_idx, batch in enumerate(iterator):
322
+ if batch is None: continue
323
+
324
+ if 'instruction' not in batch or 'response' not in batch:
325
+ if IS_MAIN_PROCESS:
326
+ logger.warning(f"Skipping invalid batch at index {batch_idx}")
327
  continue
328
 
 
329
  stats = self.train_step(batch)
330
  running_loss += stats['loss']
331
  step_in_accumulation += 1
332
+
 
333
  if step_in_accumulation == self.gradient_accumulation_steps:
334
  grad_norm = self.optimizer_step()
335
  step_in_accumulation = 0
336
+
337
+ current_lr = self.optimizer.param_groups[0]['lr']
338
 
339
+ if IS_MAIN_PROCESS:
340
+ iterator.set_postfix({
341
+ 'loss': f"{stats['loss']:.4f}",
342
+ 'lr': f"{current_lr:.2e}"
343
+ })
344
+
345
  if self.global_step % self.log_interval == 0:
346
+ current_loss_tensor = torch.tensor(running_loss).to(self.device)
347
+ if WORLD_SIZE > 1:
348
+ dist.all_reduce(current_loss_tensor, op=dist.ReduceOp.AVG)
349
+ avg_loss = current_loss_tensor.item() / (self.log_interval * self.gradient_accumulation_steps)
350
+
351
+ if IS_MAIN_PROCESS:
352
+ logger.info(
353
+ f"Step: {self.global_step} | "
354
+ f"Loss: {avg_loss:.6f} | "
355
+ f"GradNorm: {grad_norm:.4f} | "
356
+ f"LR: {current_lr:.2e} | "
357
+ f"Progress: {self.global_step}/{self.total_steps}"
358
+ )
359
  running_loss = 0.0
360
+
 
361
  if eval_dataloader and self.global_step % self.eval_interval == 0:
362
  eval_loss = self.evaluate(eval_dataloader)
363
+ if IS_MAIN_PROCESS:
364
+ logger.info(f"Eval Loss: {eval_loss:.4f}")
365
+ if eval_loss < self.best_eval_loss:
366
+ self.best_eval_loss = eval_loss
367
+ self.save_checkpoint(self.checkpoint_dir / "best_model.pt", is_best=True)
368
+
369
+ if self.global_step % self.save_interval == 0 and IS_MAIN_PROCESS:
370
+ self.save_checkpoint(self.checkpoint_dir / f"step_{self.global_step}.pt")
371
+
 
 
 
 
 
 
 
372
  if eval_dataloader:
373
  eval_loss = self.evaluate(eval_dataloader)
374
+ if IS_MAIN_PROCESS:
375
+ logger.info(f"\nEpoch {epoch+1} Eval Loss: {eval_loss:.4f}")
376
+
377
+ if IS_MAIN_PROCESS:
378
+ self.save_checkpoint(self.checkpoint_dir / "final_model.pt")
 
 
 
379
 
380
  def save_checkpoint(self, path: Path, is_best: bool = False):
381
+ if not IS_MAIN_PROCESS: return
382
+ model_to_save = self.model.module if hasattr(self.model, 'module') else self.model
383
  checkpoint = {
384
+ 'model_state_dict': model_to_save.state_dict(),
385
  'optimizer_state_dict': self.optimizer.state_dict(),
386
+ 'scheduler_state_dict': self.scheduler.state_dict() if self.scheduler else None, # 新增
387
  'scaler_state_dict': self.scaler.state_dict() if self.use_amp else None,
388
  'global_step': self.global_step,
389
  'best_eval_loss': self.best_eval_loss,
390
  'timestamp': datetime.now().isoformat()
391
  }
 
392
  torch.save(checkpoint, path)
393
  logger.info(f"Checkpoint saved to {path}" + (" (BEST)" if is_best else ""))
394
 
395
  def load_checkpoint(self, path: str):
 
396
  checkpoint = torch.load(path, map_location=self.device)
397
+
398
+ model_to_load = self.model.module if hasattr(self.model, 'module') else self.model
399
+ model_to_load.load_state_dict(checkpoint['model_state_dict'])
400
  self.optimizer.load_state_dict(checkpoint['optimizer_state_dict'])
401
+
402
+ if self.scheduler and checkpoint.get('scheduler_state_dict'):
403
+ self.scheduler.load_state_dict(checkpoint['scheduler_state_dict'])
404
 
405
  if self.use_amp and checkpoint.get('scaler_state_dict'):
406
  self.scaler.load_state_dict(checkpoint['scaler_state_dict'])
 
407
  self.global_step = checkpoint['global_step']
408
+ self.best_eval_loss = checkpoint.get('best_eval_loss', float('inf'))
409
+ if IS_MAIN_PROCESS:
410
+ logger.info(f"Checkpoint loaded from {path}")
411
+
412
+
413
+ class MinLRSchedulerWrapper:
414
+ def __init__(self, scheduler, optimizer, min_lr):
415
+ self.scheduler = scheduler
416
+ self.optimizer = optimizer
417
+ self.min_lr = min_lr
418
+
419
+ def step(self):
420
+ self.scheduler.step()
421
+ for param_group in self.optimizer.param_groups:
422
+ param_group['lr'] = max(param_group['lr'], self.min_lr)
423
+
424
+ def state_dict(self):
425
+ return {
426
+ 'scheduler': self.scheduler.state_dict(),
427
+ 'min_lr': self.min_lr
428
+ }
429
+
430
+ def load_state_dict(self, state_dict):
431
+ self.scheduler.load_state_dict(state_dict['scheduler'])
432
+ self.min_lr = state_dict['min_lr']
433
+
434
+ class RewardTrainer:
435
+ def __init__(
436
+ self,
437
+ tokenizer,
438
+ reward_model: RewardModel,
439
+ learning_rate: float = 1e-5,
440
+ weight_decay: float = 0.01,
441
+ num_epochs: int = 1,
442
+ gradient_accumulation_steps: int = 8,
443
+ max_grad_norm: float = 1.0,
444
+ log_interval: int = 10,
445
+ save_interval: int = 2000,
446
+ checkpoint_dir: str = "checkpoints/reward_checkpoints"
447
+ ):
448
+ self.device = torch.device(f'cuda:{LOCAL_RANK}')
449
+ self.model = reward_model
450
+ self.tokenizer = tokenizer
451
+ self.pad_token_id = tokenizer.pad_token_id
452
+ self.optimizer = torch.optim.AdamW(
453
+ self.model.parameters(),
454
+ lr=learning_rate,
455
+ weight_decay=weight_decay
456
+ )
457
+ self.use_amp = True
458
+ self.scaler = torch.amp.GradScaler('cuda', enabled=self.use_amp)
459
+ self.num_epochs = num_epochs
460
+ self.gradient_accumulation_steps = gradient_accumulation_steps
461
+ self.max_grad_norm = max_grad_norm
462
+ self.log_interval = log_interval
463
+ self.save_interval = save_interval
464
+ self.checkpoint_dir = Path(checkpoint_dir)
465
 
466
+ self.file_handler = None
467
+ if IS_MAIN_PROCESS:
468
+ self.checkpoint_dir.mkdir(parents=True, exist_ok=True)
469
+ log_file_name = f"reward_train_{datetime.now().strftime('%Y%m%d_%H%M%S')}.log"
470
+ log_path = self.checkpoint_dir / log_file_name
471
+
472
+ self.file_handler = logging.FileHandler(log_path, encoding='utf-8')
473
+ self.file_handler.setLevel(logging.INFO)
474
+ formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
475
+ self.file_handler.setFormatter(formatter)
476
+ logger.addHandler(self.file_handler)
477
+
478
+ self.global_step = 0
479
+ self.running_loss = 0.0
480
+ self.running_acc = 0.0
481
+
482
+ def train_step(self, batch: dict) -> dict:
483
+ chosen_ids = batch['chosen'].to(self.device)
484
+ rejected_ids = batch['rejected'].to(self.device)
485
+
486
+ batch_size = chosen_ids.size(0)
487
+
488
+ chosen_attention_mask = (chosen_ids != self.pad_token_id).long()
489
+ chosen_position_ids = torch.cumsum(chosen_attention_mask, dim=1) - 1
490
+ chosen_position_ids = chosen_position_ids * chosen_attention_mask
491
+
492
+ chosen_input = {'segments': [{'type': 'text', 'data': chosen_ids, 'modality_id': 0}]}
493
+
494
+ rejected_attention_mask = (rejected_ids != self.pad_token_id).long()
495
+ rejected_position_ids = torch.cumsum(rejected_attention_mask, dim=1) - 1
496
+ rejected_position_ids = rejected_position_ids * rejected_attention_mask
497
+
498
+ rejected_input = {'segments': [{'type': 'text', 'data': rejected_ids, 'modality_id': 0}]}
499
+
500
+ with torch.amp.autocast('cuda', enabled=self.use_amp):
501
+ chosen_rewards_full = self.model(
502
+ chosen_input,
503
+ attention_mask=chosen_attention_mask,
504
+ position_ids=chosen_position_ids
505
+ )
506
+
507
+ rejected_rewards_full = self.model(
508
+ rejected_input,
509
+ attention_mask=rejected_attention_mask,
510
+ position_ids=rejected_position_ids
511
+ )
512
+
513
+ chosen_last_idx = chosen_attention_mask.sum(dim=1) - 1
514
+ rejected_last_idx = rejected_attention_mask.sum(dim=1) - 1
515
+
516
+ chosen_rewards = chosen_rewards_full[torch.arange(batch_size, device=self.device), chosen_last_idx]
517
+ rejected_rewards = rejected_rewards_full[torch.arange(batch_size, device=self.device), rejected_last_idx]
518
+
519
+ loss = -F.logsigmoid(chosen_rewards - rejected_rewards).mean()
520
+ acc = (chosen_rewards > rejected_rewards).float().mean().item()
521
+
522
+ loss = loss / self.gradient_accumulation_steps
523
+
524
+ self.scaler.scale(loss).backward()
525
+ raw_loss = loss.item() * self.gradient_accumulation_steps
526
+ return {'loss': raw_loss, 'acc': acc}
527
+
528
+ def optimizer_step(self):
529
+ self.scaler.unscale_(self.optimizer)
530
+ grad_norm = torch.nn.utils.clip_grad_norm_(self.model.parameters(), self.max_grad_norm)
531
+ self.scaler.step(self.optimizer)
532
+ self.scaler.update()
533
+ self.optimizer.zero_grad(set_to_none=True)
534
+ self.global_step += 1
535
+ return grad_norm.item()
536
+
537
+ def save_checkpoint(self, path: Path):
538
+ if not IS_MAIN_PROCESS:
539
+ return
540
+ model_to_save = self.model.module if hasattr(self.model, 'module') else self.model
541
+ checkpoint = {
542
+ 'model_state_dict': model_to_save.state_dict(),
543
+ 'optimizer_state_dict': self.optimizer.state_dict(),
544
+ 'scaler_state_dict': self.scaler.state_dict(),
545
+ 'global_step': self.global_step,
546
+ }
547
+ torch.save(checkpoint, path)
548
+ logger.info(f"Reward checkpoint saved: {path}")
549
+
550
+ def load_checkpoint(self, path: str):
551
+ checkpoint = torch.load(path, map_location=self.device)
552
+ model_to_load = self.model.module if hasattr(self.model, 'module') else self.model
553
+ model_to_load.load_state_dict(checkpoint['model_state_dict'])
554
+ self.optimizer.load_state_dict(checkpoint['optimizer_state_dict'])
555
+ self.scaler.load_state_dict(checkpoint['scaler_state_dict'])
556
+ self.global_step = checkpoint['global_step']
557
+ if IS_MAIN_PROCESS:
558
+ logger.info(f"Reward checkpoint loaded: {path} (step {self.global_step})")
559
+
560
+ def train(self, dataloader, resume_from: Optional[str] = None):
561
+ try:
562
+ if resume_from:
563
+ self.load_checkpoint(resume_from)
564
+
565
+ self.model.train()
566
+ for epoch in range(self.num_epochs):
567
+ if hasattr(dataloader.sampler, 'set_epoch'):
568
+ dataloader.sampler.set_epoch(epoch)
569
+
570
+ iterator = tqdm(dataloader, desc=f"Reward Epoch {epoch+1}/{self.num_epochs}", disable=not IS_MAIN_PROCESS)
571
+ accum_steps = 0
572
+ self.running_loss = 0.0
573
+ self.running_acc = 0.0
574
+
575
+ for batch in iterator:
576
+ if batch is None or 'chosen' not in batch:
577
+ continue
578
+
579
+ stats = self.train_step(batch)
580
+ single_step_loss = stats['loss'] / self.gradient_accumulation_steps
581
+ self.running_loss += single_step_loss
582
+ self.running_acc += stats['acc']
583
+ accum_steps += 1
584
+
585
+ if accum_steps == self.gradient_accumulation_steps:
586
+ grad_norm = self.optimizer_step()
587
+ accum_steps = 0
588
+
589
+ if IS_MAIN_PROCESS:
590
+ iterator.set_postfix({'loss': f"{stats['loss']:.4f}", 'acc': f"{stats['acc']:.4f}"})
591
+
592
+ if self.global_step % self.log_interval == 0:
593
+ avg_loss = self.running_loss / self.log_interval
594
+ avg_acc = self.running_acc / self.log_interval
595
+ if WORLD_SIZE > 1:
596
+ loss_tensor = torch.tensor(avg_loss, device=self.device)
597
+ acc_tensor = torch.tensor(avg_acc, device=self.device)
598
+ dist.all_reduce(loss_tensor, op=dist.ReduceOp.AVG)
599
+ dist.all_reduce(acc_tensor, op=dist.ReduceOp.AVG)
600
+ avg_loss = loss_tensor.item()
601
+ avg_acc = acc_tensor.item()
602
+ if IS_MAIN_PROCESS:
603
+ logger.info(f"Reward Step {self.global_step} | Loss {avg_loss:.6f} | Acc {avg_acc:.4f} | Grad {grad_norm:.4f}")
604
+ self.running_loss = 0.0
605
+ self.running_acc = 0.0
606
+
607
+ if self.global_step % self.save_interval == 0 and self.global_step > 0:
608
+ self.save_checkpoint(self.checkpoint_dir / f"step_{self.global_step}.pt")
609
+ finally:
610
+ if IS_MAIN_PROCESS and self.file_handler:
611
+ logger.removeHandler(self.file_handler)
612
+ self.file_handler.close()
613
+ self.file_handler = None
614
+
615
+
616
+ def load_checkpoint_flexible(model, path, device):
617
+ logger.info(f"Loading weights from {path}...")
618
+ checkpoint = torch.load(path, map_location=device)
619
+
620
+ state_dict = None
621
+ if 'actor_state_dict' in checkpoint:
622
+ logger.info("Detected GRPO checkpoint format.")
623
+ state_dict = checkpoint['actor_state_dict']
624
+
625
+ elif 'model_state_dict' in checkpoint:
626
+ logger.info("Detected Standard/SFT checkpoint format.")
627
+ state_dict = checkpoint['model_state_dict']
628
+
629
+ else:
630
+ logger.info("Detected raw state dict format.")
631
+ state_dict = checkpoint
632
+
633
+ model_has_module = hasattr(model, 'module')
634
+
635
+ new_state_dict = {}
636
+ for k, v in state_dict.items():
637
+ if k.startswith('module.') and not model_has_module:
638
+ new_state_dict[k[7:]] = v
639
+ else:
640
+ new_state_dict[k] = v
641
+
642
+ model.load_state_dict(new_state_dict, strict=False)
643
+ logger.info("Weights loaded successfully.")
644
+ del checkpoint
645
+ gc.collect()
646
+ torch.cuda.empty_cache()
647
 
648
  def main():
 
 
649
  config = {
 
650
  'model_dim': 1536,
651
  'vocab_size': 151665,
652
  'n_layers': 12,
653
  'n_heads': 12,
654
  'n_kv_heads': 4,
655
+ 'max_seq_len': 2048,
656
  'dropout': 0.0,
657
  'use_moe': False,
658
+
659
+ 'batch_size': 4,
660
+ 'gradient_accumulation_steps': 16,
661
  'learning_rate': 1e-5,
662
  'weight_decay': 0.01,
663
+ 'num_epochs': 4,
664
  'max_grad_norm': 1.0,
665
 
666
+ 'warmup_steps': 100,
667
+ 'scheduler_type': 'cosine',
668
+ 'min_lr_ratio': 0.1,
669
+
670
+ 'data_mix': 'think_math_mix',
671
+ 'max_samples_train': None,
672
  'max_samples_eval': 1000,
673
+ 'max_length': 2048,
674
+ 'num_workers': 2,
675
+
 
676
  'do_rlhf': False,
677
+ 'preference_dataset': 'grpo_preferences_local',
678
+ 'grpo_prompt_mix': 'default',
679
+ 'grpo_iterations': 4,
680
  'grpo_kl_coef': 0.04,
681
  'grpo_group_size': 4,
682
+ 'grpo_max_gen_len': 256,
683
+ 'grpo_temperature': 0.9,
684
+ 'grpo_prompt_batch_size': 1,
685
+ 'grpo_max_prompts': 5000,
686
+ 'grpo_resume_path': None,
687
+
688
+ 'pretrain_checkpoint': '/root/checkpoints/pretrain_fixed/step_45000.pt',
689
+ 'sft_checkpoint': '/root/checkpoints/dcpo_posttrain_round3/step_7800.pt',
690
+ 'checkpoint_dir': '/root/checkpoints/dcpo_posttrain_round3',
691
  'log_interval': 50,
692
+ 'eval_interval': 1000,
693
+ 'save_interval': 50,
694
  }
695
+
696
+ if IS_MAIN_PROCESS:
697
+ logger.info("Configuration:")
698
+ logger.info(json.dumps(config, indent=2))
699
+ logger.info(f"Running DDP on Rank: {RANK}, Local Rank: {LOCAL_RANK}, World Size: {WORLD_SIZE}")
700
+
701
  logger.info("\nInitializing tokenizer...")
702
  tokenizer = AutoTokenizer.from_pretrained(
703
  "Qwen/Qwen2.5-7B-Instruct",
704
  use_fast=True,
705
  trust_remote_code=True
706
  )
 
707
  if tokenizer.pad_token is None:
708
  tokenizer.pad_token = tokenizer.eos_token
709
  tokenizer.pad_token_id = tokenizer.eos_token_id
710
+
711
  config['vocab_size'] = len(tokenizer)
712
+
713
+ def create_model_architecture():
714
+ return MultiModalDenseTransformer(
715
+ model_dim=config['model_dim'],
716
+ vocab_size=config['vocab_size'],
717
+ n_layers=config['n_layers'],
718
+ n_heads=config['n_heads'],
719
+ n_kv_heads=config['n_kv_heads'],
720
+ max_seq_len=config['max_seq_len'],
721
+ dropout=config['dropout'],
722
+ use_moe=config['use_moe'],
723
+ use_gradient_checkpointing=True,
724
+ rope_scaling_type="yarn",
725
+ use_multimodal_fusion=False,
726
+ use_contrastive=False
727
+ )
728
+
729
+ checkpoint_to_load = config.get('sft_checkpoint') or config.get('pretrain_checkpoint')
730
 
731
+ do_sft = config.get('sft_checkpoint')
732
+
733
+ if do_sft:
734
+ if IS_MAIN_PROCESS:
735
+ logger.info("\n" + "="*80)
736
+ logger.info("PHASE 1: Supervised Fine-Tuning with LR Scheduler")
737
+ logger.info("="*80)
738
+
739
+ model = create_model_architecture()
740
+ if checkpoint_to_load:
741
+ if IS_MAIN_PROCESS: logger.info(f"Loading checkpoint for SFT: {checkpoint_to_load}")
742
+ checkpoint = torch.load(checkpoint_to_load, map_location=f'cuda:{LOCAL_RANK}')
743
+ model.load_state_dict(checkpoint['model_state_dict'])
744
+ del checkpoint
745
+
746
+ _tmp_loader = create_posttrain_dataloader(
747
+ mix_name=config['data_mix'], tokenizer=tokenizer,
748
+ batch_size=config['batch_size'], num_workers=config['num_workers'],
749
+ max_length=config['max_length'], max_samples=config['max_samples_train'],
750
+ split='train', shuffle=True
751
+ )
752
+ train_dataloader = get_distributed_dataloader(_tmp_loader, config['batch_size'], config['num_workers'])
753
+
754
+ trainer = PostTrainer(
755
+ model=model,
756
+ tokenizer=tokenizer,
757
+ learning_rate=config['learning_rate'],
758
+ weight_decay=config['weight_decay'],
759
+ num_epochs=config['num_epochs'],
760
+ gradient_accumulation_steps=config['gradient_accumulation_steps'],
761
+ max_grad_norm=config['max_grad_norm'],
762
+ checkpoint_dir=config['checkpoint_dir'],
763
+ warmup_steps=config['warmup_steps'],
764
+ scheduler_type=config['scheduler_type'],
765
+ min_lr_ratio=config['min_lr_ratio']
766
+ )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
767
 
768
+ sft_resume_path = None
769
+ if IS_MAIN_PROCESS:
770
+ checkpoint_dir = Path(config['checkpoint_dir'])
771
+ if checkpoint_dir.exists():
772
+ ckpts = sorted([p for p in checkpoint_dir.glob("step_*.pt")], key=lambda p: int(p.stem.split('_')[1]))
773
+ if ckpts:
774
+ latest = ckpts[-1]
775
+ sft_resume_path = str(latest)
776
+ logger.info(f"Resuming SFT training from {sft_resume_path}")
777
+
778
+ if WORLD_SIZE > 1:
779
+ if IS_MAIN_PROCESS:
780
+ resume_path_list = [sft_resume_path]
781
+ else:
782
+ resume_path_list = [None]
783
+ dist.broadcast_object_list(resume_path_list, src=0)
784
+ sft_resume_path = resume_path_list[0]
785
+
786
+ trainer.train(train_dataloader, None, resume_from=sft_resume_path)
787
+
788
+ sft_save_path = Path(config['checkpoint_dir']) / "sft_complete.pt"
789
+ trainer.save_checkpoint(sft_save_path)
790
+
791
+ checkpoint_to_load = str(sft_save_path)
792
+
793
+ del model, trainer, train_dataloader
794
+ force_cleanup()
795
+
796
+ if IS_MAIN_PROCESS:
797
+ logger.info("Training Complete!")
798
+
799
+ if WORLD_SIZE > 1:
800
+ dist.destroy_process_group()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
801
 
802
  if __name__ == "__main__":
803
  main()