Download decider/temperature.py from Mapika/decider-2b: direct link, hf CLI and curl.
- Browser
- Download file 7.29 kB
-
https://huggingface.co/Mapika/decider-2b/resolve/main/decider/temperature.py
- Command line
-
hf download hf://Mapika/decider-2b/decider/temperature.py
-
curl -L -o temperature.py https://huggingface.co/Mapika/decider-2b/resolve/main/decider/temperature.py
7.29 kB
| """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) | |