Signed-off-by: Sy03 <1370724210@qq.com> Signed-off-by: zack <huixindaddy@yahoo.com> Co-authored-by: zack <huixindaddy@yahoo.com>
156 lines
5.2 KiB
Python
156 lines
5.2 KiB
Python
# ruff: noqa: N803
|
|
# SPDX-License-Identifier: Apache-2.0
|
|
# SPDX-FileCopyrightText: Copyright contributors to the vLLM-Omni project
|
|
"""Fused CosyVoice3 repetition-aware sampling for Model Runner V2.
|
|
|
|
One program per row replaces the ~70 small kernels (softmax, full-vocabulary
|
|
sort, cumulative sum, masks, two Gumbel draws, history gather, rejection) of
|
|
the tensor implementation. The nucleus is the top-p prefix of the full
|
|
distribution capped at top-k, found by repeated argmax instead of a sort; a
|
|
draw that repeats within the last ``win_size`` generated tokens is replaced by
|
|
a draw from the full remaining distribution, from a disjoint noise stream.
|
|
"""
|
|
|
|
import torch
|
|
from vllm.triton_utils import tl, triton
|
|
|
|
MAX_FUSED_TOP_K = 64
|
|
|
|
|
|
@triton.jit
|
|
def _gumbel(seed, offset):
|
|
u = tl.maximum(tl.rand(seed, offset), 4.6566127342e-10)
|
|
return -tl.log(-tl.log(u))
|
|
|
|
|
|
@triton.jit
|
|
def _ras_sample_kernel(
|
|
out_ptr,
|
|
logits_ptr,
|
|
logits_stride,
|
|
vocab,
|
|
idx_mapping_ptr,
|
|
temperature_ptr,
|
|
top_k_ptr,
|
|
top_p_ptr,
|
|
seeds_ptr,
|
|
pos_ptr,
|
|
tokens_ptr,
|
|
tokens_stride,
|
|
total_len_ptr,
|
|
prompt_len_ptr,
|
|
default_top_k,
|
|
default_top_p,
|
|
eps,
|
|
threshold,
|
|
win_size,
|
|
HAS_TOP_K: tl.constexpr,
|
|
HAS_TOP_P: tl.constexpr,
|
|
WIN: tl.constexpr,
|
|
BLOCK: tl.constexpr,
|
|
):
|
|
row = tl.program_id(0)
|
|
req = tl.load(idx_mapping_ptr + row).to(tl.int64)
|
|
cols = tl.arange(0, BLOCK)
|
|
mask = cols < vocab
|
|
x = tl.load(logits_ptr + row * logits_stride + cols, mask=mask, other=float("-inf")).to(tl.float32)
|
|
temperature = tl.load(temperature_ptr + req).to(tl.float32)
|
|
if temperature == 0.0:
|
|
tl.store(out_ptr + row, tl.argmax(x, axis=0).to(tl.int64))
|
|
return
|
|
x = x / tl.maximum(temperature, eps)
|
|
peak = tl.max(x, axis=0)
|
|
weights = tl.where(mask, tl.exp(x - peak), 0.0)
|
|
total = tl.sum(weights, axis=0)
|
|
log_probs = x - peak - tl.log(total)
|
|
top_k = tl.load(top_k_ptr + req) if HAS_TOP_K else default_top_k
|
|
top_p = tl.load(top_p_ptr + req).to(tl.float32) if HAS_TOP_P else default_top_p
|
|
seed = tl.load(seeds_ptr + req)
|
|
pos = tl.load(pos_ptr + row)
|
|
nucleus_seed = tl.randint(seed, pos)
|
|
|
|
# Walk the distribution in descending order (ties: lowest id first, like a
|
|
# stable descending sort) while the top-p prefix and the top-k cap allow.
|
|
remaining = weights
|
|
before = 0.0
|
|
best = float("-inf")
|
|
sampled = tl.argmax(x, axis=0).to(tl.int64)
|
|
for _ in range(top_k):
|
|
if before < top_p:
|
|
value = tl.max(remaining, axis=0)
|
|
token = tl.argmax(remaining, axis=0)
|
|
prob = value / total
|
|
score = tl.log(prob) + _gumbel(nucleus_seed, token)
|
|
if score > best:
|
|
best = score
|
|
sampled = token.to(tl.int64)
|
|
before += prob
|
|
remaining = tl.where(cols == token, -1.0, remaining)
|
|
|
|
# Repetition-aware rejection over the last WIN generated tokens.
|
|
total_len = tl.load(total_len_ptr + req).to(tl.int64)
|
|
prompt_len = tl.load(prompt_len_ptr + req).to(tl.int64)
|
|
offsets = total_len - 1 - tl.arange(0, WIN)
|
|
in_output = (offsets >= prompt_len) & (tl.arange(0, WIN) < win_size)
|
|
history = tl.load(tokens_ptr + req * tokens_stride + tl.maximum(offsets, 0), mask=in_output, other=-1)
|
|
repeats = tl.sum((in_output & (history == sampled)).to(tl.float32), axis=0)
|
|
if (repeats >= threshold) & (tl.sum(in_output.to(tl.int32), axis=0) > 0):
|
|
# Offset keeps the replacement noise disjoint from the nucleus draw's.
|
|
replacement_seed = tl.randint(seed, pos + 1073741824)
|
|
scores = tl.where(mask & (cols != sampled), log_probs + _gumbel(replacement_seed, cols), float("-inf"))
|
|
if tl.max(scores, axis=0) > float("-inf"):
|
|
sampled = tl.argmax(scores, axis=0).to(tl.int64)
|
|
tl.store(out_ptr + row, sampled)
|
|
|
|
|
|
def fused_ras_sample(
|
|
logits: torch.Tensor,
|
|
idx_mapping: torch.Tensor,
|
|
temperature: torch.Tensor,
|
|
top_k: torch.Tensor | None,
|
|
top_p: torch.Tensor | None,
|
|
seeds: torch.Tensor,
|
|
pos: torch.Tensor,
|
|
all_token_ids: torch.Tensor,
|
|
total_len: torch.Tensor,
|
|
prompt_len: torch.Tensor,
|
|
*,
|
|
default_top_k: int,
|
|
default_top_p: float,
|
|
win_size: int,
|
|
tau_r: float,
|
|
eps: float,
|
|
) -> torch.Tensor:
|
|
"""Sample one token per logits row; per-request tensors are indexed by ``idx_mapping``."""
|
|
rows, vocab = logits.shape
|
|
out = torch.empty(rows, dtype=torch.int64, device=logits.device)
|
|
if rows == 0:
|
|
return out
|
|
_ras_sample_kernel[(rows,)](
|
|
out,
|
|
logits,
|
|
logits.stride(0),
|
|
vocab,
|
|
idx_mapping,
|
|
temperature,
|
|
temperature if top_k is None else top_k,
|
|
temperature if top_p is None else top_p,
|
|
seeds,
|
|
pos,
|
|
all_token_ids,
|
|
all_token_ids.stride(0),
|
|
total_len,
|
|
prompt_len,
|
|
int(default_top_k),
|
|
float(default_top_p),
|
|
float(eps),
|
|
float(win_size * tau_r),
|
|
int(win_size),
|
|
HAS_TOP_K=top_k is not None,
|
|
HAS_TOP_P=top_p is not None,
|
|
WIN=triton.next_power_of_2(max(int(win_size), 1)),
|
|
BLOCK=triton.next_power_of_2(vocab),
|
|
num_warps=8,
|
|
)
|
|
return out
|