Skip to content

Commit 836674e

Browse files
committed
fix unsloth gradient accumulation in validation
1 parent 545a575 commit 836674e

4 files changed

Lines changed: 54 additions & 19 deletions

File tree

ModelForge/evaluation/metrics.py

Lines changed: 10 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,10 @@ def compute_causal_lm_metrics(eval_pred: EvalPrediction) -> Dict[str, float]:
2121
- Perplexity: exp(loss)
2222
- Loss: Cross-entropy loss
2323
24+
Note: When preprocess_logits_for_metrics is used, predictions contain
25+
pre-computed scalar losses (one per eval batch) instead of full logit tensors.
26+
This prevents RAM exhaustion during evaluation.
27+
2428
Args:
2529
eval_pred: Evaluation prediction with predictions and labels
2630
@@ -29,28 +33,16 @@ def compute_causal_lm_metrics(eval_pred: EvalPrediction) -> Dict[str, float]:
2933
"""
3034
logger.info("Computing causal LM metrics")
3135

32-
import torch
33-
import torch.nn.functional as F
34-
35-
predictions, labels = eval_pred.predictions, eval_pred.label_ids
36-
37-
logits = torch.from_numpy(np.array(predictions)).float()
38-
lbl = torch.from_numpy(np.array(labels))
39-
40-
shift_logits = logits[..., :-1, :].contiguous()
41-
shift_labels = lbl[..., 1:].contiguous()
36+
predictions = eval_pred.predictions
4237

43-
loss = F.cross_entropy(
44-
shift_logits.view(-1, shift_logits.size(-1)),
45-
shift_labels.view(-1).long(),
46-
ignore_index=-100,
47-
reduction="mean",
48-
)
49-
perplexity = float(torch.exp(loss))
38+
# predictions is shape (num_eval_batches, 1) of pre-computed scalar losses
39+
# (from _preprocess_logits_for_metrics in sft_strategy.py)
40+
mean_loss = float(np.mean(predictions))
41+
perplexity = float(np.exp(mean_loss))
5042

5143
metrics = {
5244
"perplexity": perplexity,
53-
"eval_loss": float(loss),
45+
"eval_loss": mean_loss,
5446
}
5547

5648
logger.info(f"Causal LM metrics: {metrics}")

ModelForge/services/training_service.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -432,7 +432,16 @@ def train_model(
432432

433433
# Train
434434
self.training_status["message"] = "Training in progress..."
435-
trainer.train()
435+
provider_name = config.get("provider", "")
436+
if provider_name == "unsloth":
437+
try:
438+
from unsloth import unsloth_train
439+
unsloth_train(trainer)
440+
except ImportError:
441+
logger.warning("unsloth_train not available, falling back to trainer.train()")
442+
trainer.train()
443+
else:
444+
trainer.train()
436445

437446
# Save model
438447
self.training_status["message"] = "Saving model..."

ModelForge/strategies/qlora_strategy.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
from trl import SFTTrainer, SFTConfig
2323

2424
from ..logging_config import logger
25+
from .sft_strategy import _preprocess_logits_for_metrics
2526

2627

2728
class QLoRAStrategy:
@@ -269,6 +270,7 @@ def create_trainer(
269270
peft_config=peft_config,
270271
callbacks=callbacks or [],
271272
compute_metrics=compute_metrics,
273+
preprocess_logits_for_metrics=_preprocess_logits_for_metrics,
272274
)
273275

274276
logger.info("QLoRA SFTTrainer created successfully")

ModelForge/strategies/sft_strategy.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,37 @@
2323
from ..logging_config import logger
2424

2525

26+
def _preprocess_logits_for_metrics(logits, labels):
27+
"""
28+
Reduce logit tensors to scalar loss per batch before CPU accumulation.
29+
30+
This prevents RAM exhaustion when the Trainer accumulates eval predictions.
31+
Instead of storing full logit tensors (batch × seq_len × vocab_size),
32+
we compute the loss on GPU and return only a scalar, reducing memory by
33+
orders of magnitude (e.g., 19.7 GB/batch → bytes/batch).
34+
35+
Args:
36+
logits: Model logits (batch, seq_len, vocab_size)
37+
labels: Token labels (batch, seq_len)
38+
39+
Returns:
40+
Scalar loss tensor for this batch, shape (1,)
41+
"""
42+
import torch.nn.functional as F
43+
44+
shift_logits = logits[..., :-1, :].contiguous()
45+
shift_labels = labels[..., 1:].contiguous()
46+
47+
loss = F.cross_entropy(
48+
shift_logits.view(-1, shift_logits.size(-1)),
49+
shift_labels.view(-1).long(),
50+
ignore_index=-100,
51+
reduction="mean",
52+
)
53+
54+
return loss.unsqueeze(0)
55+
56+
2657
class SFTStrategy:
2758
"""Supervised Fine-Tuning strategy using TRL's SFTTrainer."""
2859

@@ -248,6 +279,7 @@ def create_trainer(
248279
peft_config=peft_config,
249280
callbacks=callbacks or [],
250281
compute_metrics=compute_metrics,
282+
preprocess_logits_for_metrics=_preprocess_logits_for_metrics,
251283
)
252284

253285
logger.info("SFTTrainer created successfully")

0 commit comments

Comments
 (0)