| """spec-decode-ops: fused sampling and speculative-decoding verification. |
| |
| sample() executes the transformers logits pipeline (repetition penalty -> |
| temperature -> top-k -> top-p -> min-p -> categorical draw) in a fused form: |
| one optional segmented sort plus one kernel, sampling by the Gumbel-argmax |
| identity. Sort-free fast paths cover greedy decoding and unfiltered sampling. |
| |
| verify() implements canonical speculative-decoding rejection sampling |
| (Leviathan et al.; Chen et al.). Draft token x_i is accepted with probability |
| min(1, p_target(x_i) / p_draft(x_i)); the first rejection is replaced by a |
| sample from the renormalized residual max(p_target - p_draft, 0); full |
| acceptance appends a bonus token from the target's final position. The |
| emitted token stream is distributed exactly as the target distribution. |
| |
| Randomness is counter-based Philox parameterized by (seed, offset): results |
| are bitwise reproducible for fixed seed, offset, shape, and architecture. |
| """ |
| from typing import Optional, Tuple |
|
|
| import torch |
|
|
| from ._ops import ops |
|
|
|
|
| def sample( |
| logits: torch.Tensor, |
| temperature: float = 1.0, |
| top_k: int = 0, |
| top_p: float = 1.0, |
| min_p: float = 0.0, |
| repetition_penalty: float = 1.0, |
| prev_tokens: Optional[torch.Tensor] = None, |
| seed: int = 0, |
| offset: int = 0, |
| ) -> torch.Tensor: |
| """Sample one token per row of logits. |
| |
| Args: |
| logits: [M, V] f32/bf16/f16 CUDA tensor. |
| temperature: 0 selects greedy argmax; otherwise logits are divided by it. |
| top_k: keep the k highest-probability tokens (0 disables). |
| top_p: nucleus filtering; keep the smallest prefix of the sorted |
| distribution with cumulative probability >= top_p (1.0 disables). |
| min_p: keep tokens with p >= min_p * p_max (0 disables). |
| repetition_penalty: applied to prev_tokens before temperature |
| (l/r for l > 0 else l*r), matching transformers. |
| prev_tokens: [M, P] int64, -1 padded; required if penalty != 1. |
| seed, offset: Philox counter parameters. |
| Returns: |
| [M] int64 token ids. |
| """ |
| logits = logits.contiguous() |
| tokens = torch.empty(logits.shape[0], dtype=torch.int64, device=logits.device) |
| ops.fused_sample(tokens, logits, prev_tokens, float(temperature), int(top_k), |
| float(top_p), float(min_p), float(repetition_penalty), |
| int(seed), int(offset)) |
| return tokens |
|
|
|
|
| def filter_logits( |
| logits: torch.Tensor, |
| temperature: float = 1.0, |
| top_k: int = 0, |
| top_p: float = 1.0, |
| min_p: float = 0.0, |
| repetition_penalty: float = 1.0, |
| prev_tokens: Optional[torch.Tensor] = None, |
| ) -> torch.Tensor: |
| """Return the filtered logits (temperature-scaled, masked to -inf), the |
| same tensor the transformers processor chain would hand to the sampler. |
| Useful for composing filtered distributions with verify().""" |
| logits = logits.contiguous() |
| out = torch.empty(logits.shape, dtype=torch.float32, device=logits.device) |
| ops.fused_filter(out, logits, prev_tokens, float(temperature), int(top_k), |
| float(top_p), float(min_p), float(repetition_penalty)) |
| return out |
|
|
|
|
| def verify( |
| target_logits: torch.Tensor, |
| draft_logits: torch.Tensor, |
| draft_tokens: torch.Tensor, |
| temperature: float = 1.0, |
| seed: int = 0, |
| offset: int = 0, |
| ) -> Tuple[torch.Tensor, torch.Tensor]: |
| """Speculative-decoding verification. |
| |
| Args: |
| target_logits: [B, k+1, V] target-model logits over the draft |
| positions plus one (the bonus position). |
| draft_logits: [B, k, V] draft-model logits at the same positions. |
| draft_tokens: [B, k] int64 tokens the draft model sampled. |
| temperature: sampling temperature applied to both distributions; |
| 0 selects greedy verification (accept while target argmax equals |
| the draft token). |
| Returns: |
| (accept_len [B] int64, out_tokens [B, k+1] int64). For sequence b, |
| out_tokens[b, :accept_len[b]] are accepted draft tokens and |
| out_tokens[b, accept_len[b]] is the correction or bonus token, so |
| accept_len[b] + 1 tokens are always emitted. |
| """ |
| B, kp1, V = target_logits.shape |
| k = kp1 - 1 |
| accept_len = torch.empty(B, dtype=torch.int64, device=target_logits.device) |
| out_tokens = torch.empty(B, k + 1, dtype=torch.int64, device=target_logits.device) |
| ops.spec_verify(accept_len, out_tokens, target_logits.contiguous(), |
| draft_logits.contiguous(), draft_tokens.contiguous(), |
| float(temperature), int(seed), int(offset)) |
| return accept_len, out_tokens |
|
|
|
|
| __all__ = ["sample", "filter_logits", "verify"] |
|
|