Fix causal LM evaluation and report output creation

This commit is contained in:
Agentirish
2026-08-14 19:03:36 -04:00
committed by Joseph Magly
parent 9cf69d20d8
commit bf8688e13e
3 changed files with 111 additions and 6 deletions
+23
View File
@@ -0,0 +1,23 @@
model:
name: gpt2
task: causal_lm
dtype: float16
device: cuda
dataset:
name: Salesforce/wikitext
subset: wikitext-2-raw-v1
split: test
text_column: text
max_samples: 25
strategies:
- name: layer_removal
params: {}
metrics:
- perplexity
batch_size: 4
max_length: 256
output_dir: results/gpt2_gpu_quick
+1
View File
@@ -762,6 +762,7 @@ def _cmd_report(args):
report.print_summary()
output_dir = Path(args.output_dir) if args.output_dir else path.parent
output_dir.mkdir(parents=True, exist_ok=True)
metric_name = list(data["baseline_metrics"].keys())[0]
try:
report.plot_impact(metric=metric_name, output_path=output_dir / "impact.png")
+87 -6
View File
@@ -50,20 +50,54 @@ class Evaluator:
raise ValueError(f"Unsupported task: {self.handle.task}")
def _evaluate_causal_lm(self) -> dict[str, float]:
model = self.handle.model
tokenizer = self.handle.tokenizer
device = next(model.parameters()).device
ds = self.dataset
# WikiText and similar corpora contain empty separator rows.
# Filter them before max_samples so max_samples means usable texts.
raw_texts = ds[self.text_column]
valid_indices = [
index
for index, text in enumerate(raw_texts)
if isinstance(text, str) and text.strip()
]
if not valid_indices:
raise ValueError(
f"Dataset contains no non-empty text in column "
f"{self.text_column!r}."
)
ds = ds.select(valid_indices)
if self.max_samples is not None:
ds = ds.select(range(min(self.max_samples, len(ds))))
total_loss = 0.0
total_tokens = 0
skipped_batches = 0
for i in tqdm(range(0, len(ds), self.batch_size), desc="Evaluating PPL"):
for i in tqdm(
range(0, len(ds), self.batch_size),
desc="Evaluating PPL",
):
batch_texts = ds[i : i + self.batch_size][self.text_column]
# Defensive filtering in case a custom dataset returns unexpected
# values after selection or transformation.
batch_texts = [
text
for text in batch_texts
if isinstance(text, str) and text.strip()
]
if not batch_texts:
skipped_batches += 1
continue
encodings = tokenizer(
batch_texts,
return_tensors="pt",
@@ -75,15 +109,62 @@ class Evaluator:
input_ids = encodings["input_ids"]
attention_mask = encodings["attention_mask"]
outputs = model(input_ids=input_ids, attention_mask=attention_mask, labels=input_ids)
# Mask out padding tokens for loss computation
num_tokens = attention_mask[:, 1:].sum().item()
# A causal-LM loss requires at least one prediction target after
# shifting. A sequence length below two cannot contribute.
if (
input_ids.ndim != 2
or input_ids.numel() == 0
or input_ids.shape[1] < 2
):
skipped_batches += 1
continue
labels = input_ids.clone()
# Hugging Face causal-LM losses ignore labels set to -100.
# This prevents padded positions from affecting perplexity.
labels[attention_mask == 0] = -100
num_tokens = labels[:, 1:].ne(-100).sum().item()
if num_tokens <= 0:
skipped_batches += 1
continue
outputs = model(
input_ids=input_ids,
attention_mask=attention_mask,
labels=labels,
)
if outputs.loss is None:
raise RuntimeError(
"The causal language model returned no loss value."
)
if not torch.isfinite(outputs.loss):
raise RuntimeError(
f"Non-finite evaluation loss encountered at batch "
f"offset {i}: {outputs.loss.item()}"
)
total_loss += outputs.loss.item() * num_tokens
total_tokens += num_tokens
if total_tokens <= 0:
raise RuntimeError(
"Perplexity evaluation produced zero valid prediction tokens. "
"Check the dataset text column and tokenizer configuration."
)
if skipped_batches:
tqdm.write(
f"Skipped {skipped_batches} empty or too-short batch(es)."
)
import math
avg_loss = total_loss / max(total_tokens, 1)
avg_loss = total_loss / total_tokens
return {"perplexity": math.exp(avg_loss)}
def _evaluate_classification(self) -> dict[str, float]: