Download ecce_vector/corpus.py from andyoneal/ecce-vectors: direct link, hf CLI and curl.
- Browser
- Download file 3.31 kB
-
https://huggingface.co/andyoneal/ecce-vectors/resolve/main/ecce_vector/corpus.py
- Command line
-
hf download hf://andyoneal/ecce-vectors/ecce_vector/corpus.py
-
curl -L -o corpus.py https://huggingface.co/andyoneal/ecce-vectors/resolve/main/ecce_vector/corpus.py
3.31 kB
| """The fit corpus: 16 short passages (8 roleplay, 8 novel prose), split 4 + 4 of each into a fit split and a | |
| held-out split. | |
| Each item becomes one sequence: a chat-template user turn asking the model to continue a passage (128 tokens | |
| of it), then 128 reference tokens as the model's turn. The fit averages the fine-tune's edit over every | |
| position of the fit split; the score compares next-token distributions at the reference positions. | |
| The item texts and reference token ids are stored in data/fit_corpus.json, so the prompts are identical | |
| wherever this runs. | |
| """ | |
| from __future__ import annotations | |
| import json | |
| from pathlib import Path | |
| import numpy as np | |
| DATA = Path(__file__).parent / "data" / "fit_corpus.json" | |
| SPLITS = ("fit", "held_out") | |
| class Corpus: | |
| """Token arrays and position masks for both splits. | |
| ids[s] (8, T) token ids, right-padded | |
| valid[s] per sequence, the number of real tokens T_valid (the sequences are cut to the shortest) | |
| starts[s] per sequence, where the reference turn starts | |
| fit_mask[s] (8, T) 1 at the positions a vector is fitted over: 1 <= t < T_valid (BOS and padding excluded) | |
| rows[s] (sequence, first, last): the positions whose next-token prediction is scored | |
| spans[s] per sequence, its [start, end) in the concatenated scored rows | |
| """ | |
| def __init__(self, tokenizer, path: Path = DATA, turns: dict[str, list[int]] | None = None): | |
| data = json.loads(Path(path).read_text()) | |
| self.items = data["items"] | |
| n_ref = data["reference_tokens"] | |
| pad = tokenizer.pad_token_id if tokenizer.pad_token_id is not None else 0 | |
| self.ids, self.valid, self.starts, self.fit_mask, self.rows, self.spans = {}, {}, {}, {}, {}, {} | |
| for s in SPLITS: | |
| seqs, starts, valid = [], [], [] | |
| for reg in ("roleplay", "narrative"): | |
| group = [] | |
| for it in (i for i in self.items if i["split"] == s and i["register"] == reg): | |
| user = it["framing"].replace("{{TEXT}}", it["context"]) | |
| prompt = tokenizer.apply_chat_template([{"role": "user", "content": user}], | |
| add_generation_prompt=True, return_dict=True)["input_ids"] | |
| ref = (turns[it["id"]] if turns else it["reference_ids"])[:n_ref] | |
| group.append(list(prompt) + list(ref)) | |
| starts.append(len(prompt)) | |
| # each register's sequences are cut to that register's shortest | |
| T = min(len(x) for x in group) | |
| seqs += [x[:T] for x in group] | |
| valid += [T] * len(group) | |
| self.valid[s] = valid | |
| self.starts[s] = starts | |
| width = max(self.valid[s]) | |
| self.ids[s] = np.array([x + [pad] * (width - len(x)) for x in seqs]) | |
| self.fit_mask[s] = np.array([[1.0 if 1 <= t < v else 0.0 for t in range(width)] for v in self.valid[s]], | |
| np.float32) | |
| self.rows[s] = [(i, starts[i] - 1, self.valid[s][i] - 1) for i in range(len(seqs))] | |
| spans, o = [], 0 | |
| for _, a, b in self.rows[s]: | |
| spans.append((o, o + b - a)) | |
| o += b - a | |
| self.spans[s] = spans | |