File size: 2,575 Bytes
bea429c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
"""The released app.py choice-scorer rendering, padding, and calibration."""

from __future__ import annotations

import math
from collections.abc import Sequence

MAX_LENGTH = 256
TEMPERATURE = 2.35


def encode(tokenizer, state: str, question: str, option: str, max_length: int = MAX_LENGTH) -> list[int]:
    """Keep the question/option tail; truncate the state from its end, as upstream does."""
    tail = tokenizer("\n\nQuestion:\n" + question + "\n\nOption:\n" + option, add_special_tokens=False)["input_ids"]
    if len(tail) >= max_length:
        return tail[-max_length:]
    head = tokenizer("State:\n" + state, add_special_tokens=False)["input_ids"]
    return head[: max_length - len(tail)] + tail


def encode_options(tokenizer, state: str, question: str, options: Sequence[str]) -> list[list[int]]:
    if len(options) < 2:
        raise ValueError("the native app requires at least two options")
    if any(not option.strip() for option in options):
        raise ValueError("options must be nonempty")
    return [encode(tokenizer, state, question, option) for option in options]


def pad_batch(
    sequences: Sequence[Sequence[int]], pad_id: int, length: int | None = None
) -> tuple[list[list[int]], list[list[int]]]:
    """Right-pad to the longest candidate (or fixed export length), with true token masks."""
    if not sequences:
        raise ValueError("empty candidate batch")
    width = min(max(map(len, sequences)), MAX_LENGTH) if length is None else length
    if width > MAX_LENGTH or width < max(map(len, sequences)):
        raise ValueError("invalid padding length")
    ids = [list(row) + [pad_id] * (width - len(row)) for row in sequences]
    masks = [[1] * len(row) + [0] * (width - len(row)) for row in sequences]
    return ids, masks


def softmax(logits: Sequence[float], temperature: float = TEMPERATURE) -> list[float]:
    if not logits or temperature <= 0:
        raise ValueError("need logits and a positive temperature")
    scaled = [float(value) / temperature for value in logits]
    peak = max(scaled)
    weights = [math.exp(value - peak) for value in scaled]
    total = sum(weights)
    return [weight / total for weight in weights]


def choose(options: Sequence[str], logits: Sequence[float]) -> dict:
    if len(options) != len(logits):
        raise ValueError("one scalar logit is required per option")
    probabilities = softmax(logits)
    index = max(range(len(options)), key=probabilities.__getitem__)
    return {"selected_index": index, "selected_option": options[index], "probabilities": probabilities}