File size: 7,294 Bytes
533964d | 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 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 | """Answer temperatures from decider_config.json (1.4.0).
Every answer is softmax(logits / T) over its option letters. decider_config.json sets T:
"temperature": 1.3 one value for every answer (the only form before 1.4.0)
"temperature_by_type": {"choice": 1.48, "noul": 2.22, "score": 1.38}
optional; one value per answer type, a missing type uses "temperature"
"temperature_schema_first": 1.18 optional; the schema cache (questions-first layout), as before
"temperature_schema_first_by_type": {...} optional; per answer type on the schema cache
The answer types are the /v1/systemone question types. A /decide field maps onto them: "choice" -> "choice", "bool" -> "noul",
"scale" -> "score". Plain `Decider.decide` questions (a question and its options, no type) are "choice". A Score question
read with isolated levels (one yes/no row per level) uses the "score" temperature on every one of its level rows: the rows form
one Score answer, and a temperature fitted on Score answers is fitted through that same readout (decider.calibrate).
On the state-first layout the temperature of an answer of type t is temperature_by_type[t], else temperature. On the schema
cache it is temperature_schema_first_by_type[t], else temperature_schema_first, else (no schema-first value at all) the
state-first temperature of t. An explicit override (Decider(temperature=...), DECIDER_TEMPERATURE) replaces the state-first
"temperature" and switches temperature_by_type off, so the override is the one temperature of every state-first answer, as it
was before 1.4.0.
A config without a by-type map gives every path the single number it gave in 1.3.0, and that number reaches the engines as the
same Python float, so the probabilities are bit-identical to 1.3.0.
"""
import math
TYPES = ("choice", "noul", "score")
FIELD_TYPES = {"choice": "choice", "bool": "noul", "scale": "score"} # /decide field type -> answer type
_KEYS_TEXT = ('the keys are "choice", "noul" and "score" (a /decide "bool" field is "noul", a "scale" field is "score"; '
'a /v1/systemone "bool" question is "noul")')
def positive(value, where):
"""A temperature: a number (or a numeric string, as float() reads it, which 1.3.0 accepted for "temperature") that is finite
and > 0. Raises ValueError naming `where`."""
if isinstance(value, bool):
raise ValueError(f"{where} must be a finite number > 0, got {value!r}")
try:
v = float(value)
except (TypeError, ValueError):
raise ValueError(f"{where} must be a finite number > 0, got {value!r}") from None
if not math.isfinite(v) or v <= 0:
raise ValueError(f"{where} must be a finite number > 0, got {value!r}")
return v
def by_type(m, where):
"""Validate a {answer type: temperature} map. None -> {}. Unknown keys, non-numbers and values that are not finite and > 0
raise ValueError."""
if m is None:
return {}
if not isinstance(m, dict):
raise ValueError(f"{where} must be a map {{answer type: temperature}}, got {type(m).__name__}; " + _KEYS_TEXT)
out = {}
for k, v in m.items():
if k not in TYPES:
raise ValueError(f"{where} has the unknown key {k!r}; " + _KEYS_TEXT)
if isinstance(v, bool) or not isinstance(v, (int, float)):
raise ValueError(f"{where}[{k!r}] must be a finite number > 0, got {v!r}")
out[k] = positive(v, f"{where}[{k!r}]")
return out
def from_config(cfg, temperature=None, temperature_by_type=None):
"""-> ((T, by_type) for the state-first layout, (T, by_type) for the schema cache).
temperature / temperature_by_type: explicit overrides (Decider arguments, DECIDER_TEMPERATURE). An explicit temperature
without an explicit map switches the config's map off (see the module docstring)."""
cfg = cfg or {}
where = "decider_config.json"
if temperature is not None:
T = positive(temperature, "temperature")
m = by_type(temperature_by_type, "temperature_by_type") if temperature_by_type is not None else {}
else:
T = positive(cfg.get("temperature", 1.0), f'{where} "temperature"')
m = (by_type(temperature_by_type, "temperature_by_type") if temperature_by_type is not None
else by_type(cfg.get("temperature_by_type"), f'{where} "temperature_by_type"'))
ms = by_type(cfg.get("temperature_schema_first_by_type"), f'{where} "temperature_schema_first_by_type"')
if "temperature_schema_first" in cfg:
schema = (positive(cfg["temperature_schema_first"], f'{where} "temperature_schema_first"'), ms)
else:
schema = (T, {**m, **ms})
return (T, m), schema
def effective(T, m):
"""{answer type: the temperature it gets} for reporting (/health, the ready line)."""
return {t: m.get(t, T) for t in TYPES}
def for_types(T, m, types):
"""One temperature per slot, or the scalar T itself when there is no map (the 1.3.0 call)."""
if not m:
return T
return [m.get(t, T) for t in types]
def item_types(it):
"""The answer type of every slot of a prompt item ("types", set where the item is built); None for an item without it."""
ts = it.get("types")
return list(ts) if ts is not None else [None] * len(it["slots"])
def for_items(T, m, items):
"""The `temperature` argument of Engine.score_items / score_shared: the scalar T when there is no map (the 1.3.0 call),
else one list of per-slot temperatures per item."""
if not m:
return T
return [[m.get(t, T) for t in item_types(it)] for it in items]
def slot_temperatures(temperature, items):
"""Engine side. temperature: a scalar (returned as it is), or one entry per item, each a scalar or one value per slot of
that item. -> the scalar, or a flat list with one temperature per slot in item order."""
if not isinstance(temperature, (list, tuple)):
return temperature
if len(temperature) != len(items):
raise ValueError(f"temperature: {len(temperature)} entries for {len(items)} items")
flat = []
for t, it in zip(temperature, items):
n = len(it["slots"])
if isinstance(t, (list, tuple)):
if len(t) != n:
raise ValueError(f"temperature: {len(t)} values for an item with {n} slots")
flat += list(t)
else:
flat += [t] * n
return flat
def item_slice(temperature, lo, hi):
"""The per-item temperature entries of items[lo:hi] (a scalar is shared by every item)."""
return temperature[lo:hi] if isinstance(temperature, (list, tuple)) else temperature
def scaled_softmax(lg, temperature):
"""softmax(lg / T) over the last axis. A scalar T is the 1.3.0 expression unchanged; a list gives one T per row of lg."""
import torch
if isinstance(temperature, (list, tuple)):
if len(temperature) != lg.shape[0]:
raise ValueError(f"temperature: {len(temperature)} values for {lg.shape[0]} slots")
t = torch.tensor(temperature, dtype=lg.dtype).to(lg.device, non_blocking=True)[:, None]
return torch.softmax(lg / t, -1)
return torch.softmax(lg / temperature, -1)
|