| # # lmr/glue_benchmark_grid_full.py | |
| # """ | |
| # Grid-search GLUE + extra tasks runner (full single-file). | |
| # Features: | |
| # - LR sweep across bert_lr_candidates | |
| # - Random restarts for small/unstable tasks | |
| # - Save per-run checkpoints and run_meta.json | |
| # - Save all_runs.csv and best_overall per task | |
| # - Evaluate best model and compute errorbars for GLUE | |
| # - Supports EXTRA_TASKS (boolq/piqa/winogrande + hellaswag/openbookqa/arc) with MC/pair handling | |
| # """ | |
| # 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 | |
| # try: | |
| # from lmr.checkpointing import Checkpointing | |
| # from lmr.ddp import unwrap_model | |
| # except Exception: | |
| # # If these modules are not available, provide lightweight fallbacks to avoid import errors | |
| # Checkpointing = None | |
| # def unwrap_model(m): | |
| # return m | |
| # # --------------------------------------------------------------------- | |
| # # Tasks config | |
| # # --------------------------------------------------------------------- | |
| # 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"}, | |
| # } | |
| # # Extra tasks (BoolQ, PIQA, Winogrande, HellaSwag, OpenBookQA, ARC variants) | |
| # 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 tasks below | |
| # "hellaswag": { | |
| # "type": "multiple_choice", | |
| # "num_labels": 4, | |
| # "hf_path": "hellaswag", | |
| # "format": "mc", | |
| # }, | |
| # "openbookqa": { | |
| # "type": "multiple_choice", | |
| # "num_labels": 4, | |
| # "hf_path": "openbookqa", | |
| # "format": "mc", | |
| # }, | |
| # # AI2 ARC splits — align names with common usage | |
| # "arc_easy": { | |
| # "type": "multiple_choice", | |
| # "num_labels": 4, | |
| # "hf_path": "ai2_arc", | |
| # "hf_config": "ARC-Easy", | |
| # "format": "mc", | |
| # }, | |
| # "arc_challenge": { | |
| # "type": "multiple_choice", | |
| # "num_labels": 4, | |
| # "hf_path": "ai2_arc", | |
| # "hf_config": "ARC-Challenge", | |
| # "format": "mc", | |
| # }, | |
| # } | |
| # ALL_TASKS = {**GLUE_TASKS, **EXTRA_TASKS} | |
| # # Small/unstable tasks for extra random restarts | |
| # SMALL_TASKS_RANDOM_RESTARTS = {"cola", "mrpc", "rte", "stsb"} | |
| # SMALL_TASKS_RANDOM_RESTARTS_EXTRA = set({"openbookqa", "arc_easy", "arc_challenge","hellaswag"}) # adjust as desired | |
| # BERT_LR_CANDIDATES = [2e-5, 3e-5, 4e-5, 5e-5] | |
| # PREFERRED_METRIC_KEY = { | |
| # "cola": "matthews_correlation", | |
| # "sst2": "accuracy", | |
| # "mrpc": "accuracy", | |
| # "stsb": "pearson", | |
| # "qqp": "accuracy", | |
| # "mnli": "accuracy", | |
| # "qnli": "accuracy", | |
| # "rte": "accuracy", | |
| # "wnli": "accuracy", | |
| # "boolq": "accuracy", | |
| # "piqa": "accuracy", | |
| # "winogrande": "accuracy", | |
| # "hellaswag": "accuracy", | |
| # "openbookqa": "accuracy", | |
| # "arc_easy": "accuracy", | |
| # "arc_challenge": "accuracy", | |
| # } | |
| # # --------------------------------------------------------------------- | |
| # # Repro helpers | |
| # # --------------------------------------------------------------------- | |
| # def _set_all_seeds(seed: int): | |
| # random.seed(seed) | |
| # np.random.seed(seed) | |
| # torch.manual_seed(seed) | |
| # try: | |
| # torch.cuda.manual_seed_all(seed) | |
| # except Exception: | |
| # pass | |
| # try: | |
| # torch.backends.cudnn.deterministic = True | |
| # torch.backends.cudnn.benchmark = False | |
| # except Exception: | |
| # pass | |
| # 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) | |
| # 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-aware example field extraction (robust) | |
| # # --------------------------------------------------------------------- | |
| # 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[str]) where s2 can be None for single-sentence tasks. | |
| # """ | |
| # 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 to different tokenizer APIs) | |
| # # --------------------------------------------------------------------- | |
| # 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): | |
| # """ | |
| # Robust batch tokenization for a variety of tokenizer APIs. | |
| # - texts: list of (s1, s2) where s2 may be None. | |
| # - Try HF tokenizer(...) first, then various batch methods, then per-example fallback. | |
| # Returns dict with 'input_ids' (list of lists) and 'attention_mask'. | |
| # """ | |
| # 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)) | |
| # # 1) Try HF-like tokenizer(...) first | |
| # 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 | |
| # # 2) Try other batch-like methods | |
| # 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 | |
| # # 3) Fallback per-example | |
| # 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 to the right shapes/types (fixes metric mismatches) | |
| # # --------------------------------------------------------------------- | |
| # def _postprocess_predictions(task: str, logits_np: np.ndarray, cfg_task: dict): | |
| # """ | |
| # Take logits (N, C) or (N,) or (N,1) and produce preds array ready for evaluate.compute: | |
| # - classification -> 1D ints (class indices or binary 0/1) | |
| # - regression -> 1D floats (for stsb typically 0..5) | |
| # """ | |
| # ttype = cfg_task["type"] | |
| # num_labels = cfg_task["num_labels"] | |
| # if logits_np is None or logits_np.size == 0: | |
| # return np.array([]) | |
| # # If logits are shape (N, ) -> treat as single score per example (binary/regression) | |
| # if logits_np.ndim == 1: | |
| # if ttype == "classification": | |
| # preds = (logits_np > 0.5).astype(int) | |
| # else: | |
| # preds = logits_np.astype(float) | |
| # return preds | |
| # # If logits shape (N, 1) | |
| # if logits_np.ndim == 2 and logits_np.shape[1] == 1: | |
| # col = logits_np[:, 0] | |
| # if ttype == "classification": | |
| # preds = (col > 0.5).astype(int) | |
| # else: | |
| # preds = col.astype(float) | |
| # return preds | |
| # # If logits shape (N, C) | |
| # if logits_np.ndim == 2 and logits_np.shape[1] >= 1: | |
| # if ttype == "classification": | |
| # preds = np.argmax(logits_np, axis=-1).astype(int) | |
| # return preds | |
| # else: | |
| # if logits_np.shape[1] == 1: | |
| # preds = logits_np[:, 0].astype(float) | |
| # else: | |
| # preds = 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): | |
| # """ | |
| # Robust wrapper factory with resilient hidden_size inference. | |
| # Returns (model_or_wrapper, wrapped_flag) | |
| # """ | |
| # import torch.nn as nn | |
| # base_model = model | |
| # def _detect_head_dim(m): | |
| # 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 | |
| # 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: | |
| # cand = getattr(base_model, "config", None) | |
| # if cand is not None and hasattr(cand, "hidden_size"): | |
| # try: | |
| # inferred_hidden = int(cand.hidden_size) | |
| # except Exception: | |
| # inferred_hidden = None | |
| # if inferred_hidden is None: | |
| # 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 | |
| # 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): | |
| # out = None | |
| # 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: | |
| # cand = out[0] | |
| # if torch.is_tensor(cand): | |
| # if cand.ndim == 3: | |
| # pooled = cand[:, 0, :] | |
| # logits = self.classifier(pooled) | |
| # return type("Out", (), {"logits": logits, "loss": None}) | |
| # if cand.ndim == 2 and cand.shape[1] == num_labels: | |
| # return type("Out", (), {"logits": cand, "loss": None}) | |
| # logits = getattr(out, "logits", None) | |
| # if logits is not None: | |
| # if logits.ndim == 2 and logits.shape[1] == num_labels: | |
| # return type("Out", (), {"logits": logits, "loss": getattr(out, "loss", None)}) | |
| # exist_dim = logits.shape[1] | |
| # if self.logits_projector is None or self.logits_projector.weight.shape[1] != exist_dim: | |
| # self.logits_projector = nn.Linear(exist_dim, num_labels).to(logits.device) | |
| # projected = self.logits_projector(logits) | |
| # return type("Out", (), {"logits": projected, "loss": getattr(out, "loss", None)}) | |
| # hidden_states = getattr(out, "hidden_states", None) | |
| # if hidden_states is not None: | |
| # last_hidden = hidden_states[-1] if isinstance(hidden_states, (list, tuple)) else hidden_states | |
| # if torch.is_tensor(last_hidden) and last_hidden.ndim == 3: | |
| # pooled = last_hidden[:, 0, :] | |
| # logits = self.classifier(pooled) | |
| # return type("Out", (), {"logits": logits, "loss": None}) | |
| # raise RuntimeError("Wrapped base model did not return recognizable hidden states or logits") | |
| # return _WrappedModel(base_model, inferred_hidden, num_labels), True | |
| # # --------------------------------------------------------------------- | |
| # # Tokenize HF split to tensors (for finetune) | |
| # # --------------------------------------------------------------------- | |
| # def _tokenize_hf_split_to_tensors(task: str, tokenizer, raw_split, cfg_task, max_length=128, batch_tokenize_size=512): | |
| # texts = [] | |
| # labels = [] | |
| # empty_s1 = 0 | |
| # empty_s2 = 0 | |
| # for ex in raw_split: | |
| # s1, s2 = _get_text_pair_from_example(task, ex) | |
| # texts.append((s1, s2)) | |
| # labels.append(ex.get("label") if "label" in ex else -100) | |
| # if not s1 or (isinstance(s1, str) and s1.strip() == ""): | |
| # empty_s1 += 1 | |
| # if s2 is not None and (not s2 or (isinstance(s2, str) and s2.strip() == "")): | |
| # empty_s2 += 1 | |
| # total = len(texts) | |
| # print( | |
| # f"[tokenize] task={task} samples={total} empty_s1={empty_s1} empty_s2={empty_s2} " | |
| # f"({(empty_s1/total if total>0 else 0):.2%}, {(empty_s2/total if total>0 else 0):.2%})" | |
| # ) | |
| # input_ids_all = [] | |
| # attention_all = [] | |
| # for i in range(0, len(texts), batch_tokenize_size): | |
| # enc = _batch_tokenize(tokenizer, texts[i:i+batch_tokenize_size], max_length=max_length) | |
| # ids = enc.get("input_ids") | |
| # masks = enc.get("attention_mask") or enc.get("mask") or enc.get("masks") | |
| # if isinstance(ids, torch.Tensor): | |
| # ids = ids.tolist() | |
| # if isinstance(masks, torch.Tensor): | |
| # masks = masks.tolist() | |
| # input_ids_all.extend(ids) | |
| # attention_all.extend(masks) | |
| # pad_id = getattr(tokenizer, "pad_token_id", None) | |
| # if pad_id is None: | |
| # try: | |
| # pad_id = tokenizer.token_to_id("[PAD]") | |
| # except Exception: | |
| # pad_id = 0 | |
| # input_ids_t, attention_mask_t = _pad_and_tensorize(input_ids_all, attention_all, pad_id) | |
| # labels_t = torch.tensor(labels, dtype=torch.long if cfg_task["type"] == "classification" else torch.float) | |
| # return input_ids_t, attention_mask_t, labels_t | |
| # # --------------------------------------------------------------------- | |
| # # Generic multiple-choice extractor & tokenizer | |
| # # --------------------------------------------------------------------- | |
| # # Improved unwrapping + robust context assembly and MC example extractor | |
| # def _maybe_unwrap(v): | |
| # """ | |
| # Improved unwrap: | |
| # - numpy / torch scalars -> Python scalars | |
| # - single-element lists/tuples -> unwrap | |
| # - dicts are left as-is (they may represent structured choices) | |
| # - numeric strings -> int when appropriate (helps labels like '3') | |
| # - lists of strings left as-is (caller may join) | |
| # """ | |
| # try: | |
| # import numpy as _np | |
| # import torch as _torch | |
| # except Exception: | |
| # _np = None | |
| # _torch = None | |
| # if v is None: | |
| # return None | |
| # # torch tensor scalar or 0-d array | |
| # if _torch is not None and isinstance(v, _torch.Tensor): | |
| # if v.ndim == 0: | |
| # return v.item() | |
| # if v.numel() == 1: | |
| # return v.view(-1).tolist()[0] | |
| # return v | |
| # # numpy scalar / array | |
| # if _np is not None and isinstance(v, _np.ndarray): | |
| # if v.shape == () or v.size == 1: | |
| # return v.flatten().tolist()[0] | |
| # return v.tolist() | |
| # # dict likely meaningful (choices dict) -> keep as-is | |
| # if isinstance(v, dict): | |
| # return v | |
| # # list/tuple with single element -> unwrap to element | |
| # if isinstance(v, (list, tuple)) and len(v) == 1: | |
| # return _maybe_unwrap(v[0]) | |
| # # string that represents an integer -> convert to int (helps labels like '3') | |
| # if isinstance(v, str): | |
| # s = v.strip() | |
| # if s.isdigit(): | |
| # try: | |
| # return int(s) | |
| # except Exception: | |
| # pass | |
| # return v | |
| # return v | |
| # def _assemble_context_from_example(ex: dict): | |
| # """ | |
| # Build a single string context from many possible fields. | |
| # Priority / heuristics: | |
| # 1. If 'ctx' or 'context' present and non-empty -> use it. | |
| # 2. If 'ctx_a' and 'ctx_b' (or 'ctxA'/'ctxB') present -> join them. | |
| # 3. If 'premise' and 'hypothesis' present -> join them (suitable for MNLI-like). | |
| # 4. Use question-oriented fields if that is the best we can do: question_stem, question, stem. | |
| # 5. Fallback: join a selection of textual fields in a sensible order. | |
| # Converts lists/tuples of strings to joined text. | |
| # """ | |
| # def _to_text(x): | |
| # if x is None: | |
| # return "" | |
| # if isinstance(x, (list, tuple)): | |
| # # attempt to join list of strings or dicts | |
| # parts = [] | |
| # for it in x: | |
| # if isinstance(it, dict): | |
| # txt = it.get("text") or it.get("choice") or it.get("label") or str(it) | |
| # parts.append(str(txt)) | |
| # else: | |
| # parts.append(str(it)) | |
| # return " ".join([p.strip() for p in parts if p is not None and str(p).strip() != ""]) | |
| # if isinstance(x, dict): | |
| # # pick likely textual fields | |
| # for k in ("text", "content", "question", "sentence", "passage", "context"): | |
| # if k in x and x[k]: | |
| # return _to_text(x[k]) | |
| # return str(x) | |
| # return str(x) | |
| # # common direct fields | |
| # keys = {k.lower(): v for k, v in ex.items()} | |
| # # 1) explicit ctx / context | |
| # for k in ("ctx", "context"): | |
| # if k in keys and keys[k]: | |
| # return _to_text(keys[k]).strip() | |
| # # 2) ctx_a + ctx_b variants | |
| # a_keys = ("ctx_a", "ctxA", "context_a", "contextA", "ctxa") | |
| # b_keys = ("ctx_b", "ctxB", "context_b", "contextB", "ctxb") | |
| # a_val = None | |
| # b_val = None | |
| # for ka in a_keys: | |
| # if ka in keys and keys[ka]: | |
| # a_val = keys[ka] | |
| # break | |
| # for kb in b_keys: | |
| # if kb in keys and keys[kb]: | |
| # b_val = keys[kb] | |
| # break | |
| # if a_val is not None or b_val is not None: | |
| # parts = [] | |
| # if a_val is not None: | |
| # parts.append(_to_text(a_val)) | |
| # if b_val is not None: | |
| # parts.append(_to_text(b_val)) | |
| # return " ".join([p.strip() for p in parts if p and p.strip() != ""]).strip() | |
| # # 3) premise + hypothesis | |
| # if "premise" in keys or "hypothesis" in keys: | |
| # p = keys.get("premise") | |
| # h = keys.get("hypothesis") | |
| # parts = [] | |
| # if p: | |
| # parts.append(_to_text(p)) | |
| # if h: | |
| # parts.append(_to_text(h)) | |
| # return " ".join([p.strip() for p in parts if p and p.strip() != ""]).strip() | |
| # # 4) question stem / question / stem | |
| # for k in ("question_stem", "questionStem", "question", "stem", "prompt", "goal"): | |
| # if k.lower() in keys and keys[k.lower()]: | |
| # return _to_text(keys[k.lower()]).strip() | |
| # # 5) passage/article/story/sentence | |
| # for k in ("passage", "article", "story", "sentence", "paragraph"): | |
| # if k in keys and keys[k]: | |
| # return _to_text(keys[k]).strip() | |
| # # 6) try to build a concat from any textual fields in a sensible order | |
| # fallback_fields = [ | |
| # "context", "question", "passage", "article", "story", "sentence", | |
| # "prompt", "goal", "stem", "query", "description", "narration" | |
| # ] | |
| # parts = [] | |
| # for f in fallback_fields: | |
| # if f in keys and keys[f]: | |
| # parts.append(_to_text(keys[f])) | |
| # combined = " ".join([p.strip() for p in parts if p and p.strip() != ""]).strip() | |
| # if combined: | |
| # return combined | |
| # # final fallback: try first non-empty string-ish field | |
| # for k, v in ex.items(): | |
| # if isinstance(v, str) and v.strip(): | |
| # return v.strip() | |
| # if isinstance(v, (list, tuple)) and len(v) > 0 and all(isinstance(x, str) for x in v): | |
| # return " ".join(v).strip() | |
| # return "" | |
| # def _extract_mc_example(task: str, ex: dict): | |
| # """ | |
| # Robust extractor for multiple-choice examples across HF dataset schemas. | |
| # Returns (context:str, options_list:List[str], label_index_or_None) | |
| # Improvements over earlier versions: | |
| # - Uses _maybe_unwrap on fields | |
| # - Assembles context robustly via _assemble_context_from_example (handles ctx_a+ctx_b, premise+hypothesis, etc.) | |
| # - Accepts choices as: | |
| # * dict {'text': [...], 'label': [...]} (OpenBookQA / ARC) | |
| # * list of dicts or strings | |
| # * 'endings' list (HellaSwag) | |
| # * explicit fields choice1/option1 etc. | |
| # - Normalizes label forms: 'A'..'D' -> 0..3, '3' -> int(3), torch/numpy scalars handled | |
| # """ | |
| # def _map_label_to_int(label, opts): | |
| # if label is None: | |
| # return None | |
| # # unwrap containers | |
| # if isinstance(label, (list, tuple)) and len(label) > 0: | |
| # label = label[0] | |
| # try: | |
| # import numpy as _np | |
| # import torch as _torch | |
| # except Exception: | |
| # _np = None | |
| # _torch = None | |
| # if _torch is not None and isinstance(label, _torch.Tensor): | |
| # try: | |
| # return int(label.item()) | |
| # except Exception: | |
| # pass | |
| # if _np is not None and isinstance(label, _np.ndarray): | |
| # if label.size == 1: | |
| # return int(label.flatten().tolist()[0]) | |
| # if isinstance(label, str): | |
| # s = label.strip() | |
| # if len(s) == 1 and s.isalpha(): | |
| # return ord(s.upper()) - ord("A") | |
| # if s.isdigit(): | |
| # try: | |
| # return int(s) | |
| # except Exception: | |
| # pass | |
| # # if label equals one of option texts, return that index | |
| # if opts: | |
| # for i, o in enumerate(opts): | |
| # if isinstance(o, str) and s == o: | |
| # return i | |
| # return None | |
| # if isinstance(label, (int, np.integer)): | |
| # return int(label) | |
| # return None | |
| # # shallow normalized copy | |
| # ex_norm = {} | |
| # for k, v in ex.items(): | |
| # try: | |
| # ex_norm[k] = _maybe_unwrap(v) | |
| # except Exception: | |
| # ex_norm[k] = v | |
| # ex = ex_norm | |
| # # assemble context early | |
| # ctx = _assemble_context_from_example(ex) | |
| # # 1) OpenBookQA / ARC-style: 'choices' is dict with 'text' list | |
| # if "choices" in ex and isinstance(ex["choices"], dict): | |
| # chd = ex["choices"] | |
| # texts = chd.get("text") or chd.get("texts") or chd.get("choice") or None | |
| # if isinstance(texts, (list, tuple)) and len(texts) > 0: | |
| # opts = [str(x) for x in texts] | |
| # lab = ex.get("answerKey") or ex.get("answer") or ex.get("correctAnswer") or ex.get("label") | |
| # lab_idx = _map_label_to_int(lab, opts) | |
| # return ctx, opts, lab_idx | |
| # # 2) HellaSwag style 'endings' | |
| # if "endings" in ex and isinstance(ex["endings"], list) and len(ex["endings"]) > 0: | |
| # opts = [str(x) for x in ex["endings"]] | |
| # lab = ex.get("label") or ex.get("answerKey") or ex.get("answer") | |
| # lab_idx = _map_label_to_int(lab, opts) | |
| # return ctx, opts, lab_idx | |
| # # 3) 'choices' as list (strings or dicts) | |
| # if "choices" in ex and ex["choices"] is not None: | |
| # ch = ex["choices"] | |
| # if isinstance(ch, list) and len(ch) > 0: | |
| # opts = [] | |
| # for c in ch: | |
| # if isinstance(c, dict): | |
| # opts.append(c.get("text") or c.get("choice") or c.get("label") or str(c)) | |
| # else: | |
| # opts.append(str(c)) | |
| # lab = ex.get("answerKey") or ex.get("answer") or ex.get("label") or ex.get("correct") | |
| # lab_idx = _map_label_to_int(lab, opts) | |
| # return ctx, opts, lab_idx | |
| # # 4) explicit option fields like 'choice1','choice2' or 'option1'.. | |
| # opts = [] | |
| # for prefix in ("choice", "option", "ending", "answer"): | |
| # i = 1 | |
| # found = False | |
| # while True: | |
| # key = f"{prefix}{i}" | |
| # if key in ex: | |
| # val = _maybe_unwrap(ex.get(key)) | |
| # opts.append(str(val)) | |
| # found = True | |
| # i += 1 | |
| # else: | |
| # break | |
| # if found: | |
| # lab = ex.get("label") or ex.get("answerKey") or ex.get("answer") | |
| # lab_idx = _map_label_to_int(lab, opts) | |
| # return ctx, opts, lab_idx | |
| # # 5) fallback: look for list-valued fields that look like options | |
| # for k, v in ex.items(): | |
| # if isinstance(v, (list, tuple)) and 2 <= len(v) <= 10 and all(isinstance(x, (str, dict)) for x in v): | |
| # opts = [x.get("text") if isinstance(x, dict) and x.get("text") else str(x) for x in v] | |
| # lab = ex.get("answer") or ex.get("label") or ex.get("answerKey") | |
| # lab_idx = _map_label_to_int(lab, opts) | |
| # return ctx, opts, lab_idx | |
| # # 6) collect candidate option-fields heuristically | |
| # candidate_opts = [] | |
| # for k in sorted(ex.keys()): | |
| # if any(tok in k.lower() for tok in ("option", "choice", "ending", "answer", "alt", "sol", "choices")): | |
| # val = _maybe_unwrap(ex.get(k)) | |
| # candidate_opts.append(str(val)) | |
| # if candidate_opts: | |
| # lab = ex.get("label") or ex.get("answer") or ex.get("answerKey") | |
| # lab_idx = _map_label_to_int(lab, candidate_opts) | |
| # return ctx, candidate_opts, lab_idx | |
| # # last resort: no options discovered | |
| # lab = ex.get("label") or ex.get("answerKey") or ex.get("answer") | |
| # lab_idx = _map_label_to_int(lab, []) | |
| # return ctx, [], lab_idx | |
| # # --------------------------- | |
| # # 新增:detect_num_choices | |
| # # --------------------------- | |
| # def detect_num_choices(raw_split, task: str, max_samples: int = 200): | |
| # max_s = min(len(raw_split), max_samples) | |
| # # 强制拿出前 max_s 个示例为「标量形式」:select 确保每个示例是 dict of scalars 而不是子 Dataset | |
| # subset = raw_split.select(range(max_s)) if hasattr(raw_split, "select") else raw_split[:max_s] | |
| # counts = {} | |
| # total = 0 | |
| # kept = 0 | |
| # bad_label = 0 | |
| # for ex in subset: | |
| # total += 1 | |
| # try: | |
| # ctx, opts, lab = _extract_mc_example(task, ex) | |
| # except Exception: | |
| # counts.setdefault("no_opts", 0) | |
| # counts["no_opts"] += 1 | |
| # continue | |
| # if not opts: | |
| # counts.setdefault("no_opts", 0) | |
| # counts["no_opts"] += 1 | |
| # else: | |
| # kept += 1 | |
| # counts.setdefault(len(opts), 0) | |
| # counts[len(opts)] += 1 | |
| # if lab is None or (isinstance(lab, int) and lab < 0): | |
| # bad_label += 1 | |
| # numeric = {k: v for k, v in counts.items() if isinstance(k, int)} | |
| # detected = None | |
| # if numeric: | |
| # detected = max(numeric.items(), key=lambda x: x[1])[0] | |
| # summary = { | |
| # "task": task, | |
| # "total_scanned": total, | |
| # "kept": kept, | |
| # "counts": counts, | |
| # "detected_num_choices": detected, | |
| # "skipped_bad_label": bad_label, | |
| # } | |
| # print(f"[detect_num_choices][{task}] scanned={total} kept={kept} detected={detected} counts={counts} bad_label={bad_label}") | |
| # return summary | |
| # # --------------------------- | |
| # # 更新:_tokenize_generic_mc_split_to_tensors | |
| # # --------------------------- | |
| # def _tokenize_generic_mc_split_to_tensors(task: str, tokenizer, raw_split, max_length=128, batch_tokenize_size=256, expect_num_choices: Optional[int] = None): | |
| # """ | |
| # Generic MC tokenizer with optional expect_num_choices enforcement. | |
| # Returns (input_ids_t (N,C,L), attention_t (N,C,L), labels_t (N,)), and prints debug counts. | |
| # If expect_num_choices is provided, examples whose number of options != expect_num_choices are considered 'inconsistent' and skipped. | |
| # This version does not swallow exceptions: tokenizer errors and unexpected shapes will raise. | |
| # """ | |
| # contexts = [] | |
| # options = [] | |
| # labels = [] | |
| # total = 0 | |
| # skipped_no_opts = 0 | |
| # skipped_inconsistent = 0 | |
| # skipped_bad_label = 0 | |
| # kept = 0 | |
| # for ex in raw_split: | |
| # ctx, opts, lab = _extract_mc_example(task, ex) | |
| # total += 1 | |
| # # print(task) | |
| # # print(ex) | |
| # # print(ctx) | |
| # # print(opts) | |
| # # print(lab) | |
| # # import pdb | |
| # # pdb.set_trace() | |
| # # print(ex) | |
| # # print(ctx) | |
| # # print(opts) | |
| # # print(lab) | |
| # if not opts: | |
| # skipped_no_opts += 1 | |
| # continue | |
| # if expect_num_choices is not None and len(opts) != expect_num_choices: | |
| # skipped_inconsistent += 1 | |
| # continue | |
| # if lab is None: | |
| # skipped_bad_label += 1 | |
| # continue | |
| # try: | |
| # lab_i = int(lab) | |
| # except Exception: | |
| # skipped_bad_label += 1 | |
| # continue | |
| # if lab_i < 0 or lab_i >= len(opts): | |
| # skipped_bad_label += 1 | |
| # continue | |
| # contexts.append(ctx if ctx is not None else "") | |
| # options.append(opts) | |
| # labels.append(lab_i) | |
| # kept += 1 | |
| # print(f"[tokenize_generic_mc][{task}] total={total} kept={kept} skipped_no_opts={skipped_no_opts} skipped_inconsistent={skipped_inconsistent} skipped_bad_label={skipped_bad_label}") | |
| # if len(contexts) == 0: | |
| # return torch.zeros((0, 1, 1), dtype=torch.long), torch.zeros((0, 1, 1), dtype=torch.long), torch.tensor([], dtype=torch.long) | |
| # pad_id = getattr(tokenizer, "pad_token_id", None) | |
| # if pad_id is None: | |
| # try: | |
| # pad_id = tokenizer.token_to_id("[PAD]") | |
| # except Exception: | |
| # pad_id = 0 | |
| # num_choices = len(options[0]) | |
| # # Build flat pairs for batch tokenization and assert tokenizer returns expected length | |
| # input_ids_rows = [] | |
| # attention_rows = [] | |
| # for i in range(0, len(contexts), batch_tokenize_size): | |
| # chunk_ctx = contexts[i:i+batch_tokenize_size] | |
| # chunk_opts = options[i:i+batch_tokenize_size] | |
| # flat_pairs = [] | |
| # for c, opts in zip(chunk_ctx, chunk_opts): | |
| # for o in opts: | |
| # flat_pairs.append((c, o)) | |
| # enc = tokenizer(flat_pairs, truncation=True, padding=False, max_length=max_length) | |
| # ids_flat = enc.get("input_ids") | |
| # masks_flat = enc.get("attention_mask") or enc.get("mask") or enc.get("masks") | |
| # if isinstance(ids_flat, torch.Tensor): | |
| # ids_flat = ids_flat.tolist() | |
| # if isinstance(masks_flat, torch.Tensor): | |
| # masks_flat = masks_flat.tolist() | |
| # expected = len(chunk_ctx) * num_choices | |
| # if len(ids_flat) != expected: | |
| # # raise explicit error so user sees where tokenizer mismatched | |
| # raise RuntimeError(f"[tokenize_generic_mc][{task}] tokenizer returned {len(ids_flat)} items but expected {expected} (i={i}). " | |
| # f"First flat_pairs sample: {flat_pairs[0] if len(flat_pairs)>0 else None}") | |
| # per_example = [] | |
| # per_mask_example = [] | |
| # idx = 0 | |
| # for _ in range(len(chunk_ctx)): | |
| # row = [] | |
| # row_mask = [] | |
| # for _ in range(num_choices): | |
| # row.append(ids_flat[idx]) | |
| # row_mask.append(masks_flat[idx]) | |
| # idx += 1 | |
| # per_example.append(row) | |
| # per_mask_example.append(row_mask) | |
| # input_ids_rows.extend(per_example) | |
| # attention_rows.extend(per_mask_example) | |
| # max_len = max(len(seq) for row in input_ids_rows for seq in row) if input_ids_rows else 1 | |
| # input_ids_padded = [ | |
| # [ seq + [pad_id] * (max_len - len(seq)) for seq in row ] | |
| # for row in input_ids_rows | |
| # ] | |
| # attention_padded = [ | |
| # [ mask + [0] * (max_len - len(mask)) for mask in row ] | |
| # for row in attention_rows | |
| # ] | |
| # input_ids_t = torch.tensor(input_ids_padded, dtype=torch.long) # (N, C, L) | |
| # attention_t = torch.tensor(attention_padded, dtype=torch.long) | |
| # labels_t = torch.tensor(labels, dtype=torch.long) | |
| # return input_ids_t, attention_t, labels_t | |
| # # --------------------------- | |
| # # train_full_finetune_extra(替换版:用替换 classifier 输出 1 的方法) | |
| # # --------------------------- | |
| # def train_full_finetune_extra(task: str, tokenizer, model, raw_train, raw_val, | |
| # device: str = "cuda", epochs: int = 3, batch_size: int = 16, | |
| # lr: float = 2e-5, weight_decay: float = 0.01, warmup_steps: int = 100, | |
| # max_length: int = 128, grad_accum_steps: int = 1, out_checkpoint_dir: Optional[str] = None, | |
| # expect_num_choices: Optional[int] = None): | |
| # """ | |
| # Fine-tune for EXTRA_TASKS with replacing the model's classification head to output 1 scalar: | |
| # - Replace model.classifier / model.lm_head / model.score / similar with Linear(hidden, 1) | |
| # - For MC tasks: flatten (B, C, L) -> (B*C, L), forward, obtain (B*C,1) -> reshape (B, C) -> CE loss | |
| # - Ensures new head parameters are created before optimizer so they are optimized. | |
| # This variant surfaces errors (no silent swallowing). | |
| # """ | |
| # import torch.nn as nn | |
| # cfg = EXTRA_TASKS[task] | |
| # device = torch.device(device if torch.cuda.is_available() else "cpu") | |
| # model.to(device) | |
| # is_mc = cfg["format"] == "mc" | |
| # # --------------------------- | |
| # # Tokenize splits | |
| # # --------------------------- | |
| # if is_mc: | |
| # train_ids, train_mask, train_labels = _tokenize_generic_mc_split_to_tensors( | |
| # task, tokenizer, raw_train, max_length=max_length, batch_tokenize_size=256, expect_num_choices=expect_num_choices | |
| # ) | |
| # val_ids, val_mask, val_labels = _tokenize_generic_mc_split_to_tensors( | |
| # task, tokenizer, raw_val, max_length=max_length, batch_tokenize_size=256, expect_num_choices=expect_num_choices | |
| # ) | |
| # if train_ids.ndim != 3: | |
| # raise RuntimeError(f"[ExtraTrain][{task}] train_ids expected 3 dims (N,C,L) got {train_ids.ndim}") | |
| # if val_ids.ndim != 3 and len(val_ids) > 0: | |
| # raise RuntimeError(f"[ExtraTrain][{task}] val_ids expected 3 dims (N,C,L) got {val_ids.ndim}") | |
| # train_ds = TensorDataset(train_ids, train_mask, train_labels) | |
| # val_ds = TensorDataset(val_ids, val_mask, val_labels) | |
| # else: | |
| # train_ids, train_mask, train_labels = _tokenize_hf_split_to_tensors(task, tokenizer, raw_train, cfg, max_length=max_length) | |
| # val_ids, val_mask, val_labels = _tokenize_hf_split_to_tensors(task, tokenizer, raw_val, cfg, max_length=max_length) | |
| # train_ds = TensorDataset(train_ids, train_mask, train_labels) | |
| # val_ds = TensorDataset(val_ids, val_mask, val_labels) | |
| # if len(train_ds) == 0: | |
| # raise RuntimeError(f"No training samples after tokenization for task={task}; aborting.") | |
| # train_loader = DataLoader(train_ds, batch_size=batch_size, shuffle=True, pin_memory=True) | |
| # val_loader = DataLoader(val_ds, batch_size=max(64, batch_size), shuffle=False, pin_memory=True) | |
| # # --------------------------- | |
| # # Replace model head to output scalar per example (for MC we'll apply it to each choice) | |
| # # --------------------------- | |
| # def _infer_hidden_size_from_model(m): | |
| # # try config.hidden_size first | |
| # hidden = None | |
| # try: | |
| # if hasattr(m, "config") and hasattr(m.config, "hidden_size"): | |
| # hidden = int(m.config.hidden_size) | |
| # except Exception: | |
| # hidden = None | |
| # if hidden is not None: | |
| # return hidden | |
| # # try to inspect state_dict looking for embedding/proj dimensions | |
| # try: | |
| # un = unwrap_model(m) | |
| # 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: | |
| # return int(v.shape[1]) | |
| # 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: | |
| # return int(cand) | |
| # except Exception: | |
| # pass | |
| # return None | |
| # # Only replace head for MC and for pair tasks we keep existing pipeline (but we still may want to ensure classifier shape) | |
| # if is_mc: | |
| # # detect suitable hidden size | |
| # hidden_size = _infer_hidden_size_from_model(model) | |
| # if hidden_size is None: | |
| # raise RuntimeError("Cannot infer hidden size from model; please set model.config.hidden_size or pass hidden_size explicitly.") | |
| # # Candidate head attribute names to replace | |
| # head_attrs = ["classifier", "lm_head", "score", "classifier_head", "head"] | |
| # replaced = False | |
| # for attr in head_attrs: | |
| # if hasattr(model, attr): | |
| # try: | |
| # old = getattr(model, attr) | |
| # # create new linear head that maps hidden -> 1 | |
| # new_head = nn.Linear(hidden_size, 1).to(device) | |
| # # init reasonably | |
| # try: | |
| # nn.init.xavier_uniform_(new_head.weight) | |
| # if new_head.bias is not None: | |
| # nn.init.zeros_(new_head.bias) | |
| # except Exception: | |
| # pass | |
| # setattr(model, attr, new_head) | |
| # print(f"[ExtraTrain][{task}] Replaced model.{attr} with Linear({hidden_size},1)") | |
| # replaced = True | |
| # break | |
| # except Exception: | |
| # # ignore and try next | |
| # pass | |
| # if not replaced: | |
| # # fallback: attach as attribute _mc_choice_head | |
| # model._mc_choice_head = nn.Linear(hidden_size, 1).to(device) | |
| # try: | |
| # nn.init.xavier_uniform_(model._mc_choice_head.weight) | |
| # if model._mc_choice_head.bias is not None: | |
| # nn.init.zeros_(model._mc_choice_head.bias) | |
| # except Exception: | |
| # pass | |
| # print(f"[ExtraTrain][{task}] Attached model._mc_choice_head = Linear({hidden_size},1)") | |
| # # --------------------------- | |
| # # Create optimizer AFTER head replacement so head params are included | |
| # # --------------------------- | |
| # optimizer = torch.optim.AdamW(model.parameters(), lr=lr, weight_decay=weight_decay) | |
| # total_steps = max(1, (len(train_loader) // max(1, grad_accum_steps)) * epochs) | |
| # from transformers import get_cosine_schedule_with_warmup | |
| # scheduler = get_cosine_schedule_with_warmup(optimizer, num_warmup_steps=warmup_steps, num_training_steps=total_steps) | |
| # ce_loss_fn = torch.nn.CrossEntropyLoss() | |
| # mse_loss_fn = torch.nn.MSELoss() | |
| # model.train() | |
| # global_step = 0 | |
| # final_metric_res = {} | |
| # expected_num_labels = cfg["num_labels"] | |
| # # training loop | |
| # for epoch in range(epochs): | |
| # running_loss = 0.0 | |
| # for step, batch in enumerate(tqdm(train_loader, desc=f"[ExtraTrain] {task} epoch {epoch+1}")): | |
| # if is_mc: | |
| # ids_b, mask_b, labs_b = batch # ids_b: (B, C, L) | |
| # ids_b = ids_b.to(device); mask_b = mask_b.to(device); labs_b = labs_b.to(device) | |
| # if labs_b.dim() > 1: | |
| # labs_b = labs_b.view(-1) | |
| # B = ids_b.size(0) | |
| # if labs_b.numel() != B: | |
| # raise RuntimeError(f"[ExtraTrain][FATAL] label count ({labs_b.numel()}) != batch size ({B}) at step {step}; aborting.") | |
| # # flatten (B,C,L) -> (B*C, L) | |
| # Bf, Cf, Lf = ids_b.shape | |
| # flat_ids = ids_b.view(Bf * Cf, Lf).to(device) | |
| # flat_mask = mask_b.view(Bf * Cf, Lf).to(device) if mask_b is not None else None | |
| # # forward | |
| # out_flat = None | |
| # try: | |
| # if flat_mask is not None: | |
| # out_flat = model(input_ids=flat_ids, attention_mask=flat_mask, labels=None) | |
| # else: | |
| # out_flat = model(input_ids=flat_ids, labels=None) | |
| # except TypeError: | |
| # # Some models accept single positional tensor | |
| # out_flat = model(flat_ids) | |
| # # get a pooled representation or logits from out_flat | |
| # flat_logits = getattr(out_flat, "logits", None) | |
| # pooled = None | |
| # if flat_logits is None: | |
| # # try pooler_output or last_hidden_state | |
| # pooled = getattr(out_flat, "pooler_output", None) | |
| # if pooled is None: | |
| # lh = getattr(out_flat, "last_hidden_state", None) | |
| # if lh is not None: | |
| # pooled = lh[:, 0, :] | |
| # else: | |
| # # If model already returns logits of shape (B*C, D) or (B*C,1), we can still feed them to head or reduce | |
| # pass | |
| # # compute scalar per flat example using the replaced head | |
| # # Case 1: model already returned logits as scalar (B*C,1) or (B*C,) | |
| # scalar_flat = None | |
| # if flat_logits is not None: | |
| # # flat_logits could be (B*C,) or (B*C,1) or (B*C,D) | |
| # if flat_logits.ndim == 1: | |
| # scalar_flat = flat_logits.view(-1, 1) | |
| # elif flat_logits.ndim == 2 and flat_logits.shape[1] == 1: | |
| # scalar_flat = flat_logits.view(-1, 1) | |
| # elif flat_logits.ndim == 2: | |
| # # project via the replaced head (we expect a head that maps hidden->1, so treat flat_logits as features) | |
| # # If we replaced model.classifier, it's likely expecting hidden vectors, but we have flat_logits features: | |
| # # attempt to use model._mc_choice_head if present; else take mean over dims then linear | |
| # feat = flat_logits | |
| # if hasattr(model, "_mc_choice_head"): | |
| # # ensure dimensions align: if feat dim matches head.in_features, use it | |
| # try: | |
| # in_f = model._mc_choice_head.in_features | |
| # if feat.shape[1] == in_f: | |
| # scalar_flat = model._mc_choice_head(feat) | |
| # else: | |
| # # reduce feat to hidden via mean | |
| # reduced = feat.mean(dim=1) | |
| # scalar_flat = model._mc_choice_head(reduced.unsqueeze(1) if len(reduced.shape)==1 else reduced) | |
| # except Exception: | |
| # reduced = feat.mean(dim=1) | |
| # scalar_flat = model._mc_choice_head(reduced.unsqueeze(1) if len(reduced.shape)==1 else reduced) | |
| # else: | |
| # # fallback: mean over feature dims to scalar | |
| # scalar_flat = feat.mean(dim=1, keepdim=True) | |
| # elif flat_logits.ndim == 3: | |
| # # reduce seq dim and feature dim to scalar | |
| # scalar_flat = flat_logits.mean(dim=tuple(range(1, flat_logits.ndim))).view(-1, 1) | |
| # else: | |
| # scalar_flat = flat_logits.reshape(flat_logits.size(0), -1).mean(dim=1, keepdim=True) | |
| # elif pooled is not None: | |
| # # pooled is (B*C, hidden), apply the replaced head | |
| # # find which head attribute we replaced | |
| # head_found = None | |
| # for attr in ("classifier", "lm_head", "score", "classifier_head", "head", "_mc_choice_head"): | |
| # if hasattr(model, attr): | |
| # head_found = getattr(model, attr) | |
| # break | |
| # if head_found is None: | |
| # raise RuntimeError("[ExtraTrain] no replacement head found on model to project pooled -> scalar") | |
| # # head_found may expect input dim hidden_size | |
| # try: | |
| # scalar_flat = head_found(pooled) | |
| # except Exception as e: | |
| # # if mismatch, try a linear mapping by reducing pooled | |
| # if pooled.ndim == 2: | |
| # # If head expects 1-d input, ensure shape matches | |
| # try: | |
| # scalar_flat = head_found(pooled) | |
| # except Exception: | |
| # scalar_flat = pooled.mean(dim=1, keepdim=True) | |
| # else: | |
| # scalar_flat = pooled.mean(dim=1, keepdim=True) | |
| # else: | |
| # raise RuntimeError("[ExtraTrain] cannot obtain features or logits from model forward to compute scalar per choice") | |
| # # Now scalar_flat should be (B*C, 1) -> reshape to (B, C) | |
| # if scalar_flat is None: | |
| # raise RuntimeError("[ExtraTrain] scalar_flat is None after processing model output") | |
| # if scalar_flat.ndim == 1: | |
| # scalar_flat = scalar_flat.view(-1, 1) | |
| # choice_scores = scalar_flat.view(Bf, Cf) | |
| # # If dataset's number of choices (Cf) differs from expected_num_labels, project to expected | |
| # if Cf != expected_num_labels: | |
| # # create a mapping layer if not exist (choice_count -> expected_num_labels) | |
| # if not hasattr(model, "_mc_choice_to_label"): | |
| # model._mc_choice_to_label = nn.Linear(Cf, expected_num_labels).to(device) | |
| # # register params in optimizer | |
| # try: | |
| # optimizer.add_param_group({'params': model._mc_choice_to_label.parameters()}) | |
| # except Exception: | |
| # pass | |
| # print(f"[ExtraTrain][{task}] created _mc_choice_to_label: {Cf}->{expected_num_labels}") | |
| # used_logits = model._mc_choice_to_label(choice_scores) | |
| # else: | |
| # used_logits = choice_scores | |
| # if used_logits.ndim != 2 or used_logits.size(0) != labs_b.size(0): | |
| # raise RuntimeError(f"Logits/labels shape mismatch used_logits={tuple(used_logits.shape)} labels={tuple(labs_b.shape)} at step {step}") | |
| # loss = ce_loss_fn(used_logits, labs_b.long()) | |
| # else: | |
| # ids_b, mask_b, labs_b = batch | |
| # ids_b = ids_b.to(device); mask_b = mask_b.to(device); labs_b = labs_b.to(device) | |
| # out = model(input_ids=ids_b, attention_mask=mask_b, labels=None) | |
| # logits = getattr(out, "logits", None) | |
| # if logits is None and isinstance(out, (tuple, list)): | |
| # logits = out[0] | |
| # if logits is None: | |
| # raise RuntimeError("Model did not return logits for pair task") | |
| # if logits.ndim == 2 and logits.shape[1] != expected_num_labels: | |
| # exist_dim = logits.shape[1] | |
| # model._mc_projector = nn.Linear(exist_dim, expected_num_labels).to(device) | |
| # try: | |
| # optimizer.add_param_group({'params': model._mc_projector.parameters()}) | |
| # except Exception: | |
| # pass | |
| # used_logits = model._mc_projector(logits) | |
| # else: | |
| # used_logits = logits | |
| # if cfg["type"] == "classification": | |
| # loss = ce_loss_fn(used_logits, labs_b.long()) | |
| # else: | |
| # if used_logits.ndim == 2 and used_logits.shape[1] == 1: | |
| # preds = used_logits.squeeze(1) | |
| # elif used_logits.ndim == 2: | |
| # preds = used_logits.mean(dim=1) | |
| # else: | |
| # preds = used_logits | |
| # loss = mse_loss_fn(preds, labs_b.float()) | |
| # loss = loss / max(1, grad_accum_steps) | |
| # loss.backward() | |
| # if (step + 1) % max(1, grad_accum_steps) == 0: | |
| # torch.nn.utils.clip_grad_norm_(model.parameters(), 1.0) | |
| # optimizer.step() | |
| # scheduler.step() | |
| # optimizer.zero_grad() | |
| # global_step += 1 | |
| # running_loss += float(loss.item()) * (ids_b.size(0) if is_mc else ids_b.size(0)) | |
| # # validation | |
| # model.eval() | |
| # all_logits = [] | |
| # all_labels = [] | |
| # with torch.no_grad(): | |
| # for batch in tqdm(val_loader, desc=f"[ExtraVal] {task} epoch {epoch+1}", leave=False): | |
| # if is_mc: | |
| # ids_b, mask_b, labs_b = batch | |
| # if labs_b.dim() > 1: | |
| # labs_b = labs_b.view(-1) | |
| # B = ids_b.size(0) | |
| # if labs_b.numel() != B: | |
| # raise RuntimeError(f"[ExtraVal][FATAL] val labels count ({labs_b.numel()}) != batch size ({B})") | |
| # ids_b = ids_b.to(device); mask_b = mask_b.to(device); labs_b = labs_b.to(device) | |
| # Bf, Cf, Lf = ids_b.shape | |
| # flat_ids = ids_b.view(Bf*Cf, Lf).to(device) | |
| # flat_mask = mask_b.view(Bf*Cf, Lf).to(device) if mask_b is not None else None | |
| # try: | |
| # if flat_mask is not None: | |
| # out_flat = model(input_ids=flat_ids, attention_mask=flat_mask, labels=None) | |
| # else: | |
| # out_flat = model(input_ids=flat_ids, labels=None) | |
| # except TypeError: | |
| # out_flat = model(flat_ids) | |
| # flat_logits = getattr(out_flat, "logits", None) | |
| # pooled = None | |
| # if flat_logits is None: | |
| # pooled = getattr(out_flat, "pooler_output", None) | |
| # if pooled is None: | |
| # lh = getattr(out_flat, "last_hidden_state", None) | |
| # if lh is not None: | |
| # pooled = lh[:, 0, :] | |
| # scalar_flat = None | |
| # if flat_logits is not None: | |
| # if flat_logits.ndim == 1: | |
| # scalar_flat = flat_logits.view(-1, 1) | |
| # elif flat_logits.ndim == 2 and flat_logits.shape[1] == 1: | |
| # scalar_flat = flat_logits.view(-1, 1) | |
| # elif flat_logits.ndim == 2: | |
| # if hasattr(model, "_mc_choice_head"): | |
| # try: | |
| # in_f = model._mc_choice_head.in_features | |
| # if flat_logits.shape[1] == in_f: | |
| # scalar_flat = model._mc_choice_head(flat_logits) | |
| # else: | |
| # scalar_flat = flat_logits.mean(dim=1, keepdim=True) | |
| # except Exception: | |
| # scalar_flat = flat_logits.mean(dim=1, keepdim=True) | |
| # else: | |
| # scalar_flat = flat_logits.mean(dim=1, keepdim=True) | |
| # elif flat_logits.ndim == 3: | |
| # scalar_flat = flat_logits.mean(dim=tuple(range(1, flat_logits.ndim))).view(-1, 1) | |
| # else: | |
| # scalar_flat = flat_logits.reshape(flat_logits.size(0), -1).mean(dim=1, keepdim=True) | |
| # elif pooled is not None: | |
| # head_found = None | |
| # for attr in ("classifier", "lm_head", "score", "classifier_head", "head", "_mc_choice_head"): | |
| # if hasattr(model, attr): | |
| # head_found = getattr(model, attr) | |
| # break | |
| # if head_found is None: | |
| # raise RuntimeError("[ExtraVal] no replacement head found on model to project pooled -> scalar") | |
| # try: | |
| # scalar_flat = head_found(pooled) | |
| # except Exception: | |
| # scalar_flat = pooled.mean(dim=1, keepdim=True) | |
| # else: | |
| # raise RuntimeError("Model did not return logits/poolable outputs during MC validation.") | |
| # choice_scores = scalar_flat.view(Bf, Cf) | |
| # if choice_scores.shape[1] != expected_num_labels: | |
| # exist_dim = choice_scores.shape[1] | |
| # if not hasattr(model, "_mc_choice_to_label"): | |
| # model._mc_choice_to_label = nn.Linear(exist_dim, expected_num_labels).to(device) | |
| # try: | |
| # optimizer.add_param_group({'params': model._mc_choice_to_label.parameters()}) | |
| # except Exception: | |
| # pass | |
| # used_logits = model._mc_choice_to_label(choice_scores) | |
| # else: | |
| # used_logits = choice_scores | |
| # all_logits.append(used_logits.detach().cpu().numpy()) | |
| # all_labels.append(labs_b.detach().cpu().numpy()) | |
| # else: | |
| # ids_b, mask_b, labs_b = batch | |
| # ids_b = ids_b.to(device); mask_b = mask_b.to(device); labs_b = labs_b.to(device) | |
| # out = model(input_ids=ids_b, attention_mask=mask_b, labels=None) | |
| # logits = getattr(out, "logits", None) | |
| # if logits is None and isinstance(out, (tuple, list)): | |
| # logits = out[0] | |
| # if logits.ndim == 2 and logits.shape[1] != expected_num_labels: | |
| # exist_dim = logits.shape[1] | |
| # if not hasattr(model, "_mc_projector"): | |
| # model._mc_projector = nn.Linear(exist_dim, expected_num_labels).to(device) | |
| # try: | |
| # optimizer.add_param_group({'params': model._mc_projector.parameters()}) | |
| # except Exception: | |
| # pass | |
| # used_logits = model._mc_projector(logits) | |
| # else: | |
| # used_logits = logits | |
| # all_logits.append(used_logits.detach().cpu().numpy()) | |
| # all_labels.append(labs_b.detach().cpu().numpy()) | |
| # model.train() | |
| # all_logits = np.concatenate(all_logits, axis=0) if all_logits else np.zeros((0, cfg["num_labels"])) | |
| # all_labels = np.concatenate(all_labels, axis=0) if all_labels else np.zeros((0,)) | |
| # if cfg["type"] == "classification" or cfg["type"] == "multiple_choice": | |
| # preds = np.argmax(all_logits, axis=-1) if all_logits.size else np.array([]) | |
| # else: | |
| # preds = _postprocess_predictions(task, all_logits, {"type": cfg["type"], "num_labels": cfg["num_labels"]}) | |
| # metric = evaluate.load("accuracy") | |
| # metric_res = metric.compute(predictions=preds.tolist(), references=all_labels.tolist()) | |
| # print(f"[Extra FT] {task} epoch {epoch+1} metric={metric_res}") | |
| # final_metric_res = metric_res | |
| # if out_checkpoint_dir: | |
| # outp = Path(out_checkpoint_dir) | |
| # outp.mkdir(parents=True, exist_ok=True) | |
| # fname = outp / "finetuned_extra.pt" | |
| # sd = unwrap_model(model).state_dict() | |
| # torch.save(sd, str(fname)) | |
| # print(f"[Extra FT] Saved finetuned model to: {fname}") | |
| # return model, final_metric_res | |
| # # --------------------------- | |
| # # 辅助:打印 HellaSwag 样例(用来“给我看一下”) | |
| # # --------------------------- | |
| # def print_hellaswag_samples(n: int = 5): | |
| # """ | |
| # 加载 hellaswag 数据集并打印前 n 个样例经 _extract_mc_example 解析后的 context/options/label。 | |
| # 运行时会在 stdout 中输出,方便你检查数据格式。 | |
| # """ | |
| # ds = load_dataset("hellaswag") | |
| # split = ds.get("validation") or ds.get("train") or ds.get("test") | |
| # if split is None: | |
| # print("[print_hellaswag_samples] no split") | |
| # return | |
| # for i, ex in enumerate(split[:n]): | |
| # ctx, opts, lab = _extract_mc_example("hellaswag", ex) | |
| # print(f"=== sample {i} ===") | |
| # print("context:", (ctx[:400] + "...") if ctx is not None and len(str(ctx))>400 else ctx) | |
| # print("num_options:", len(opts)) | |
| # for j, o in enumerate(opts): | |
| # print(f" [{j}] {o[:200]}{'...' if len(str(o))>200 else ''}") | |
| # print("label:", lab) | |
| # print() | |
| # # --------------------------- | |
| # # _tokenize_mc_split_to_tensors (general MC tokenizer) | |
| # # --------------------------- | |
| # def _tokenize_mc_split_to_tensors(task: str, tokenizer, raw_split, max_length=128, batch_tokenize_size=256): | |
| # """ | |
| # Generalized multiple-choice tokenizer that supports: | |
| # - PIQA (goal/sol1/sol2) | |
| # - Winogrande (sentence/option1/option2) | |
| # - ARC-like (question + choices, answerKey) | |
| # - RACE/other formats with 'options'/'choices' (list of strings or list of dicts with 'text') | |
| # Returns: | |
| # input_ids: (N, C, L) | |
| # attention_mask: (N, C, L) | |
| # labels: (N,) with -1 for unknown/no-label examples | |
| # This function performs strict checks and will raise on inconsistencies (no silent catches). | |
| # """ | |
| # contexts = [] | |
| # options = [] | |
| # labels = [] | |
| # def _extract_choices_text(choices_field): | |
| # out = [] | |
| # for c in choices_field: | |
| # if isinstance(c, str): | |
| # out.append(c) | |
| # elif isinstance(c, dict): | |
| # txt = c.get("text") or c.get("content") or c.get("choice") or c.get("label") | |
| # out.append(txt if txt is not None else str(c)) | |
| # else: | |
| # out.append(str(c)) | |
| # return out | |
| # def _map_answer_to_index(ans, choice_texts): | |
| # if ans is None: | |
| # return -1 | |
| # if isinstance(ans, (list, tuple)): | |
| # if len(ans) == 0: | |
| # return -1 | |
| # ans = ans[0] | |
| # if isinstance(ans, str): | |
| # s = ans.strip() | |
| # if len(s) == 1 and s.isalpha(): | |
| # idx = ord(s.upper()) - ord("A") | |
| # if 0 <= idx < len(choice_texts): | |
| # return idx | |
| # try: | |
| # ni = int(s) | |
| # if 0 <= ni < len(choice_texts): | |
| # return ni | |
| # if 1 <= ni <= len(choice_texts): | |
| # return ni - 1 | |
| # except Exception: | |
| # pass | |
| # for i, ct in enumerate(choice_texts): | |
| # if isinstance(ct, str) and s == ct: | |
| # return i | |
| # for i, ct in enumerate(choice_texts): | |
| # if isinstance(ct, str) and s in ct: | |
| # return i | |
| # low = s.lower() | |
| # if low in ("true", "t", "yes", "y"): | |
| # return 1 | |
| # if low in ("false", "f", "no", "n"): | |
| # return 0 | |
| # return -1 | |
| # if isinstance(ans, (int, np.integer)): | |
| # ai = int(ans) | |
| # if 0 <= ai < len(choice_texts): | |
| # return ai | |
| # if 1 <= ai <= len(choice_texts): | |
| # return ai - 1 | |
| # return -1 | |
| # return -1 | |
| # num_choices = None | |
| # for ex in raw_split: | |
| # ctx = None | |
| # opts = None | |
| # lab = None | |
| # # PIQA | |
| # if "goal" in ex and ("sol1" in ex or "sol2" in ex): | |
| # ctx = ex.get("goal") | |
| # opts = [ex.get("sol1"), ex.get("sol2")] | |
| # lab = ex.get("label") or ex.get("answer") or ex.get("answerKey") | |
| # if isinstance(lab, (str, np.str_)) and str(lab).isdigit(): | |
| # lab = int(lab) | |
| # # Winogrande | |
| # elif "sentence" in ex and ("option1" in ex or "option2" in ex): | |
| # ctx = ex.get("sentence") | |
| # opts = [ex.get("option1"), ex.get("option2")] | |
| # lab = ex.get("answer") or ex.get("label") or ex.get("answerKey") | |
| # if isinstance(lab, str) and lab.isdigit(): | |
| # lab = int(lab) - 1 # convert 1-based -> 0-based | |
| # # ARC / RACE-style: question + choices (choices may be list of strings or list of dicts) | |
| # elif "question" in ex and ("choices" in ex or "options" in ex): | |
| # ctx = ex.get("question") | |
| # choices_field = ex.get("choices") or ex.get("options") | |
| # opts = _extract_choices_text(choices_field) | |
| # lab = ex.get("answerKey") or ex.get("answer") or ex.get("correct_answer") or ex.get("label") | |
| # # Generic fallback: try known pairs | |
| # elif "context" in ex and "options" in ex: | |
| # ctx = ex.get("context") | |
| # opts = _extract_choices_text(ex.get("options")) | |
| # lab = ex.get("label") or ex.get("answer") | |
| # else: | |
| # if "question" in ex: | |
| # ctx = ex.get("question") | |
| # elif "query" in ex: | |
| # ctx = ex.get("query") | |
| # elif "prompt" in ex: | |
| # ctx = ex.get("prompt") | |
| # else: | |
| # for k, v in ex.items(): | |
| # if isinstance(v, str) and len(v) > 0: | |
| # ctx = v | |
| # break | |
| # for k, v in ex.items(): | |
| # if isinstance(v, (list, tuple)) and len(v) > 1: | |
| # if all(isinstance(x, (str, dict)) for x in v): | |
| # opts = _extract_choices_text(v) | |
| # break | |
| # lab = ex.get("answer") or ex.get("label") or ex.get("answerKey") or ex.get("correct_answer") | |
| # if opts is None: | |
| # opts = [""] | |
| # if num_choices is None: | |
| # num_choices = len(opts) | |
| # else: | |
| # if len(opts) != num_choices: | |
| # if len(opts) < num_choices: | |
| # opts = opts + [""] * (num_choices - len(opts)) | |
| # else: | |
| # opts = opts[:num_choices] | |
| # lab_idx = _map_answer_to_index(lab, opts) | |
| # contexts.append(ctx) | |
| # options.append(opts) | |
| # labels.append(int(lab_idx) if lab_idx is not None else -1) | |
| # if num_choices is None: | |
| # return torch.zeros((0, 1, 1), dtype=torch.long), torch.zeros((0, 1, 1), dtype=torch.long), torch.tensor([], dtype=torch.long) | |
| # input_ids_rows = [] | |
| # attention_rows = [] | |
| # pad_id = getattr(tokenizer, "pad_token_id", None) | |
| # if pad_id is None: | |
| # try: | |
| # pad_id = tokenizer.token_to_id("[PAD]") | |
| # except Exception: | |
| # pad_id = 0 | |
| # for i in range(0, len(contexts), batch_tokenize_size): | |
| # chunk_ctx = contexts[i:i+batch_tokenize_size] | |
| # chunk_opts = options[i:i+batch_tokenize_size] | |
| # flat_pairs = [] | |
| # for c, opts in zip(chunk_ctx, chunk_opts): | |
| # for o in opts: | |
| # flat_pairs.append((c if c is not None else "", o if o is not None else "")) | |
| # enc = tokenizer(flat_pairs, truncation=True, padding=False, max_length=max_length) | |
| # ids_flat = enc.get("input_ids") | |
| # masks_flat = enc.get("attention_mask") or enc.get("mask") or enc.get("masks") | |
| # if isinstance(ids_flat, torch.Tensor): | |
| # ids_flat = ids_flat.tolist() | |
| # if isinstance(masks_flat, torch.Tensor): | |
| # masks_flat = masks_flat.tolist() | |
| # per_example = [] | |
| # per_mask_example = [] | |
| # idx = 0 | |
| # for _ in range(len(chunk_ctx)): | |
| # row = [] | |
| # row_mask = [] | |
| # for _ in range(num_choices): | |
| # if idx >= len(ids_flat): | |
| # # this will raise later if shapes inconsistent; better to raise now | |
| # raise RuntimeError(f"[tokenize_mc] tokenizer returned too few items at batch starting index {i} (got {len(ids_flat)}, needed at least {len(chunk_ctx)*num_choices})") | |
| # row.append(ids_flat[idx]) | |
| # row_mask.append(masks_flat[idx]) | |
| # idx += 1 | |
| # per_example.append(row) | |
| # per_mask_example.append(row_mask) | |
| # input_ids_rows.extend(per_example) | |
| # attention_rows.extend(per_mask_example) | |
| # max_len = max(len(seq) for row in input_ids_rows for seq in row) if input_ids_rows else 1 | |
| # input_ids_padded = [ | |
| # [ seq + [pad_id] * (max_len - len(seq)) for seq in row ] | |
| # for row in input_ids_rows | |
| # ] | |
| # attention_padded = [ | |
| # [ mask + [0] * (max_len - len(mask)) for mask in row ] | |
| # for row in attention_rows | |
| # ] | |
| # input_ids_t = torch.tensor(input_ids_padded, dtype=torch.long) # (N, C, L) | |
| # attention_t = torch.tensor(attention_padded, dtype=torch.long) | |
| # labels_t = torch.tensor(labels, dtype=torch.long) | |
| # return input_ids_t, attention_t, labels_t | |
| # # --------------------------------------------------------------------- | |
| # # Postprocess preds helper already defined above (_postprocess_predictions) | |
| # # --------------------------------------------------------------------- | |
| # # --------------------------------------------------------------------- | |
| # # Training for GLUE tasks (full fine-tune) | |
| # # --------------------------------------------------------------------- | |
| # def train_full_finetune( | |
| # task: str, | |
| # tokenizer, | |
| # model, | |
| # raw_train, | |
| # raw_val, | |
| # device: str = "cuda", | |
| # epochs: int = 3, | |
| # batch_size: int = 32, | |
| # lr: float = 2e-5, | |
| # weight_decay: float = 0.01, | |
| # warmup_steps: int = 100, | |
| # max_length: int = 128, | |
| # grad_accum_steps: int = 1, | |
| # out_checkpoint_dir: Optional[str] = None, | |
| # seed: Optional[int] = None, | |
| # ): | |
| # cfg_task = GLUE_TASKS[task] | |
| # device_t = torch.device(device if torch.cuda.is_available() else "cpu") | |
| # if seed is not None: | |
| # _set_all_seeds(int(seed)) | |
| # hidden_size = None | |
| # if hasattr(model, "config") and hasattr(model.config, "hidden_size"): | |
| # try: | |
| # hidden_size = int(model.config.hidden_size) | |
| # except Exception: | |
| # hidden_size = None | |
| # model, wrapped_flag = make_wrapped_model_if_needed( | |
| # model, hidden_size, cfg_task["num_labels"], force_num_labels=cfg_task["num_labels"] | |
| # ) | |
| # model.to(device_t) | |
| # train_ids, train_mask, train_labels = _tokenize_hf_split_to_tensors(task, tokenizer, raw_train, cfg_task, max_length=max_length) | |
| # val_ids, val_mask, val_labels = _tokenize_hf_split_to_tensors(task, tokenizer, raw_val, cfg_task, max_length=max_length) | |
| # train_ds = TensorDataset(train_ids, train_mask, train_labels) | |
| # val_ds = TensorDataset(val_ids, val_mask, val_labels) | |
| # g = torch.Generator() | |
| # if seed is not None: | |
| # g.manual_seed(int(seed)) | |
| # train_loader = DataLoader(train_ds, batch_size=batch_size, shuffle=True, pin_memory=True, generator=g) | |
| # val_loader = DataLoader(val_ds, batch_size=max(64, batch_size), shuffle=False, pin_memory=True) | |
| # optimizer = torch.optim.AdamW(model.parameters(), lr=lr, weight_decay=weight_decay) | |
| # total_steps = max(1, (len(train_loader) // max(1, grad_accum_steps)) * epochs) | |
| # try: | |
| # from transformers import get_cosine_schedule_with_warmup | |
| # scheduler = get_cosine_schedule_with_warmup(optimizer, num_warmup_steps=warmup_steps, num_training_steps=total_steps) | |
| # except Exception: | |
| # scheduler = None | |
| # loss_fn = torch.nn.CrossEntropyLoss() if cfg_task["type"] == "classification" else torch.nn.MSELoss() | |
| # best_metric_res: Dict[str, Any] = {} | |
| # best_score: Optional[float] = None | |
| # best_epoch = -1 | |
| # model.train() | |
| # for epoch in range(epochs): | |
| # for step, batch in enumerate(tqdm(train_loader, desc=f"Train {task} epoch {epoch+1} (lr={lr:g})")): | |
| # ids_b, mask_b, labs_b = batch | |
| # ids_b = ids_b.to(device_t) | |
| # mask_b = mask_b.to(device_t) | |
| # labs_b = labs_b.to(device_t) | |
| # out = model(input_ids=ids_b, attention_mask=mask_b, labels=None) | |
| # logits = getattr(out, "logits", None) | |
| # if logits is None: | |
| # if isinstance(out, (tuple, list)): | |
| # logits = out[0] | |
| # else: | |
| # raise RuntimeError("Model did not return logits during finetune") | |
| # if cfg_task["type"] == "classification": | |
| # loss = loss_fn(logits, labs_b.long()) | |
| # else: | |
| # if logits.ndim == 2 and logits.shape[1] == 1: | |
| # preds = logits.squeeze(1) | |
| # elif logits.ndim == 2: | |
| # preds = logits.mean(dim=1) | |
| # else: | |
| # preds = logits | |
| # loss = loss_fn(preds, labs_b.float()) | |
| # loss = loss / max(1, grad_accum_steps) | |
| # loss.backward() | |
| # if (step + 1) % max(1, grad_accum_steps) == 0: | |
| # torch.nn.utils.clip_grad_norm_(model.parameters(), 1.0) | |
| # optimizer.step() | |
| # if scheduler is not None: | |
| # scheduler.step() | |
| # optimizer.zero_grad() | |
| # # validation | |
| # model.eval() | |
| # tot_val_loss = 0.0 | |
| # all_logits = [] | |
| # all_labels = [] | |
| # with torch.no_grad(): | |
| # for ids_b, mask_b, labs_b in tqdm(val_loader, desc=f"Validate {task} epoch {epoch+1}", leave=False): | |
| # ids_b = ids_b.to(device_t) | |
| # mask_b = mask_b.to(device_t) | |
| # labs_b = labs_b.to(device_t) | |
| # out = model(input_ids=ids_b, attention_mask=mask_b, labels=None) | |
| # logits = getattr(out, "logits", None) | |
| # if logits is None: | |
| # if isinstance(out, (tuple, list)): | |
| # logits = out[0] | |
| # else: | |
| # raise RuntimeError("Model did not return logits during validation") | |
| # if cfg_task["type"] == "classification": | |
| # l = loss_fn(logits, labs_b.long()) | |
| # else: | |
| # if logits.ndim == 2 and logits.shape[1] == 1: | |
| # preds = logits.squeeze(1) | |
| # elif logits.ndim == 2: | |
| # preds = logits.mean(dim=1) | |
| # else: | |
| # preds = logits | |
| # l = loss_fn(preds, labs_b.float()) | |
| # tot_val_loss += l.item() * ids_b.size(0) | |
| # all_logits.append(logits.detach().cpu().numpy()) | |
| # all_labels.append(labs_b.detach().cpu().numpy()) | |
| # model.train() | |
| # all_logits = np.concatenate(all_logits, axis=0) if all_logits else np.zeros((0, cfg_task["num_labels"])) | |
| # all_labels = np.concatenate(all_labels, axis=0) if all_labels else np.zeros((0,)) | |
| # preds = _postprocess_predictions(task, all_logits, cfg_task) | |
| # metric = evaluate.load("glue", cfg_task["hf_name"]) | |
| # try: | |
| # metric_res = metric.compute(predictions=preds.tolist(), references=all_labels.tolist()) | |
| # except Exception: | |
| # try: | |
| # metric_res = metric.compute(predictions=preds, references=all_labels) | |
| # except Exception as e: | |
| # metric_res = {"error": str(e)} | |
| # avg_val_loss = tot_val_loss / len(val_ds) if len(val_ds) > 0 else float("nan") | |
| # score = _metric_to_scalar(task, metric_res, fallback_val_loss=avg_val_loss) | |
| # print(f"[FT] {task} epoch {epoch+1} lr={lr:g} val_loss={avg_val_loss:.6f} metric={metric_res} score={score:.6f}") | |
| # if best_score is None or float(score) > float(best_score): | |
| # best_score = float(score) | |
| # best_metric_res = metric_res | |
| # best_epoch = epoch + 1 | |
| # if out_checkpoint_dir: | |
| # outp = Path(out_checkpoint_dir) | |
| # outp.mkdir(parents=True, exist_ok=True) | |
| # best_fname = outp / "best_finetuned.pt" | |
| # try: | |
| # sd = unwrap_model(model).state_dict() | |
| # except Exception: | |
| # sd = model.state_dict() | |
| # torch.save(sd, str(best_fname)) | |
| # print(f"[FT] Saved best checkpoint (epoch {best_epoch}) to: {best_fname}") | |
| # # save final checkpoint | |
| # if out_checkpoint_dir: | |
| # outp = Path(out_checkpoint_dir) | |
| # outp.mkdir(parents=True, exist_ok=True) | |
| # fname = outp / "finetuned.pt" | |
| # try: | |
| # sd = unwrap_model(model).state_dict() | |
| # except Exception: | |
| # sd = model.state_dict() | |
| # torch.save(sd, str(fname)) | |
| # print(f"[FT] Saved finetuned model to: {fname}") | |
| # meta = { | |
| # "task": task, | |
| # "lr": float(lr), | |
| # "seed": int(seed) if seed is not None else None, | |
| # "epochs": int(epochs), | |
| # "batch_size": int(batch_size), | |
| # "grad_accum_steps": int(grad_accum_steps), | |
| # "warmup_steps": int(warmup_steps), | |
| # "weight_decay": float(weight_decay), | |
| # "max_length": int(max_length), | |
| # "wrapped_flag": bool(wrapped_flag), | |
| # "best_epoch": int(best_epoch), | |
| # "best_score": float(best_score) if best_score is not None else None, | |
| # "best_metrics": best_metric_res, | |
| # } | |
| # _json_dump(meta, outp / "run_meta.json") | |
| # print( | |
| # f"[FT] Best validation for task '{task}' " | |
| # f"(lr={lr:g}, seed={seed}): epoch={best_epoch}, " | |
| # f"score={best_score}, metrics={best_metric_res}" | |
| # ) | |
| # return model, best_metric_res, float(best_score) if best_score is not None else -1e9, best_epoch | |
| # # --------------------------------------------------------------------- | |
| # # Load finetuned checkpoint for eval (wrapped/unwrapped) | |
| # # --------------------------------------------------------------------- | |
| # def _load_finetuned_checkpoint_for_task(task: str, base_model, checkpoint_path: str): | |
| # if not checkpoint_path or not os.path.exists(checkpoint_path): | |
| # return base_model | |
| # cfg = GLUE_TASKS.get(task) or EXTRA_TASKS.get(task) | |
| # sd = torch.load(checkpoint_path, map_location="cpu") | |
| # keys = list(sd.keys()) if isinstance(sd, dict) else [] | |
| # looks_wrapped = any(k.startswith("base.") for k in keys) or any(k.startswith("classifier.") for k in keys) | |
| # hidden_size = None | |
| # if hasattr(base_model, "config") and hasattr(base_model.config, "hidden_size"): | |
| # try: | |
| # hidden_size = int(base_model.config.hidden_size) | |
| # except Exception: | |
| # hidden_size = None | |
| # if looks_wrapped and cfg is not None: | |
| # wrapped_model, _ = make_wrapped_model_if_needed( | |
| # base_model, | |
| # hidden_size, | |
| # cfg["num_labels"], | |
| # force_num_labels=cfg["num_labels"], | |
| # ) | |
| # try: | |
| # unwrap_model(wrapped_model).load_state_dict(sd, strict=False) | |
| # except Exception: | |
| # wrapped_model.load_state_dict(sd, strict=False) | |
| # return wrapped_model | |
| # try: | |
| # unwrap_model(base_model).load_state_dict(sd, strict=False) | |
| # except Exception: | |
| # base_model.load_state_dict(sd, strict=False) | |
| # return base_model | |
| # # --------------------------------------------------------------------- | |
| # # Evaluation only for EXTRA_TASKS (no training) | |
| # # --------------------------------------------------------------------- | |
| # def run_extra_task( | |
| # task: str, | |
| # tokenizer, | |
| # model, | |
| # device: str = "cuda", | |
| # batch_size: int = 32, | |
| # max_length: int = 128, | |
| # output_dir: Optional[str] = None, | |
| # expect_num_choices: Optional[int] = None, | |
| # ): | |
| # """ | |
| # Evaluation-only runner for EXTRA_TASKS. | |
| # Uses the SAME MC flatten->reshape logic as train_full_finetune_extra. | |
| # Reports accuracy on validation split (lm-eval style). | |
| # """ | |
| # cfg = EXTRA_TASKS[task] | |
| # device_t = torch.device(device if torch.cuda.is_available() else "cpu") | |
| # model.to(device_t) | |
| # model.eval() | |
| # # load dataset | |
| # if "hf_config" in cfg: | |
| # ds = load_dataset(cfg["hf_path"], cfg["hf_config"]) | |
| # else: | |
| # ds = load_dataset(cfg["hf_path"]) | |
| # raw_eval = ds.get("validation") or ds.get("test") | |
| # if raw_eval is None: | |
| # raise RuntimeError(f"[run_extra_task] No validation/test split for task={task}") | |
| # is_mc = cfg["format"] == "mc" | |
| # if is_mc: | |
| # ids, masks, labels = _tokenize_generic_mc_split_to_tensors( | |
| # task, | |
| # tokenizer, | |
| # raw_eval, | |
| # max_length=max_length, | |
| # expect_num_choices=expect_num_choices, | |
| # ) | |
| # if ids.ndim != 3: | |
| # raise RuntimeError(f"[run_extra_task][{task}] ids must be (N,C,L)") | |
| # ds_eval = TensorDataset(ids, masks, labels) | |
| # else: | |
| # ids, masks, labels = _tokenize_hf_split_to_tensors( | |
| # task, | |
| # tokenizer, | |
| # raw_eval, | |
| # cfg, | |
| # max_length=max_length, | |
| # ) | |
| # ds_eval = TensorDataset(ids, masks, labels) | |
| # loader = DataLoader(ds_eval, batch_size=batch_size, shuffle=False) | |
| # all_preds = [] | |
| # all_refs = [] | |
| # with torch.no_grad(): | |
| # for batch in tqdm(loader, desc=f"[ExtraEval] {task}"): | |
| # if is_mc: | |
| # ids_b, mask_b, labs_b = batch | |
| # ids_b = ids_b.to(device_t) | |
| # mask_b = mask_b.to(device_t) | |
| # labs_b = labs_b.to(device_t) | |
| # B, C, L = ids_b.shape | |
| # flat_ids = ids_b.view(B * C, L) | |
| # flat_mask = mask_b.view(B * C, L) | |
| # try: | |
| # out = model(input_ids=flat_ids, attention_mask=flat_mask, labels=None) | |
| # except TypeError: | |
| # out = model(flat_ids) | |
| # flat_logits = getattr(out, "logits", None) | |
| # if flat_logits is None and isinstance(out, (tuple, list)): | |
| # flat_logits = out[0] | |
| # if flat_logits is None: | |
| # raise RuntimeError("Model did not return logits during MC eval") | |
| # # reduce to (B, C) | |
| # if flat_logits.ndim == 1: | |
| # choice_scores = flat_logits.view(B, C) | |
| # elif flat_logits.ndim == 2: | |
| # D = flat_logits.shape[1] | |
| # if D == 1: | |
| # choice_scores = flat_logits.view(B, C).squeeze(-1) | |
| # else: | |
| # if not hasattr(model, "_mc_projector") or model._mc_projector.weight.shape[1] != D: | |
| # model._mc_projector = torch.nn.Linear(D, 1).to(device_t) | |
| # proj = model._mc_projector(flat_logits) | |
| # choice_scores = proj.view(B, C).squeeze(-1) | |
| # else: | |
| # choice_scores = flat_logits.mean(dim=tuple(range(1, flat_logits.ndim))).view(B, C) | |
| # preds = torch.argmax(choice_scores, dim=1) | |
| # else: | |
| # ids_b, mask_b, labs_b = batch | |
| # ids_b = ids_b.to(device_t) | |
| # mask_b = mask_b.to(device_t) | |
| # out = model(input_ids=ids_b, attention_mask=mask_b, labels=None) | |
| # logits = getattr(out, "logits", None) | |
| # if logits is None and isinstance(out, (tuple, list)): | |
| # logits = out[0] | |
| # preds = torch.argmax(logits, dim=-1) | |
| # all_preds.append(preds.cpu().numpy()) | |
| # all_refs.append(labs_b.cpu().numpy()) | |
| # all_preds = np.concatenate(all_preds, axis=0) | |
| # all_refs = np.concatenate(all_refs, axis=0) | |
| # acc = float((all_preds == all_refs).mean()) | |
| # metric_res = {"accuracy": acc} | |
| # print(f"[ExtraEval] {task} accuracy={acc:.4f}") | |
| # if output_dir: | |
| # Path(output_dir).mkdir(parents=True, exist_ok=True) | |
| # with open(Path(output_dir) / "eval_metrics.json", "w") as f: | |
| # json.dump(metric_res, f, indent=2) | |
| # return metric_res | |
| # # --------------------------------------------------------------------- | |
| # # Grid-runner: LR sweep + restarts + checkpointing + eval | |
| # # --------------------------------------------------------------------- | |
| # def run_glue_benchmark( | |
| # config, | |
| # tokenizer, | |
| # model, | |
| # checkpointing: Optional[Checkpointing] = None, | |
| # out_dir: str = "glue_outputs_grid", | |
| # ): | |
| # tasks = getattr(config, "glue_tasks", None) | |
| # if tasks is None: | |
| # tasks = list(ALL_TASKS.keys()) | |
| # # tasks = ['hellaswag','openbookqa','arc_easy','arc_challenge'] | |
| # # tasks = tasks[::-1] | |
| # # ['hellaswag','openbookqa','arc_easy','arc_challenge'] | |
| # # tasks = tasks[::-1] | |
| # tasks = ["piqa", "boolq", "winogrande"] | |
| # # "hellaswag": "accuracy", | |
| # # "openbookqa": "accuracy", | |
| # # "arc_easy": "accuracy", | |
| # # "arc_challenge": "accuracy", | |
| # batch_size = getattr(config, "batch_size", 64) | |
| # max_length = getattr(config, "max_length", 128) | |
| # device = getattr(config, "device", "cuda") | |
| # auto_train = getattr(config, "auto_train", True) | |
| # train_epochs = getattr(config, "train_epochs", 3) | |
| # train_epochs_per_task = getattr(config, "train_epochs_per_task", {}) or { | |
| # t: train_epochs for t in tasks | |
| # } | |
| # train_batch_size = getattr(config, "train_batch_size", 32) | |
| # train_warmup_steps = getattr(config, "train_warmup_steps", 100) | |
| # train_weight_decay = getattr(config, "train_weight_decay", 0.01) | |
| # train_grad_accum_steps = getattr(config, "train_grad_accum_steps", 1) | |
| # lr_candidates = getattr(config, "bert_lr_candidates", BERT_LR_CANDIDATES) | |
| # random_restarts_small = int(getattr(config, "random_restarts_small", 1)) | |
| # base_seed = int(getattr(config, "base_seed", 543211)) | |
| # out_dir = Path(out_dir) | |
| # out_dir.mkdir(parents=True, exist_ok=True) | |
| # checkpoint_out_root = out_dir / "checkpoints" | |
| # checkpoint_out_root.mkdir(parents=True, exist_ok=True) | |
| # rows = [] | |
| # # save original model state | |
| # try: | |
| # original_state = unwrap_model(model).state_dict() | |
| # except Exception: | |
| # original_state = model.state_dict() | |
| # def _reset_model_to_original(): | |
| # pass | |
| # # try: | |
| # # unwrap_model(model).load_state_dict(original_state, strict=False) | |
| # # except Exception: | |
| # # model.load_state_dict(original_state, strict=False) | |
| # for task in tasks: | |
| # print(f"\n==== Grid-running task: {task} ====") | |
| # epochs_this_task = int(train_epochs_per_task.get(task, train_epochs)) | |
| # # load dataset | |
| # if task in EXTRA_TASKS: | |
| # cfg = EXTRA_TASKS[task] | |
| # if "hf_config" in cfg: | |
| # ds = load_dataset(cfg["hf_path"], cfg["hf_config"]) | |
| # else: | |
| # ds = load_dataset(cfg["hf_path"]) | |
| # train_raw = ds.get("train") | |
| # val_raw = ds.get("validation") or ds.get("test") | |
| # else: | |
| # hf = load_dataset("glue", GLUE_TASKS[task]["hf_name"]) | |
| # train_raw = hf["train"] | |
| # val_raw = hf["validation_matched"] if task == "mnli" else hf["validation"] | |
| # task_ckpt_root = checkpoint_out_root / task | |
| # task_ckpt_root.mkdir(parents=True, exist_ok=True) | |
| # all_run_records = [] | |
| # best_run = {"score": None, "lr": None, "restart": None, "seed": None, "best_ckpt_path": None} | |
| # small_flag = (task in SMALL_TASKS_RANDOM_RESTARTS) or (task in SMALL_TASKS_RANDOM_RESTARTS_EXTRA) | |
| # restarts_per_lr = random_restarts_small if small_flag else 1 | |
| # if auto_train: | |
| # for lr in lr_candidates: | |
| # for restart_idx in range(restarts_per_lr): | |
| # seed = ( | |
| # base_seed | |
| # + (abs(hash(task)) % 10000) * 1000 | |
| # + restart_idx * 10 | |
| # + (int(round(lr * 1e7)) % 1000) | |
| # ) | |
| # print(f"[SWEEP] task={task} lr={lr:g} restart={restart_idx} seed={seed}") | |
| # _reset_model_to_original() | |
| # run_dir = task_ckpt_root / f"lr_{lr:g}" / f"restart_{restart_idx}" | |
| # run_dir.mkdir(parents=True, exist_ok=True) | |
| # if checkpointing is not None: | |
| # try: | |
| # checkpointing.load_model_states("recent") | |
| # except Exception: | |
| # pass | |
| # if task in EXTRA_TASKS: | |
| # det = detect_num_choices(val_raw, task) | |
| # expect_num = det.get("detected_num_choices") | |
| # model, metric_res = train_full_finetune_extra( | |
| # task=task, | |
| # tokenizer=tokenizer, | |
| # model=model, | |
| # raw_train=train_raw, | |
| # raw_val=val_raw, | |
| # device=device, | |
| # epochs=epochs_this_task, | |
| # batch_size=train_batch_size, | |
| # lr=float(lr), | |
| # weight_decay=train_weight_decay, | |
| # warmup_steps=train_warmup_steps, | |
| # max_length=max_length, | |
| # grad_accum_steps=train_grad_accum_steps, | |
| # out_checkpoint_dir=str(run_dir), | |
| # expect_num_choices=expect_num, | |
| # ) | |
| # sc = _metric_to_scalar(task, metric_res) | |
| # best_epoch = None | |
| # else: | |
| # model, metric_res, sc, best_epoch = train_full_finetune( | |
| # task=task, | |
| # tokenizer=tokenizer, | |
| # model=model, | |
| # raw_train=train_raw, | |
| # raw_val=val_raw, | |
| # device=device, | |
| # epochs=epochs_this_task, | |
| # batch_size=train_batch_size, | |
| # lr=float(lr), | |
| # weight_decay=train_weight_decay, | |
| # warmup_steps=train_warmup_steps, | |
| # max_length=max_length, | |
| # grad_accum_steps=train_grad_accum_steps, | |
| # out_checkpoint_dir=str(run_dir), | |
| # seed=seed, | |
| # ) | |
| # rec = { | |
| # "task": task, | |
| # "lr": float(lr), | |
| # "restart": int(restart_idx), | |
| # "seed": int(seed), | |
| # "dev_score": float(sc) if sc is not None else None, | |
| # "run_dir": str(run_dir), | |
| # "best_ckpt_path": str(run_dir / "best_finetuned.pt"), | |
| # } | |
| # all_run_records.append(rec) | |
| # if best_run["score"] is None or (sc is not None and sc > best_run["score"]): | |
| # best_run.update( | |
| # { | |
| # "score": float(sc), | |
| # "lr": float(lr), | |
| # "restart": int(restart_idx), | |
| # "seed": int(seed), | |
| # "best_ckpt_path": rec["best_ckpt_path"], | |
| # } | |
| # ) | |
| # pd.DataFrame(all_run_records).to_csv(task_ckpt_root / "all_runs.csv", index=False) | |
| # best_overall_dir = task_ckpt_root / "best_overall" | |
| # best_overall_dir.mkdir(parents=True, exist_ok=True) | |
| # if best_run["best_ckpt_path"] and os.path.exists(best_run["best_ckpt_path"]): | |
| # shutil.copy2(best_run["best_ckpt_path"], best_overall_dir / "best_finetuned.pt") | |
| # _json_dump(best_run, best_overall_dir / "best_meta.json") | |
| # checkpointing.load_model_states("recent") | |
| # model_for_eval = _load_finetuned_checkpoint_for_task( | |
| # task, model, str(best_overall_dir / "best_finetuned.pt") | |
| # ) | |
| # else: | |
| # model_for_eval = model | |
| # task_out_dir = out_dir / task | |
| # task_out_dir.mkdir(parents=True, exist_ok=True) | |
| # if task in EXTRA_TASKS: | |
| # det = detect_num_choices(val_raw, task) | |
| # expect_num = det.get("detected_num_choices") | |
| # metric_res = run_extra_task( | |
| # task=task, | |
| # tokenizer=tokenizer, | |
| # model=model_for_eval, | |
| # device=device, | |
| # batch_size=batch_size, | |
| # max_length=max_length, | |
| # output_dir=str(task_out_dir), | |
| # expect_num_choices=expect_num, | |
| # ) | |
| # rows.append({"task": task, "split": "validation", "metrics": json.dumps(metric_res)}) | |
| # else: | |
| # res = run_glue_task( | |
| # task=task, | |
| # tokenizer=tokenizer, | |
| # model=model_for_eval, | |
| # device=device, | |
| # batch_size=batch_size, | |
| # max_length=max_length, | |
| # output_dir=str(task_out_dir), | |
| # compute_errorbar=True, | |
| # ) | |
| # for split, pack in res.items(): | |
| # rows.append( | |
| # { | |
| # "task": task, | |
| # "split": split, | |
| # "metrics": json.dumps(pack["metrics"]), | |
| # "errorbar": json.dumps(pack["errorbar"]), | |
| # } | |
| # ) | |
| # summary_csv = out_dir / "glue_summary.csv" | |
| # pd.DataFrame(rows).to_csv(summary_csv, index=False) | |
| # print(f"[Grid] Summary saved to: {summary_csv}") | |
| # return pd.DataFrame(rows) | |
| # # --------------------------------------------------------------------- | |
| # # CLI | |
| # # --------------------------------------------------------------------- | |
| # if __name__ == "__main__": | |
| # print( | |
| # "This module is intended to be imported and used by providing tokenizer/model objects.\n" | |
| # "Example:\n" | |
| # " tok = AutoTokenizer.from_pretrained('bert-base-uncased')\n" | |
| # " model = AutoModelForSequenceClassification.from_pretrained('bert-base-uncased')\n" | |
| # " run_glue_benchmark(cfg, tok, model)\n" | |
| # ) | |