Download source/src/strata/eval/tasks.py from nur-dev/strata-headquotient-q25: direct link, hf CLI and curl.
- Browser
- Download file 7.25 kB
-
https://huggingface.co/nur-dev/strata-headquotient-q25/resolve/main/source/src/strata/eval/tasks.py
- Command line
-
hf download hf://nur-dev/strata-headquotient-q25/source/src/strata/eval/tasks.py
-
curl -L -o tasks.py https://huggingface.co/nur-dev/strata-headquotient-q25/resolve/main/source/src/strata/eval/tasks.py
7.25 kB
| """Zero-shot task adapters + metrics for the fetched eval sets. | |
| Two task shapes, both scored purely by LM likelihood: | |
| * Minimal pairs (BLiMP): accuracy = fraction where the grammatical sentence gets | |
| a higher total log-probability than the ungrammatical one. | |
| * Multiple choice (COPA / XCOPA): accuracy = fraction where the gold choice gets | |
| the highest (length-normalised) continuation log-probability. | |
| Loaders read the immutable raw parquet directly (pyarrow), so they do not depend | |
| on dataset loading scripts. | |
| """ | |
| from __future__ import annotations | |
| import glob | |
| from dataclasses import dataclass, field | |
| from pathlib import Path | |
| import torch | |
| from strata.eval.scoring import encode_with_continuation, score_sequences | |
| # Cause/effect connectors for building COPA/XCOPA prompts, per language. | |
| _CONNECTORS = { | |
| "en": {"cause": "because", "effect": "so"}, | |
| "zh": {"cause": "因为", "effect": "所以"}, | |
| } | |
| class MinimalPairExample: | |
| good: str | |
| bad: str | |
| tag: str = "" | |
| class MultipleChoiceExample: | |
| context: str | |
| choices: tuple[str, ...] | |
| gold: int | |
| class TaskResult: | |
| task: str | |
| metric: str | |
| value: float | |
| n_examples: int | |
| subscores: dict[str, float] = field(default_factory=dict) | |
| def to_dict(self) -> dict[str, object]: | |
| return { | |
| "task": self.task, | |
| "metric": self.metric, | |
| "value": self.value, | |
| "n_examples": self.n_examples, | |
| "subscores": self.subscores, | |
| } | |
| # --------------------------------------------------------------------------- | |
| # Evaluation drivers | |
| # --------------------------------------------------------------------------- | |
| def evaluate_minimal_pairs( | |
| model, | |
| tokenizer, | |
| examples: list[MinimalPairExample], | |
| *, | |
| task: str, | |
| device: torch.device, | |
| pad_id: int, | |
| batch_size: int = 16, | |
| precision: str = "bf16", | |
| predicate_memory_intervention: str = "none", | |
| predicate_memory_residual_scale: float | None = None, | |
| graph_object_residual_scale: float | None = None, | |
| ) -> TaskResult: | |
| """Total-log-prob comparison of good vs. bad sentences.""" | |
| if not examples: | |
| raise ValueError(f"{task}: no examples to evaluate") | |
| seqs: list[list[int]] = [] | |
| ctx: list[int] = [] | |
| for ex in examples: | |
| for text in (ex.good, ex.bad): | |
| ids = list(tokenizer.encode(text, add_bos=True).input_ids) | |
| seqs.append(ids) | |
| ctx.append(1) # score every real token (position 0 is BOS) | |
| scores = score_sequences( | |
| model, seqs, ctx, device=device, pad_id=pad_id, batch_size=batch_size, precision=precision, | |
| predicate_memory_intervention=predicate_memory_intervention, | |
| predicate_memory_residual_scale=predicate_memory_residual_scale, | |
| graph_object_residual_scale=graph_object_residual_scale, | |
| ) | |
| correct = 0 | |
| per_tag: dict[str, list[int]] = {} | |
| for i, ex in enumerate(examples): | |
| good_score, bad_score = scores[2 * i], scores[2 * i + 1] | |
| hit = int(good_score > bad_score) | |
| correct += hit | |
| if ex.tag: | |
| per_tag.setdefault(ex.tag, []).append(hit) | |
| subscores = {tag: sum(v) / len(v) for tag, v in sorted(per_tag.items())} | |
| return TaskResult(task, "accuracy", correct / len(examples), len(examples), subscores) | |
| def evaluate_multiple_choice( | |
| model, | |
| tokenizer, | |
| examples: list[MultipleChoiceExample], | |
| *, | |
| task: str, | |
| device: torch.device, | |
| pad_id: int, | |
| batch_size: int = 16, | |
| precision: str = "bf16", | |
| length_normalize: bool = True, | |
| predicate_memory_intervention: str = "none", | |
| predicate_memory_residual_scale: float | None = None, | |
| graph_object_residual_scale: float | None = None, | |
| ) -> TaskResult: | |
| """Pick the choice with the highest (length-normalised) continuation log-prob.""" | |
| if not examples: | |
| raise ValueError(f"{task}: no examples to evaluate") | |
| seqs: list[list[int]] = [] | |
| ctx: list[int] = [] | |
| spans: list[tuple[int, int]] = [] # (start, end) index into seqs per example | |
| for ex in examples: | |
| start = len(seqs) | |
| for choice in ex.choices: | |
| ids, context_len = encode_with_continuation(tokenizer, ex.context, choice) | |
| seqs.append(ids) | |
| ctx.append(context_len) | |
| spans.append((start, len(seqs))) | |
| scores = score_sequences( | |
| model, seqs, ctx, device=device, pad_id=pad_id, batch_size=batch_size, | |
| precision=precision, length_normalize=length_normalize, | |
| predicate_memory_intervention=predicate_memory_intervention, | |
| predicate_memory_residual_scale=predicate_memory_residual_scale, | |
| graph_object_residual_scale=graph_object_residual_scale, | |
| ) | |
| correct = 0 | |
| for ex, (start, end) in zip(examples, spans): | |
| choice_scores = scores[start:end] | |
| pred = max(range(len(choice_scores)), key=lambda k: choice_scores[k]) | |
| correct += int(pred == ex.gold) | |
| return TaskResult(task, "accuracy", correct / len(examples), len(examples)) | |
| # --------------------------------------------------------------------------- | |
| # Loaders (read raw parquet directly) | |
| # --------------------------------------------------------------------------- | |
| def _read_parquet_rows(path: str) -> list[dict]: | |
| import pyarrow.parquet as pq | |
| return pq.ParquetFile(path).read().to_pylist() | |
| def load_blimp(raw_dir: Path, *, configs: list[str] | None = None, max_per_config: int | None = None) -> list[MinimalPairExample]: | |
| examples: list[MinimalPairExample] = [] | |
| paths = sorted(glob.glob(str(raw_dir / "*" / "*.parquet"))) | |
| if not paths: | |
| raise FileNotFoundError(f"no BLiMP parquet under {raw_dir}") | |
| for path in paths: | |
| uid = Path(path).parent.name | |
| if configs is not None and uid not in configs: | |
| continue | |
| rows = _read_parquet_rows(path) | |
| if max_per_config is not None: | |
| rows = rows[:max_per_config] | |
| for row in rows: | |
| examples.append(MinimalPairExample(good=row["sentence_good"], bad=row["sentence_bad"], tag=uid)) | |
| return examples | |
| def _copa_context(premise: str, question: str, language: str) -> str: | |
| premise = premise.rstrip().rstrip(".。").strip() | |
| connectors = _CONNECTORS.get(language, _CONNECTORS["en"]) | |
| connector = connectors.get(str(question), connectors["effect"]) | |
| joiner = "" if language == "zh" else " " | |
| return f"{premise}{joiner}{connector}{joiner}" | |
| def load_copa(parquet_path: str, *, language: str = "en", max_examples: int | None = None) -> list[MultipleChoiceExample]: | |
| rows = _read_parquet_rows(parquet_path) | |
| rows = [r for r in rows if int(r.get("label", -1)) in (0, 1)] # drop unlabeled test rows | |
| if max_examples is not None: | |
| rows = rows[:max_examples] | |
| examples: list[MultipleChoiceExample] = [] | |
| for row in rows: | |
| context = _copa_context(row["premise"], row["question"], language) | |
| examples.append( | |
| MultipleChoiceExample( | |
| context=context, | |
| choices=(str(row["choice1"]), str(row["choice2"])), | |
| gold=int(row["label"]), | |
| ) | |
| ) | |
| return examples | |