Skip to content

Commit efb60c4

Browse files
committed
Refactor Training and Validation for Correct Resumption and Evaluation
1 parent b18eef8 commit efb60c4

3 files changed

Lines changed: 90 additions & 15 deletions

File tree

aetheris/cli/main.py

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -55,10 +55,17 @@ def train_command(args):
5555
# --- STAGE 1: PRE-TRAINING ---
5656
if current_stage == "Pre-Training" or start_step == 0:
5757
pt_loader = create_streaming_loader("cerebras/SlimPajama-627B", "train",
58-
tokenizer, config, args.batch_size, mode="pretrain", hf_token=args.hf_token)
58+
tokenizer, config, args.batch_size, mode="pretrain",
59+
hf_token=args.hf_token, start_step=start_step)
5960

61+
# Validation loader (no skipping needed, always from start of val set)
62+
pt_val_loader = create_streaming_loader("cerebras/SlimPajama-627B", "validation",
63+
tokenizer, config, args.batch_size, mode="pretrain",
64+
hf_token=args.hf_token)
65+
6066
start_step = trainer.train_epoch(pt_loader, total_steps=args.pretrain_steps,
61-
start_step=start_step, stage_name="Pre-Training")
67+
start_step=start_step, stage_name="Pre-Training",
68+
val_loader=pt_val_loader)
6269
current_stage = "SFT"
6370
start_step = 0
6471

@@ -68,10 +75,16 @@ def train_command(args):
6875
param_group['lr'] = 5e-5
6976

7077
sft_loader = create_streaming_loader("OpenAssistant/oasst1", "train",
71-
tokenizer, config, args.batch_size, mode="sft", hf_token=args.hf_token)
78+
tokenizer, config, args.batch_size, mode="sft",
79+
hf_token=args.hf_token, start_step=start_step)
80+
81+
sft_val_loader = create_streaming_loader("OpenAssistant/oasst1", "validation",
82+
tokenizer, config, args.batch_size, mode="sft",
83+
hf_token=args.hf_token)
7284

7385
trainer.train_epoch(sft_loader, total_steps=args.sft_steps,
74-
start_step=start_step, stage_name="SFT")
86+
start_step=start_step, stage_name="SFT",
87+
val_loader=sft_val_loader)
7588

7689
print("\nTraining Complete!")
7790

aetheris/data.py

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,13 @@ def get_tokenizer(model_name: str = "gpt2"):
1313
return tokenizer
1414

1515
class StreamingDataset(IterableDataset):
16-
def __init__(self, dataset, tokenizer, max_seq_len, mode="pretrain", buffer_size=500):
16+
def __init__(self, dataset, tokenizer, max_seq_len, mode="pretrain", buffer_size=500, skip_samples=0):
1717
self.dataset = dataset
1818
self.tokenizer = tokenizer
1919
self.max_seq_len = max_seq_len
2020
self.mode = mode
2121
self.buffer_size = buffer_size
22+
self.skip_samples = skip_samples
2223

2324
def _prepare_sft_text(self, example):
2425
if 'messages' in example:
@@ -39,7 +40,10 @@ def _prepare_sft_text(self, example):
3940
def __iter__(self) -> Iterator[Dict[str, torch.Tensor]]:
4041
iterator = iter(self.dataset)
4142
buffer = []
42-
43+
44+
# Calculate roughly how many items to skip if they were yielded
45+
# We process skipping in the yield loop
46+
4347
for example in iterator:
4448
text = (example.get('text', '') if self.mode == "pretrain"
4549
else self._prepare_sft_text(example))
@@ -70,16 +74,32 @@ def __iter__(self) -> Iterator[Dict[str, torch.Tensor]]:
7074
if len(buffer) >= self.buffer_size:
7175
random.shuffle(buffer)
7276
for _ in range(self.buffer_size // 2):
73-
yield buffer.pop()
77+
item = buffer.pop()
78+
if self.skip_samples > 0:
79+
self.skip_samples -= 1
80+
continue
81+
yield item
7482

7583
# Yield remaining
7684
random.shuffle(buffer)
7785
while buffer:
78-
yield buffer.pop()
86+
item = buffer.pop()
87+
if self.skip_samples > 0:
88+
self.skip_samples -= 1
89+
continue
90+
yield item
7991

80-
def create_streaming_loader(dataset_name, split, tokenizer, config, batch_size, mode="pretrain", hf_token=None):
92+
def create_streaming_loader(dataset_name, split, tokenizer, config, batch_size, mode="pretrain", hf_token=None, start_step=0):
8193
raw_dataset = load_dataset(dataset_name, split=split, streaming=True,
8294
trust_remote_code=True, token=hf_token)
83-
stream_ds = StreamingDataset(raw_dataset, tokenizer, config.max_seq_len, mode=mode)
95+
96+
# Calculate samples to skip: start_step * batch_size
97+
skip_samples = start_step * batch_size
98+
if skip_samples > 0:
99+
print(f" [Loader] Resuming: Fast-forwarding dataset by {skip_samples} samples...")
100+
101+
stream_ds = StreamingDataset(raw_dataset, tokenizer, config.max_seq_len, mode=mode, skip_samples=skip_samples)
102+
103+
# Increase num_workers for better utilization
84104
return DataLoader(stream_ds, batch_size=batch_size, pin_memory=True,
85-
num_workers=1, prefetch_factor=2)
105+
num_workers=4, prefetch_factor=4)

aetheris/trainer/trainer.py

Lines changed: 46 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,48 @@ def __init__(self, model, optimizer, scaler, config, device, checkpoint_dir, log
1515

1616
self.model.to(self.device)
1717

18-
def train_epoch(self, train_loader, total_steps, start_step=0, stage_name="Training"):
18+
def validate(self, val_loader, global_step):
19+
self.model.eval()
20+
total_loss = 0
21+
total_items = 0
22+
num_batches = 100 # Validate on 100 batches to save time
23+
24+
print(f"\n[Validation] Starting validation at step {global_step}...")
25+
26+
with torch.no_grad():
27+
for i, batch in enumerate(val_loader):
28+
if i >= num_batches:
29+
break
30+
31+
input_ids, labels = batch
32+
input_ids = input_ids.to(self.device, non_blocking=True)
33+
labels = labels.to(self.device, non_blocking=True)
34+
35+
# Auto-cast context
36+
if self.device.type == 'cuda':
37+
autocast_dtype = torch.float16
38+
else:
39+
autocast_dtype = torch.bfloat16
40+
41+
use_autocast = True if self.config.torch_dtype != torch.float32 else False
42+
43+
if use_autocast:
44+
with torch.amp.autocast('cuda' if self.device.type == 'cuda' else 'cpu', dtype=autocast_dtype):
45+
output = self.model(input_ids, labels)
46+
else:
47+
output = self.model(input_ids, labels)
48+
49+
total_loss += output["loss"].item()
50+
total_items += 1
51+
52+
avg_loss = total_loss / total_items if total_items > 0 else 0
53+
perplexity = torch.exp(torch.tensor(avg_loss)).item()
54+
55+
print(f"[Validation] Step {global_step} | Loss: {avg_loss:.4f} | PPL: {perplexity:.4f}")
56+
self.model.train()
57+
return avg_loss
58+
59+
def train_epoch(self, train_loader, total_steps, start_step=0, stage_name="Training", val_loader=None, eval_every=500):
1960
print(f"\n{'='*70}\nStarting {stage_name}: Target Steps={total_steps}\n{'='*70}")
2061
self.model.train()
2162
global_step = start_step
@@ -29,9 +70,7 @@ def train_epoch(self, train_loader, total_steps, start_step=0, stage_name="Train
2970
while global_step < total_steps:
3071
step_start = time.time()
3172

32-
# Clear cache periodically
33-
if global_step % 100 == 0:
34-
torch.cuda.empty_cache()
73+
# Removed periodic cache clearing for performance
3574

3675
self.optimizer.zero_grad(set_to_none=True)
3776

@@ -99,5 +138,8 @@ def train_epoch(self, train_loader, total_steps, start_step=0, stage_name="Train
99138

100139
if global_step % 500 == 0:
101140
save_checkpoint(self.model, self.optimizer, self.scaler, global_step, stage_name, self.checkpoint_dir)
141+
142+
if val_loader and global_step % eval_every == 0 and global_step > start_step:
143+
self.validate(val_loader, global_step)
102144

103145
return global_step

0 commit comments

Comments
 (0)