Ruurd's picture
Guarantee a minimum token commitment quota each APD step
942fb38 verified
Raw History Blame Contribute Delete
4.39 kB
"""Optional confidence-based token commitment, independent of token scoring."""
import math
import torch
def confidence_threshold(step: int, steps: int, aggressiveness: float = 0.0) -> float:
"""CAI-dLLM cosine shape, 15% warmup, endpoints 0.90 and 0.50.
Normalize the discrete block's first/last steps to 0/1 so its final step
reaches the requested endpoint. A one-step block uses the final threshold.
Aggressiveness scales both endpoints by (1-a): 0 preserves the old schedule,
1 admits every finite-confidence candidate. Commitment guards still apply.
"""
aggressiveness = float(aggressiveness)
if not math.isfinite(aggressiveness) or not 0.0 <= aggressiveness <= 1.0:
raise ValueError("APD aggressiveness must be finite and between 0 and 1")
if steps < 1 or not 0 <= step < steps:
raise ValueError("Expected a valid zero-based step within the block")
if steps == 1:
return 0.50 * (1.0 - aggressiveness)
progress = step / (steps - 1)
phase = max(0.0, (progress - 0.15) / 0.85)
original = 0.50 + 0.20 * (1.0 + math.cos(math.pi * phase))
return original if aggressiveness == 0 else original * (1.0 - aggressiveness)
def selected_token_probabilities(logits, token_ids):
"""Full-vocabulary model probabilities, without top-k/temperature scaling."""
scores = logits.float()
return (scores.gather(1, token_ids[:, None]).squeeze(1) - scores.logsumexp(-1)).exp()
def resolve_mask_predictions(logits, selected, confidence, pending, mask_token_id):
"""Output-validity fallback if a decoder selects MASK itself.
Use the same forward's highest-scoring non-mask token only for those rows.
Ordinary sampled/Viterbi-selected tokens and their confidence stay unchanged.
"""
invalid = pending & (selected == mask_token_id)
if not bool(invalid.any()):
return selected, confidence
if logits.shape[-1] < 2:
raise ValueError("APD cannot resolve a vocabulary containing only MASK")
rows = invalid.nonzero(as_tuple=True)[0]
top_ids = logits[rows].topk(2, dim=-1).indices
alternative = torch.where(top_ids[:, 0] == mask_token_id, top_ids[:, 1], top_ids[:, 0])
selected, confidence = selected.clone(), confidence.clone()
selected[rows] = alternative
confidence[rows] = selected_token_probabilities(logits[rows], alternative)
return selected, confidence
def commitment_mask(confidence, pending, step, steps, *, endings=None, delay_endings=False, aggressiveness=0.0, minimum_tokens=1):
"""Commit all qualifying pending positions; force progress and final cleanup.
With ending retention delay, postpone endings while ordinary predictions
would remain uncommitted. The fallback prefers an ordinary token in that
case. Fill a minimum per-step quota using highest-confidence pending rows.
Ordinary rows precede endings when ending retention is delayed.
"""
if not isinstance(minimum_tokens, int) or minimum_tokens < 1:
raise ValueError("APD minimum tokens must be a positive integer")
threshold = confidence_threshold(step, steps, aggressiveness)
if step == steps - 1:
return pending.clone()
finite = torch.isfinite(confidence)
chosen = pending & finite & (confidence >= threshold)
ordinary = pending if endings is None else pending & ~endings
if delay_endings and endings is not None and bool((ordinary & ~chosen).any()):
chosen &= ~endings
required = min(minimum_tokens, int(pending.sum()))
needed = max(0, required - int(chosen.sum()))
if needed:
# Preserve the old ending delay while satisfying the mandatory quota:
# exhaust ordinary rows before filling from endings. Nonfinite scores
# rank last; stable ordering retains the old first-index fallback.
pools = [ordinary, pending & ~ordinary] if delay_endings and endings is not None else [pending]
for pool in pools:
indices = (pool & ~chosen).nonzero(as_tuple=True)[0]
count = min(needed, len(indices))
if count:
scores = confidence[indices].masked_fill(~finite[indices], -torch.inf)
ranked = scores.argsort(descending=True, stable=True)
chosen[indices[ranked[:count]]] = True
needed -= count
if needed == 0:
break
return chosen