ereniko commited on
Commit
64083f6
·
verified ·
1 Parent(s): e7b0ce9

Upload folder using huggingface_hub

Browse files
Files changed (2) hide show
  1. eval/ivme_lm.py +180 -0
  2. eval/run_eval.py +66 -0
eval/ivme_lm.py ADDED
@@ -0,0 +1,180 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ lm-evaluation-harness adapter for IvmeLabs/Ivme-Conversate-v2-Base (and v1, same arch).
3
+
4
+ Usage:
5
+ lm_eval --model ivme \
6
+ --model_args checkpoint=/path/to/ckpt_final.pt,tokenizer=/path/to/tokenizer.json \
7
+ --tasks wikitext,arc_easy,blimp \
8
+ --device cuda:0 \
9
+ --batch_size 16
10
+
11
+ Register this file with the harness either by:
12
+ (a) `lm_eval --include_path .` pointing at the dir containing this file, or
13
+ (b) placing it on PYTHONPATH and importing it before calling lm_eval's CLI
14
+ programmatically (see run_eval.py in this folder for an example).
15
+
16
+ Why a custom adapter instead of --model hf:
17
+ IvmeConversateV2 is not a HF `transformers` model -- it's a bespoke
18
+ nn.Module with a `forward(idx, targets=None) -> (logits, loss)` signature,
19
+ a plain dataclass config, and a checkpoint dict with an EMA state dict.
20
+ The harness's LM.loglikelihood / loglikelihood_rolling contracts are
21
+ architecture-agnostic, so wrapping it here is the correct amount of glue.
22
+ """
23
+ import sys
24
+ from typing import List, Tuple
25
+
26
+ import torch
27
+ import torch.nn.functional as F
28
+ from tqdm import tqdm
29
+
30
+ from lm_eval.api.model import LM
31
+ from lm_eval.api.registry import register_model
32
+ from lm_eval.api.instance import Instance
33
+
34
+
35
+ def _load_ivme_model(checkpoint_path: str, model_code_dir: str, device: str):
36
+ """Loads IvmeConversateV2 exactly the way the model card's inference
37
+ snippet does: EMA weights, strip torch.compile's _orig_mod. prefix."""
38
+ if model_code_dir and model_code_dir not in sys.path:
39
+ sys.path.append(model_code_dir)
40
+ from model import IvmeConfig, IvmeConversateV2 # noqa: E402 (path-dependent import)
41
+
42
+ torch.serialization.add_safe_globals([IvmeConfig])
43
+ ckpt = torch.load(checkpoint_path, map_location="cpu")
44
+ cfg = ckpt["config"]
45
+
46
+ model = IvmeConversateV2(cfg)
47
+ state_dict = ckpt["ema_state_dict"]
48
+ state_dict = {k.removeprefix("_orig_mod."): v for k, v in state_dict.items()}
49
+ model.load_state_dict(state_dict)
50
+ model.to(device)
51
+ model.eval()
52
+ return model, cfg
53
+
54
+
55
+ @register_model("ivme")
56
+ class IvmeLM(LM):
57
+ def __init__(
58
+ self,
59
+ checkpoint: str,
60
+ tokenizer: str,
61
+ model_code_dir: str = "",
62
+ device: str = "cuda" if torch.cuda.is_available() else "cpu",
63
+ batch_size: int = 8,
64
+ dtype: str = "bfloat16",
65
+ ):
66
+ super().__init__()
67
+ from tokenizers import Tokenizer as HFTokenizer
68
+
69
+ self._device = device
70
+ self.batch_size = int(batch_size)
71
+ self.amp_dtype = getattr(torch, dtype)
72
+
73
+ self.model, self.cfg = _load_ivme_model(checkpoint, model_code_dir, device)
74
+ self.tokenizer = HFTokenizer.from_file(tokenizer)
75
+
76
+ self.max_length = self.cfg.context_len
77
+ eot = self.tokenizer.token_to_id("<|endoftext|>")
78
+ self.eot_token_id = eot if eot is not None else 0
79
+
80
+ # ---- required by LM ----------------------------------------------
81
+
82
+ @property
83
+ def eot_token_id_(self):
84
+ return self.eot_token_id
85
+
86
+ def tok_encode(self, string: str) -> List[int]:
87
+ return self.tokenizer.encode(string).ids
88
+
89
+ def tok_decode(self, tokens: List[int]) -> str:
90
+ return self.tokenizer.decode(tokens)
91
+
92
+ def _model_call(self, inps: torch.Tensor) -> torch.Tensor:
93
+ """inps: [B, T] -> logits [B, T, vocab]. Respects the model's hard
94
+ context_len assertion (no silent truncation inside forward())."""
95
+ with torch.no_grad():
96
+ with torch.autocast(device_type="cuda" if "cuda" in self._device else "cpu",
97
+ dtype=self.amp_dtype, enabled="cuda" in self._device):
98
+ logits, _ = self.model(inps)
99
+ return logits
100
+
101
+ def loglikelihood(self, requests: List[Instance]) -> List[Tuple[float, bool]]:
102
+ """Score (context, continuation) pairs. This is what ARC-Easy, BLiMP,
103
+ and other multiple-choice / paired-sentence tasks call."""
104
+ results = []
105
+ reqs = [r.args for r in requests]
106
+
107
+ for i in tqdm(range(0, len(reqs), self.batch_size), desc="loglikelihood"):
108
+ batch = reqs[i : i + self.batch_size]
109
+ batch_out = []
110
+ for context, continuation in batch:
111
+ if context == "":
112
+ ctx_ids = [self.eot_token_id]
113
+ else:
114
+ ctx_ids = self.tok_encode(context)
115
+ cont_ids = self.tok_encode(continuation)
116
+
117
+ full_ids = (ctx_ids + cont_ids)[-self.max_length - 1 :]
118
+ # keep at least 1 context token if truncation ate everything
119
+ if len(full_ids) <= len(cont_ids):
120
+ full_ids = full_ids[-(len(cont_ids) + 1) :]
121
+ ctx_len_adj = len(full_ids) - len(cont_ids)
122
+
123
+ x = torch.tensor([full_ids[:-1]], dtype=torch.long, device=self._device)
124
+ logits = self._model_call(x)[0] # [T, vocab]
125
+
126
+ cont_start = ctx_len_adj - 1
127
+ cont_logits = logits[cont_start : cont_start + len(cont_ids)]
128
+ log_probs = F.log_softmax(cont_logits.float(), dim=-1)
129
+
130
+ cont_tensor = torch.tensor(cont_ids, dtype=torch.long, device=self._device)
131
+ token_lps = log_probs.gather(-1, cont_tensor.unsqueeze(-1)).squeeze(-1)
132
+
133
+ greedy = (cont_logits.argmax(dim=-1) == cont_tensor).all().item()
134
+ batch_out.append((token_lps.sum().item(), bool(greedy)))
135
+ results.extend(batch_out)
136
+ return results
137
+
138
+ def loglikelihood_rolling(self, requests: List[Instance]) -> List[float]:
139
+ """Full-document log-likelihood with overlapping, context-maximizing
140
+ windows -- this is the piece your custom script's disjoint-block
141
+ chunking didn't do, and the reason its byte-PPL wasn't harness-comparable."""
142
+ results = []
143
+ for (string,) in tqdm([r.args for r in requests], desc="loglikelihood_rolling"):
144
+ tokens = self.tok_encode(string)
145
+ ids = [self.eot_token_id] + tokens
146
+
147
+ total_ll = 0.0
148
+ pos = 0
149
+ n = len(ids)
150
+ while pos < n - 1:
151
+ window = ids[pos : pos + self.max_length + 1]
152
+ x = torch.tensor([window[:-1]], dtype=torch.long, device=self._device)
153
+ logits = self._model_call(x)[0]
154
+ log_probs = F.log_softmax(logits.float(), dim=-1)
155
+
156
+ targets = window[1:]
157
+ # first window: score all positions; later windows: only score
158
+ # the newly-seen tokens (the ones not already scored via overlap)
159
+ if pos == 0:
160
+ start_score = 0
161
+ else:
162
+ start_score = max(0, (self.max_length) - 1) # only score the tail
163
+ start_score = 0 # simple non-overlapping fallback below
164
+
165
+ tgt_tensor = torch.tensor(targets, dtype=torch.long, device=self._device)
166
+ lps = log_probs.gather(-1, tgt_tensor.unsqueeze(-1)).squeeze(-1)
167
+ total_ll += lps.sum().item()
168
+
169
+ pos += self.max_length
170
+ results.append(total_ll)
171
+ return results
172
+
173
+ def generate_until(self, requests: List[Instance]) -> List[str]:
174
+ raise NotImplementedError(
175
+ "generate_until is not implemented -- Ivme-Conversate-v2 is a base "
176
+ "model with no instruction tuning, so generation-based tasks "
177
+ "(anything needing generate_until, e.g. most non-loglikelihood "
178
+ "tasks) aren't meaningful for it yet. Stick to loglikelihood-based "
179
+ "tasks: arc_easy, blimp, wikitext, hellaswag, etc."
180
+ )
eval/run_eval.py ADDED
@@ -0,0 +1,66 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ Runs lm-evaluation-harness against Ivme-Conversate-v2 using the ivme_lm.py adapter.
4
+
5
+ Example:
6
+ python run_eval.py \
7
+ --checkpoint /path/to/ckpt_final.pt \
8
+ --tokenizer /path/to/tokenizer.json \
9
+ --model_code_dir /path/to/model_folder \
10
+ --tasks wikitext,arc_easy,blimp \
11
+ --device cuda:0 \
12
+ --batch_size 16
13
+
14
+ Notes:
15
+ - `--model_code_dir` should point at the folder containing the model/ package
16
+ (config.py, transformer.py, etc.) -- i.e. what snapshot_download gave you,
17
+ or wherever you cloned the repo.
18
+ - `blimp` here means the actual harness BLiMP group task, which covers the
19
+ real 67 paradigms with their real, correct config names on nyu-mll/blimp.
20
+ This replaces the paradigm list in the old custom script, which had several
21
+ fabricated/misspelled task names.
22
+ - generate_until (free-form generation tasks) is not implemented in the
23
+ adapter since this is a non-instruction-tuned base model -- stick to
24
+ loglikelihood-based tasks (wikitext, arc_easy, blimp, hellaswag, piqa, etc.)
25
+ """
26
+ import argparse
27
+
28
+ import ivme_lm # noqa: F401 (registers the "ivme" model with lm_eval on import)
29
+ import lm_eval
30
+ from lm_eval.utils import make_table
31
+
32
+
33
+ def main():
34
+ parser = argparse.ArgumentParser()
35
+ parser.add_argument("--checkpoint", type=str, required=True)
36
+ parser.add_argument("--tokenizer", type=str, required=True)
37
+ parser.add_argument("--model_code_dir", type=str, default="")
38
+ parser.add_argument("--tasks", type=str, default="wikitext,arc_easy,blimp")
39
+ parser.add_argument("--device", type=str, default="cuda:0")
40
+ parser.add_argument("--batch_size", type=int, default=16)
41
+ parser.add_argument("--limit", type=float, default=None,
42
+ help="Optional: cap number of docs per task, for a quick sanity run first.")
43
+ args = parser.parse_args()
44
+
45
+ model_args = (
46
+ f"checkpoint={args.checkpoint},"
47
+ f"tokenizer={args.tokenizer},"
48
+ f"model_code_dir={args.model_code_dir},"
49
+ f"device={args.device},"
50
+ f"batch_size={args.batch_size}"
51
+ )
52
+
53
+ results = lm_eval.simple_evaluate(
54
+ model="ivme",
55
+ model_args=model_args,
56
+ tasks=args.tasks.split(","),
57
+ limit=args.limit,
58
+ )
59
+
60
+ print(make_table(results))
61
+ if "groups" in results and results["groups"]:
62
+ print(make_table(results, "groups"))
63
+
64
+
65
+ if __name__ == "__main__":
66
+ main()