YuE2-3B-OrbitQuant-W4A4 / src /runtime /yue2 /captured_sampling.py
WaveCut's picture
Extend request-local captured sampling to ABC planning
e67a37b verified
Raw History Blame Contribute Delete
5.05 kB
"""Request-owned CUDA graph for ABC and semantic top-k/top-p sampling."""
import torch
from .protocol import MUSIC_END, CODEC_OFFSET, CODEC_SIZE, ABC_END, EOD
class CapturedSampler:
"""Return one vocabulary ID in shared output storage, valid until next call.
The caller owns the RNG and already validates the generated history. Capture
and warmup restore its state; only real replays consume random numbers.
"""
@torch.inference_mode()
def __init__(self, logits, sampling, generator, *, phase="semantic"):
if phase not in {"abc", "semantic"}:
raise ValueError("Captured sampling phase must be abc or semantic")
if logits.device.type != "cuda" or logits.ndim != 2 or logits.shape[0] != 1:
raise ValueError("Captured sampling requires one CUDA logit row after CFG")
required = ABC_END + 1 if phase == "abc" else CODEC_OFFSET + CODEC_SIZE
if logits.shape[-1] < required or sampling.temperature <= 0:
raise ValueError("Captured sampling requires the phase vocabulary and positive temperature")
# Torch 2.10 can report a CUDA generator without its device index.
if (generator.device.type != "cuda" or
generator.device.index is not None and generator.device.index != logits.device.index):
raise ValueError("Sampling generator and logits must share a CUDA device")
self.sampling = sampling
self.phase = phase
self.offset = 0 if phase == "abc" else MUSIC_END
self.eos_index = EOD if phase == "abc" else 0
self.generator = generator
self.graph = self.output = None
self.closed = False
size = EOD + 1 if phase == "abc" else CODEC_OFFSET + CODEC_SIZE - MUSIC_END
self.logits = torch.zeros((1, size),
device=logits.device, dtype=torch.float32)
self.recent = torch.full((1, sampling.penalty_window), -1,
device=logits.device, dtype=torch.long)
self.allow_eos = torch.ones((), device=logits.device, dtype=torch.bool)
saved = generator.get_state()
try:
with torch.cuda.device(logits.device):
current = torch.cuda.current_stream()
warmup = torch.cuda.Stream()
warmup.wait_stream(current)
with torch.cuda.stream(warmup):
for _ in range(3):
self._compute()
current.wait_stream(warmup)
torch.cuda.synchronize()
self.graph = torch.cuda.CUDAGraph()
self.graph.register_generator_state(generator)
with torch.cuda.graph(self.graph):
self.output = self._compute()
finally:
generator.set_state(saved)
def _compute(self):
sampling = self.sampling
scores = self.logits.clone()
scores[..., self.eos_index] = torch.where(self.allow_eos, scores[..., self.eos_index], -torch.inf)
if sampling.repetition_penalty != 1:
frequency = torch.zeros_like(scores)
frequency.scatter_add_(-1, self.recent.clamp_min(0), (self.recent >= 0).float())
alpha = sampling.repetition_penalty ** frequency
scores = torch.where(scores < 0, scores * alpha, scores / alpha)
if sampling.temperature != 1:
scores /= sampling.temperature
values, indices = scores.topk(min(sampling.top_k, scores.shape[-1]))
if sampling.top_p < 1:
probabilities = values.softmax(-1)
removed = probabilities.cumsum(-1) - probabilities > sampling.top_p
removed[..., :1] = False
values = values.masked_fill(removed, -torch.inf)
choice = torch.multinomial(values.softmax(-1), 1, generator=self.generator)
selected = indices.gather(-1, choice)
if self.phase == "abc":
return torch.where(selected == EOD, ABC_END, selected)
return selected + MUSIC_END
@torch.inference_mode()
def __call__(self, logits, history, step):
if self.closed:
raise RuntimeError("CapturedSampler is closed")
recent = [token - self.offset for token in history[-self.sampling.penalty_window:]]
recent += [-1] * (self.sampling.penalty_window - len(recent))
self.recent.copy_(torch.tensor([recent], dtype=torch.long, device="cpu"))
if self.phase == "abc":
# Ordinary IDs stay unchanged; only the separate EOS is appended.
self.logits[..., :EOD].copy_(logits[..., :EOD])
self.logits[..., EOD].copy_(logits[..., ABC_END])
else:
self.logits.copy_(logits[..., MUSIC_END:CODEC_OFFSET + CODEC_SIZE])
self.allow_eos.fill_(step >= self.sampling.min_tokens)
self.graph.replay()
return self.output
def close(self):
self.graph = self.output = self.generator = None
self.logits = self.recent = self.allow_eos = None
self.closed = True