Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
122 lines
5.5 KiB
Python
122 lines
5.5 KiB
Python
"""Prompt markup: the text part. Pure -- strings in, spans out, tensors built.
|
|
|
|
Two markups, stripped in ONE left-to-right pass so every span indexes the
|
|
same clean text (stripping them one after another left the first list's
|
|
offsets pointing into a string that no longer existed):
|
|
|
|
* `(phrase:1.5)` / `[phrase:1.5]` -- weight. A logit bias of log(w) on that
|
|
phrase's key positions: softmax(logits + log w) multiplies that key's share
|
|
of the attention by w, which is what a weight is supposed to mean.
|
|
* `[A|B]` -- simultaneous actions: two things that happen at the same time.
|
|
The prompt the model normally reads holds both ("A while B"); `variants` holds
|
|
one prompt per option with the others left out, for the sampler to answer
|
|
separately and merge (Step merging > Simultaneous actions).
|
|
|
|
Weighting only exists because the text is IN the attention sequence (H3's
|
|
single packed stream). Scaling embeddings would not work: Qwen's rows are
|
|
contextual hidden states, with no per-word magnitude to turn up.
|
|
"""
|
|
|
|
import math
|
|
import re
|
|
|
|
_NUM = r"-?(?:\d+\.\d*|\.\d+|\d+)"
|
|
_WEIGHTED = re.compile(r"(?<!\\)\(([^():]*?):\s*(" + _NUM + r")\s*\)")
|
|
_BRACKET_WEIGHTED = re.compile(r"(?<!\\)\[([^\[\]@|]*?):\s*(" + _NUM + r")\s*\]")
|
|
_TYPED_ALTERNATIVES = re.compile(r"(?<!\\)\[([^\[\]|]+(?:\|[^\[\]|]+)+)\]")
|
|
OPEN, SEP, CLOSE = "⟦", "¦", "⟧" # core/shortcuts: how the app carries the options once it has split the raw text
|
|
_ALTERNATIVES = re.compile(OPEN + "([^" + OPEN + SEP + CLOSE + "]+(?:" + SEP + "[^" + OPEN + SEP + CLOSE + "]+)+)" + CLOSE)
|
|
_MARKUP = re.compile("|".join(p.pattern for p in (_ALTERNATIVES, _WEIGHTED, _BRACKET_WEIGHTED)))
|
|
|
|
# What the main prompt (the one read outside the merged steps) holds of an [A|B]: both joined, A only or B only.
|
|
MAIN_MODES = {"while": " while ", "space": " ", "first": None, "second": None}
|
|
MAX_OPTIONS = 2
|
|
# Below this a weight means "remove": log(0) is -inf, which is NaN the moment a
|
|
# query attends to nothing else.
|
|
MIN_WEIGHT = 1e-3
|
|
MASKED_BIAS = -30.0
|
|
# Past this a phrase wins every query regardless of content: the prompt reads as
|
|
# collapsing onto one word.
|
|
MAX_ABS_BIAS = 6.0
|
|
# Overlapping spans (a phrase and one of its own words) add, bounded at this
|
|
# multiple of the strongest single span -- a lone (word:2.0) still means 2.0.
|
|
OVERLAP_HEADROOM = 1.5
|
|
|
|
|
|
def _options(raw):
|
|
return [o.strip() for o in raw.split(SEP) if o.strip()]
|
|
|
|
|
|
def parse(text, pick=None, main="while"):
|
|
"""-> {"clean", "weighted": [(c0, c1, w)], "variants": [clean text per option], "extra": n}, char spans
|
|
into `clean`. `clean` holds the options as `main` says ("while": "A while B", "space": "A B", "first"/"second":
|
|
that option alone); with `pick` k it holds option k of each [A|B] instead (how the variants are made)."""
|
|
out = {"clean": text or "", "weighted": [], "variants": [], "extra": 0}
|
|
if not text or ("(" not in text and "[" not in text and OPEN not in text):
|
|
return out
|
|
text = _TYPED_ALTERNATIVES.sub(lambda m: OPEN + SEP.join(m.group(1).split("|")) + CLOSE, text)
|
|
pieces, pos, width, found = [], 0, 0, 0
|
|
for m in _MARKUP.finditer(text):
|
|
pieces.append(text[pos:m.start()])
|
|
width += len(pieces[-1])
|
|
if m.group(1) is not None:
|
|
opts = _options(m.group(1))
|
|
if len(opts) < 2: # "[x|]": nothing to be simultaneous with
|
|
phrase = opts[0] if opts else ""
|
|
else:
|
|
found = max(found, len(opts))
|
|
if pick is None and main in ("first", "second"):
|
|
pick_here = 0 if main == "first" else 1
|
|
elif pick is None:
|
|
pick_here = None
|
|
else:
|
|
pick_here = pick
|
|
phrase = (MAIN_MODES.get(main, " while ").join(opts) if pick_here is None else opts[min(pick_here, len(opts) - 1)])
|
|
else:
|
|
phrase = m.group(2) if m.group(2) is not None else m.group(4)
|
|
weight = m.group(3) if m.group(3) is not None else m.group(5)
|
|
out["weighted"].append((width, width + len(phrase), float(weight)))
|
|
pieces.append(phrase)
|
|
width += len(phrase)
|
|
pos = m.end()
|
|
pieces.append(text[pos:])
|
|
out["clean"] = "".join(pieces)
|
|
if found >= 2 and pick is None:
|
|
out["variants"] = [parse(text, pick=k)["clean"] for k in range(min(found, MAX_OPTIONS))]
|
|
out["extra"] = max(0, found - MAX_OPTIONS)
|
|
return out
|
|
|
|
|
|
def token_spans(offsets, char_spans):
|
|
"""Char spans -> (tok0, tok1, *rest) using a tokenizer's offset mapping."""
|
|
out = []
|
|
for c0, c1, *rest in char_spans:
|
|
toks = [i for i, (a, b) in enumerate(offsets) if a < c1 and b > c0]
|
|
if toks:
|
|
out.append((toks[0], toks[-1] + 1, *rest))
|
|
return out
|
|
|
|
|
|
def bias_value(weight):
|
|
if weight <= MIN_WEIGHT:
|
|
return MASKED_BIAS
|
|
return max(-MAX_ABS_BIAS, min(MAX_ABS_BIAS, math.log(weight)))
|
|
|
|
|
|
def key_bias(spans, base, seq_len, device, dtype):
|
|
"""[1, 1, 1, seq_len] additive bias for (tok0, tok1, w) spans, prompt at `base`."""
|
|
import torch
|
|
bias, strongest = None, 0.0
|
|
for t0, t1, weight in spans:
|
|
b = bias_value(weight)
|
|
if b == 0.0 or t1 <= t0:
|
|
continue
|
|
if bias is None:
|
|
bias = torch.zeros(1, 1, 1, seq_len, device=device, dtype=dtype)
|
|
bias[..., base + t0:base + t1] += b
|
|
strongest = max(strongest, abs(b))
|
|
if bias is not None:
|
|
limit = min(MAX_ABS_BIAS, strongest * OVERLAP_HEADROOM)
|
|
bias.clamp_(min=-limit, max=limit)
|
|
return bias
|