| # """ | |
| # Extended GLUE + extra MC/QA/RC benchmark runner with grid-search (LR sweep + restarts). | |
| # Features: | |
| # BERT-style LR sweep for GLUE tasks (and extended tasks) | |
| # Small-task multiple random restarts (seeded) for robustness | |
| # Full fine-tune training & evaluation | |
| # 5-fold leave-one-fold-out error bars for GLUE eval | |
| # Support for EXTRA_TASKS: boolq, piqa, winogrande, openbookqa, hellaswag, | |
| # arc_challenge (ARC-Challenge), arc_easy (ARC-Easy), race_middle, race_high | |
| # Robust tokenization helpers that support many tokenizer APIs | |
| # Multiple-choice support for variable numbers of choices (2, 4, ...) | |
| # Usage: | |
| # Integrate with your main script which provides tokenizer/model/checkpointing. | |
| # Example: | |
| # run_glue_benchmark(config.benchmark, tokenizer, model, checkpointing, out_dir="glue_outputs") | |
| # Notes: | |
| # This file aims to be comprehensive. It's long by design. | |
| # It expects datasets, transformers, evaluate, torch, numpy, pandas, tqdm to be installed. | |
| # """ | |
| # import os | |
| # import json | |
| # import re | |
| # import math | |
| # import random | |
| # import shutil | |
| # import time | |
| # from pathlib import Path | |
| # from typing import Optional, List, Tuple, Dict, Any | |
| # import torch | |
| # import numpy as np | |
| # import pandas as pd | |
| # from datasets import load_dataset | |
| # from torch.utils.data import DataLoader, TensorDataset | |
| # from tqdm import tqdm | |
| # import evaluate | |
| # Project imports (adjust if your package layout differs) | |
| # from lmr.checkpointing import Checkpointing | |
| # from lmr.ddp import unwrap_model | |
| # --------------------------------------------------------------------- | |
| # GLUE config + EXTRA tasks | |
| # --------------------------------------------------------------------- | |
| # GLUE_TASKS = { | |
| # "cola": {"type": "classification", "num_labels": 2, "hf_name": "cola"}, | |
| # "sst2": {"type": "classification", "num_labels": 2, "hf_name": "sst2"}, | |
| # "mrpc": {"type": "classification", "num_labels": 2, "hf_name": "mrpc"}, | |
| # "stsb": {"type": "regression", "num_labels": 1, "hf_name": "stsb"}, | |
| # "qqp": {"type": "classification", "num_labels": 2, "hf_name": "qqp"}, | |
| # "mnli": {"type": "classification", "num_labels": 3, "hf_name": "mnli"}, | |
| # "qnli": {"type": "classification", "num_labels": 2, "hf_name": "qnli"}, | |
| # "rte": {"type": "classification", "num_labels": 2, "hf_name": "rte"}, | |
| # "wnli": {"type": "classification", "num_labels": 2, "hf_name": "wnli"}, | |
| # } | |
| # Preferred metric key per task (for selecting best run / best epoch) | |
| # PREFERRED_METRIC_KEY = { | |
| # "cola": "matthews_correlation", | |
| # "sst2": "accuracy", | |
| # "mrpc": "accuracy", | |
| # "stsb": "pearson", | |
| # "qqp": "accuracy", | |
| # "mnli": "accuracy", | |
| # "qnli": "accuracy", | |
| # "rte": "accuracy", | |
| # "wnli": "accuracy", | |
| # } | |
| # SMALL_TASKS_RANDOM_RESTARTS = {"cola", "mrpc", "rte", "stsb"} | |
| # BERT_LR_CANDIDATES = [2e-5, 3e-5, 4e-5, 5e-5] | |
| # Extra tasks (multiple-choice and pairwise) | |
| # EXTRA_TASKS = { | |
| # "boolq": { | |
| # "type": "classification", | |
| # "num_labels": 2, | |
| # "hf_path": "boolq", | |
| # "format": "pair", | |
| # }, | |
| # "piqa": { | |
| # "type": "multiple_choice", | |
| # "num_labels": 2, | |
| # "hf_path": "piqa", | |
| # "format": "mc", | |
| # }, | |
| # "winogrande": { | |
| # "type": "multiple_choice", | |
| # "num_labels": 2, | |
| # "hf_path": "winogrande", | |
| # "hf_config": "winogrande_xl", | |
| # "format": "mc", | |
| # }, | |
| # # Added QA / MC / RC datasets | |
| # "openbookqa": { | |
| # "type": "multiple_choice", | |
| # "num_labels": 4, | |
| # "hf_path": "allenai/openbookqa", | |
| # "format": "mc", | |
| # }, | |
| # "hellaswag": { | |
| # "type": "multiple_choice", | |
| # "num_labels": 4, | |
| # "hf_path": "hellaswag", | |
| # "format": "mc", | |
| # }, | |
| # # ARC: ai2_arc has configs "ARC-Challenge" and "ARC-Easy" | |
| # "arc_challenge": { | |
| # "type": "multiple_choice", | |
| # "num_labels": 4, | |
| # "hf_path": "allenai/ai2_arc", | |
| # "hf_config": "ARC-Challenge", | |
| # "format": "mc", | |
| # }, | |
| # "arc_easy": { | |
| # "type": "multiple_choice", | |
| # "num_labels": 4, | |
| # "hf_path": "allenai/ai2_arc", | |
| # "hf_config": "ARC-Easy", | |
| # "format": "mc", | |
| # }, | |
| # # RACE reading comprehension | |
| # "race_middle": { | |
| # "type": "multiple_choice", | |
| # "num_labels": 4, | |
| # "hf_path": "race", | |
| # "hf_config": "middle", | |
| # "format": "mc", | |
| # }, | |
| # "race_high": { | |
| # "type": "multiple_choice", | |
| # "num_labels": 4, | |
| # "hf_path": "race", | |
| # "hf_config": "high", | |
| # "format": "mc", | |
| # }, | |
| # } | |
| # ALL_TASKS = {**GLUE_TASKS, **EXTRA_TASKS} | |
| # --------------------------------------------------------------------- | |
| # Repro helpers | |
| # --------------------------------------------------------------------- | |
| # def _set_all_seeds(seed: int): | |
| # random.seed(seed) | |
| # np.random.seed(seed) | |
| # torch.manual_seed(seed) | |
| # torch.cuda.manual_seed_all(seed) | |
| # torch.backends.cudnn.deterministic = True | |
| # torch.backends.cudnn.benchmark = False | |
| # def _json_dump(obj: Any, path: Path): | |
| # path.parent.mkdir(parents=True, exist_ok=True) | |
| # with open(path, "w", encoding="utf-8") as f: | |
| # json.dump(obj, f, indent=2, ensure_ascii=False) | |
| # def _safe_float(x): | |
| # try: | |
| # if isinstance(x, (np.generic,)): | |
| # return float(x.item()) | |
| # return float(x) | |
| # except Exception: | |
| # return None | |
| # def _metric_to_scalar(task: str, metric_res: Dict[str, Any], fallback_val_loss: Optional[float] = None) -> float: | |
| # if isinstance(metric_res, dict) and metric_res: | |
| # pref = PREFERRED_METRIC_KEY.get(task) | |
| # if pref is not None and pref in metric_res: | |
| # v = _safe_float(metric_res.get(pref)) | |
| # if v is not None and not math.isnan(v): | |
| # return float(v) | |
| # # fallback: first numeric entry | |
| # for _, v in metric_res.items(): | |
| # fv = _safe_float(v) | |
| # if fv is not None and not math.isnan(fv): | |
| # return float(fv) | |
| # if fallback_val_loss is not None: | |
| # try: | |
| # return -float(fallback_val_loss) | |
| # except Exception: | |
| # pass | |
| # return -1e9 | |
| # --------------------------------------------------------------------- | |
| # Task example extraction | |
| # --------------------------------------------------------------------- | |
| # def _get_text_pair_from_example(task: str, ex: dict): | |
| # """ | |
| # Robustly extract (s1, s2) from a HF GLUE example dict ex depending on task. | |
| # Returns (s1:str, s2:Optional) where s2 can be None for single-sentence tasks. | |
| # """ | |
| # # Known per-task fields | |
| # task_field_map = { | |
| # "cola": ("sentence", None), | |
| # "sst2": ("sentence", None), | |
| # "mrpc": ("sentence1", "sentence2"), | |
| # "stsb": ("sentence1", "sentence2"), | |
| # "qqp": ("question1", "question2"), | |
| # "mnli": ("premise", "hypothesis"), | |
| # "qnli": ("question", "sentence"), | |
| # "rte": ("sentence1", "sentence2"), | |
| # "wnli": ("sentence1", "sentence2"), | |
| # } | |
| # f1, f2 = task_field_map.get(task, (None, None)) | |
| # def _try_keys(keys): | |
| # for k in keys: | |
| # if k in ex and ex.get(k) is not None: | |
| # return ex.get(k) | |
| # return None | |
| # s1_candidates = [] | |
| # s2_candidates = [] | |
| # if f1: | |
| # s1_candidates.append(f1) | |
| # s1_candidates += ["sentence1", "premise", "question", "sentence", "text", "question1"] | |
| # if f2: | |
| # s2_candidates.append(f2) | |
| # s2_candidates += ["sentence2", "hypothesis", "question2", "question1", "text2"] | |
| # s1 = _try_keys(s1_candidates) | |
| # s2 = _try_keys(s2_candidates) | |
| # if s1 is None: | |
| # s1 = ex.get("sentence") or ex.get("premise") or ex.get("question") or ex.get("text") | |
| # if s2 is None: | |
| # s2 = ex.get("sentence2") or ex.get("hypothesis") or ex.get("question2") | |
| # s1 = "" if s1 is None else (s1 if isinstance(s1, str) else str(s1)) | |
| # s2 = None if s2 is None else (s2 if isinstance(s2, str) else str(s2)) | |
| # return s1, s2 | |
| # --------------------------------------------------------------------- | |
| # Tokenization helpers (robust) | |
| # --------------------------------------------------------------------- | |
| # def _pad_and_tensorize(input_ids_list, attention_mask_list, pad_token_id: int): | |
| # max_len = max(len(x) for x in input_ids_list) if input_ids_list else 0 | |
| # ids_padded = [x + [pad_token_id] * (max_len - len(x)) for x in input_ids_list] | |
| # mask_padded = [m + [0] * (max_len - len(m)) for m in attention_mask_list] | |
| # input_ids = torch.tensor(ids_padded, dtype=torch.long) | |
| # attention_mask = torch.tensor(mask_padded, dtype=torch.long) | |
| # return input_ids, attention_mask | |
| # def _batch_tokenize(tokenizer, texts: List[Tuple[Optional[str], Optional[str]]], max_length: int = 128): | |
| # sanitized = [] | |
| # for a, b in texts: | |
| # a_s = "" if a is None else (a if isinstance(a, str) else str(a)) | |
| # b_s = None if b is None else (b if isinstance(b, str) else str(b)) | |
| # sanitized.append((a_s, b_s)) | |
| # try: | |
| # flat = [(a if b is None else (a, b)) for a, b in sanitized] | |
| # enc = tokenizer(flat, truncation=True, padding=False, max_length=max_length) | |
| # if isinstance(enc.get("input_ids", None), torch.Tensor): | |
| # enc["input_ids"] = enc["input_ids"].tolist() | |
| # if isinstance(enc.get("attention_mask", None), torch.Tensor): | |
| # enc["attention_mask"] = enc["attention_mask"].tolist() | |
| # return enc | |
| # except Exception: | |
| # pass | |
| # for method_name in ("batch_encode", "encode_batch", "batch_encode_plus", "encode_batch_pair", "encode_batch_items"): | |
| # fn = getattr(tokenizer, method_name, None) | |
| # if fn is None: | |
| # continue | |
| # try: | |
| # try: | |
| # enc = fn(sanitized, max_length=max_length, truncation=True, padding=False) | |
| # except TypeError: | |
| # enc = fn(sanitized) | |
| # if isinstance(enc.get("input_ids", None), torch.Tensor): | |
| # enc["input_ids"] = enc["input_ids"].tolist() | |
| # if isinstance(enc.get("attention_mask", None), torch.Tensor): | |
| # enc["attention_mask"] = enc["attention_mask"].tolist() | |
| # return enc | |
| # except Exception: | |
| # continue | |
| # input_ids_list = [] | |
| # attention_mask_list = [] | |
| # for a, b in sanitized: | |
| # try: | |
| # if b is None: | |
| # try: | |
| # single = tokenizer.encode(a) | |
| # except TypeError: | |
| # single = tokenizer.encode([a]) | |
| # else: | |
| # single = None | |
| # try: | |
| # single = tokenizer.encode((a, b)) | |
| # except Exception: | |
| # try: | |
| # single = tokenizer.encode(a, b) | |
| # except Exception: | |
| # single = tokenizer(a if b is None else (a, b)) | |
| # if isinstance(single, dict): | |
| # ids = single.get("input_ids") or single.get("ids") or [] | |
| # mask = single.get("attention_mask") or single.get("mask") or [1] * len(ids) | |
| # elif isinstance(single, torch.Tensor): | |
| # ids = single.tolist() | |
| # mask = [1] * len(ids) | |
| # elif isinstance(single, list): | |
| # ids = single | |
| # mask = [1] * len(ids) | |
| # else: | |
| # tmp = tokenizer(a if b is None else (a, b)) | |
| # if isinstance(tmp, dict): | |
| # ids = tmp.get("input_ids") or tmp.get("ids") or [] | |
| # mask = tmp.get("attention_mask") or tmp.get("mask") or [1] * len(ids) | |
| # elif torch.is_tensor(tmp): | |
| # ids = tmp.tolist() | |
| # mask = [1] * len(ids) | |
| # else: | |
| # ids = list(tmp) | |
| # mask = [1] * len(ids) | |
| # if len(ids) > max_length: | |
| # ids = ids[:max_length] | |
| # mask = mask[:max_length] | |
| # input_ids_list.append(ids) | |
| # attention_mask_list.append(mask) | |
| # except Exception as e: | |
| # snippet = (a[:80] + "...") if a else "<empty>" | |
| # raise RuntimeError(f"Tokenizer fallback encode failed for example '{snippet}': {e}") | |
| # return {"input_ids": input_ids_list, "attention_mask": attention_mask_list} | |
| # --------------------------------------------------------------------- | |
| # Postprocess preds | |
| # --------------------------------------------------------------------- | |
| # def _postprocess_predictions(task: str, logits_np: np.ndarray, cfg_task: dict): | |
| # ttype = cfg_task["type"] | |
| # if logits_np is None or logits_np.size == 0: | |
| # return np.array([]) | |
| # if logits_np.ndim == 1: | |
| # if ttype == "classification": | |
| # return (logits_np > 0.5).astype(int) | |
| # return logits_np.astype(float) | |
| # if logits_np.ndim == 2 and logits_np.shape[1] == 1: | |
| # col = logits_np[:, 0] | |
| # if ttype == "classification": | |
| # return (col > 0.5).astype(int) | |
| # return col.astype(float) | |
| # if logits_np.ndim == 2: | |
| # if ttype == "classification": | |
| # return np.argmax(logits_np, axis=-1).astype(int) | |
| # preds = logits_np[:, 0].astype(float) if logits_np.shape[1] == 1 else logits_np.mean(axis=1).astype(float) | |
| # if task == "stsb": | |
| # preds = np.clip(preds, 0.0, 5.0) | |
| # return preds | |
| # return logits_np.ravel() | |
| # --------------------------------------------------------------------- | |
| # Model wrapping helper (robust) | |
| # --------------------------------------------------------------------- | |
| # def make_wrapped_model_if_needed(model, hidden_size: Optional[int], num_labels: int, force_num_labels: Optional[int] = None): | |
| # import torch.nn as nn | |
| # base_model = model | |
| # def _detect_head_dim(m): | |
| # try: | |
| # if hasattr(m, "classifier") and isinstance(getattr(m, "classifier"), nn.Linear): | |
| # return getattr(m, "classifier").out_features | |
| # if hasattr(m, "lm_head") and isinstance(getattr(m, "lm_head"), nn.Linear): | |
| # return getattr(m, "lm_head").out_features | |
| # if hasattr(m, "get_output_embeddings"): | |
| # out_emb = m.get_output_embeddings() | |
| # if out_emb is not None: | |
| # if isinstance(out_emb, nn.Embedding): | |
| # return out_emb.embedding_dim if hasattr(out_emb, "embedding_dim") else out_emb.num_embeddings | |
| # if isinstance(out_emb, nn.Linear): | |
| # return out_emb.out_features | |
| # except Exception: | |
| # pass | |
| # return None | |
| # if force_num_labels is None: | |
| # head_dim = _detect_head_dim(base_model) | |
| # if head_dim is not None and head_dim == num_labels: | |
| # return base_model, False | |
| # inferred_hidden = hidden_size | |
| # if inferred_hidden is None: | |
| # try: | |
| # cand = getattr(base_model, "config", None) | |
| # if cand is not None and hasattr(cand, "hidden_size"): | |
| # inferred_hidden = int(cand.hidden_size) | |
| # except Exception: | |
| # inferred_hidden = None | |
| # if inferred_hidden is None: | |
| # try: | |
| # un = unwrap_model(base_model) | |
| # sd = un.state_dict() | |
| # for k, v in sd.items(): | |
| # if re.search(r"embed|embedding|word_embeddings|token_embedding|embed_tokens", k, re.I): | |
| # if hasattr(v, "shape") and len(v.shape) == 2: | |
| # inferred_hidden = int(v.shape[1]) | |
| # break | |
| # if re.search(r"q_proj|k_proj|v_proj|o_proj|dense|fc|linear|proj", k, re.I): | |
| # if hasattr(v, "shape") and len(v.shape) == 2: | |
| # cand = max(v.shape) | |
| # if 1 < cand < 1_000_000: | |
| # inferred_hidden = int(cand) | |
| # break | |
| # except Exception: | |
| # inferred_hidden = None | |
| # if inferred_hidden is None: | |
| # raise RuntimeError( | |
| # "Cannot infer hidden_size for wrapped classifier head. " | |
| # "Please set `model.config.hidden_size` or pass `hidden_size` explicitly." | |
| # ) | |
| # class _WrappedModel(nn.Module): | |
| # def __init__(self, base, hidden_size, num_labels): | |
| # super().__init__() | |
| # self.base = base | |
| # self.classifier = nn.Linear(hidden_size, num_labels) | |
| # self.logits_projector = None | |
| # def forward(self, input_ids=None, attention_mask=None, labels=None, **kwargs): | |
| # try: | |
| # out = self.base(input_ids=input_ids, attention_mask=attention_mask, **kwargs) | |
| # except TypeError: | |
| # out = self.base(input_ids) | |
| # last_hidden = getattr(out, "last_hidden_state", None) | |
| # if last_hidden is not None: | |
| # pooled = last_hidden[:, 0, :] | |
| # logits = self.classifier(pooled) | |
| # return type("Out", (), {"logits": logits, "loss": None}) | |
| # if isinstance(out, (tuple, list)) and len(out) > 0: | |
| # ChatGPT can make mistakes. OpenAI doesn't use Duke University workspace data to train its models. |