Download runtime/scripts/native_scoring.py from flymy-ai/decision-2b-preview: direct link, hf CLI and curl.
- Browser
- Download file 4.48 kB
-
https://huggingface.co/flymy-ai/decision-2b-preview/resolve/main/runtime/scripts/native_scoring.py
- Command line
-
hf download hf://flymy-ai/decision-2b-preview/runtime/scripts/native_scoring.py
-
curl -L -o native_scoring.py https://huggingface.co/flymy-ai/decision-2b-preview/resolve/main/runtime/scripts/native_scoring.py
4.48 kB
| """Option likelihoods under one prefix, with the prefix computed once. | |
| The likelihood readout scores each option's text as a continuation of the same chat-template prompt. The | |
| first implementation re-ran the whole prompt for every option, so a hard item with a 1,400-token prompt and | |
| four options put about 5,600 tokens through the model while the report counted 1,400. Here the prompt goes | |
| through once, its key/value cache is reused for every option, and `processed_tokens` is exactly what the | |
| model saw: the prompt plus the option continuations. The numbers are the same as the uncached computation | |
| (`tests/test_native_scoring.py`); only the work and the honest token count change. | |
| """ | |
| import copy | |
| import math | |
| def score_options(model, prefix_ids, pieces, device, max_tokens, batch=8): | |
| """(probabilities, processed_tokens) over the options, or (None, widest) when an option would not fit. | |
| Probabilities are the softmax of the length-normalised log-likelihood of each option's tokens given the | |
| prefix. `pieces` are the options' token ids; options of equal length are scored in one batch of `batch`. | |
| """ | |
| import torch | |
| from transformers import DynamicCache | |
| if not pieces or any(not piece for piece in pieces): | |
| raise ValueError('every option needs at least one token') | |
| widest = len(prefix_ids) + max(len(piece) for piece in pieces) | |
| if widest > max_tokens: | |
| return None, widest | |
| totals = [None] * len(pieces) | |
| with torch.no_grad(): | |
| prefix = torch.tensor([list(prefix_ids)], device=device) | |
| out = model(input_ids=prefix, past_key_values=DynamicCache(), use_cache=True, logits_to_keep=1) | |
| first = torch.log_softmax(out.logits[0, -1].float(), dim=-1) # predicts each option's first token | |
| cache = out.past_key_values | |
| by_length = {} | |
| for i, piece in enumerate(pieces): | |
| by_length.setdefault(len(piece), []).append(i) | |
| for length, indices in by_length.items(): | |
| for start in range(0, len(indices), batch): | |
| chunk = indices[start:start + batch] | |
| ids = torch.tensor([pieces[i] for i in chunk], device=device) | |
| shared = copy.deepcopy(cache) | |
| if len(chunk) > 1: | |
| shared.batch_repeat_interleave(len(chunk)) | |
| logprobs = None | |
| if length > 1: | |
| logits = model(input_ids=ids, past_key_values=shared, use_cache=True).logits.float() | |
| logprobs = torch.log_softmax(logits, dim=-1) # position j predicts token j + 1 | |
| for row, i in enumerate(chunk): | |
| piece = pieces[i] | |
| total = float(first[piece[0]]) | |
| for j in range(1, length): | |
| total += float(logprobs[row, j - 1, piece[j]]) | |
| totals[i] = total | |
| means = [t / len(piece) for t, piece in zip(totals, pieces)] | |
| top = max(means) | |
| weights = [math.exp(m - top) for m in means] | |
| processed = len(prefix_ids) + sum(len(piece) for piece in pieces) | |
| return [w / sum(weights) for w in weights], processed | |
| def score_options_uncached(model, prefix_ids, pieces, device, max_tokens, batch=8): | |
| """The original computation, kept as the reference the cached one is tested against.""" | |
| import torch | |
| widest = len(prefix_ids) + max(len(piece) for piece in pieces) | |
| if widest > max_tokens: | |
| return None, widest | |
| totals = [None] * len(pieces) | |
| by_length = {} | |
| for i, piece in enumerate(pieces): | |
| by_length.setdefault(len(piece), []).append(i) | |
| prefix_tensor = torch.tensor(list(prefix_ids)) | |
| for length, indices in by_length.items(): | |
| for start in range(0, len(indices), batch): | |
| chunk = indices[start:start + batch] | |
| ids = torch.stack([torch.cat([prefix_tensor, torch.tensor(pieces[i])]) for i in chunk]).to(device) | |
| with torch.no_grad(): | |
| logits = model(input_ids=ids, use_cache=False, logits_to_keep=length + 1).logits.float() | |
| logprobs = torch.log_softmax(logits, dim=-1) | |
| for row, i in enumerate(chunk): | |
| totals[i] = float(sum(logprobs[row, j, token] for j, token in enumerate(pieces[i]))) | |
| means = [t / len(piece) for t, piece in zip(totals, pieces)] | |
| top = max(means) | |
| weights = [math.exp(m - top) for m in means] | |
| return [w / sum(weights) for w in weights], sum(len(prefix_ids) + len(piece) for piece in pieces) | |