137 lines
5.6 KiB
Python
137 lines
5.6 KiB
Python
# ruff: noqa: N803
|
|
# SPDX-License-Identifier: Apache-2.0
|
|
# SPDX-FileCopyrightText: Copyright contributors to the vLLM-Omni project
|
|
"""Batched per-request ``exponential_(generator=...)`` that is bit-identical to torch.
|
|
|
|
Seeded sampling draws exponential noise one request at a time, which costs a
|
|
kernel launch and host overhead per request and step. This kernel reproduces
|
|
torch's CUDA distribution kernel exactly for float32 (the grid-stride
|
|
Philox4x32-10 ``curand_uniform4`` stream, the fast-math ``__logf`` transform
|
|
and the per-call Philox offset increment), so a batch draws in one launch while
|
|
every value and every generator state afterwards matches the per-request loop.
|
|
"""
|
|
|
|
import torch
|
|
from vllm.platforms import current_platform
|
|
from vllm.triton_utils import tl, triton
|
|
|
|
|
|
@triton.jit
|
|
def _generator_exponential_kernel(
|
|
out_ptr,
|
|
rows_ptr,
|
|
seed_ptr,
|
|
offset_ptr,
|
|
numel,
|
|
threads,
|
|
HAS_ROWS: tl.constexpr,
|
|
BLOCK: tl.constexpr,
|
|
):
|
|
request = tl.program_id(0)
|
|
elements = tl.program_id(1) * BLOCK + tl.arange(0, BLOCK)
|
|
mask = elements < numel
|
|
seed = tl.load(seed_ptr + request).to(tl.uint64)
|
|
offset = tl.load(offset_ptr + request).to(tl.uint64)
|
|
# torch's grid-stride kernel: thread t draws curand_uniform4 once per grid
|
|
# stride and element j = t + threads * (4 * call + component).
|
|
thread = (elements % threads).to(tl.uint64)
|
|
stride = (elements // threads).to(tl.uint64)
|
|
counter = offset // 4 + stride // 4
|
|
component = stride % 4
|
|
c0 = (counter & 0xFFFFFFFF).to(tl.uint32)
|
|
c1 = (counter >> 32).to(tl.uint32)
|
|
c2 = (thread & 0xFFFFFFFF).to(tl.uint32)
|
|
c3 = (thread >> 32).to(tl.uint32)
|
|
r0, r1, r2, r3 = tl.philox(seed, c0, c1, c2, c3, 10)
|
|
bits = tl.where(component == 0, r0, tl.where(component == 1, r1, tl.where(component == 2, r2, r3)))
|
|
# curand_uniform: (0, 1] from 32 bits.
|
|
uniform = bits.to(tl.float32) * 2.3283064365386963e-10 + 1.1641532182693481e-10
|
|
# at::exponential with lambda 1 under fast math (at::log is __logf).
|
|
log = tl.where(
|
|
uniform >= 1.0 - 5.960464477539063e-08,
|
|
-5.960464477539063e-08,
|
|
tl.extra.cuda.libdevice.fast_logf(uniform),
|
|
)
|
|
row = tl.load(rows_ptr + request).to(tl.int64) if HAS_ROWS else request.to(tl.int64)
|
|
tl.store(out_ptr + row * numel + elements, -log, mask=mask)
|
|
|
|
|
|
_POLICY_CACHE: dict[tuple[int, int], tuple[int, int]] = {}
|
|
|
|
|
|
def torch_exponential_policy(numel: int, device: torch.device) -> tuple[int, int]:
|
|
"""(grid threads, Philox offset increment) of torch's CUDA ``exponential_``."""
|
|
key = (numel, device.index or 0)
|
|
policy = _POLICY_CACHE.get(key)
|
|
if policy is None:
|
|
props = torch.cuda.get_device_properties(device)
|
|
blocks = min(props.multi_processor_count * (props.max_threads_per_multi_processor // 256), -(-numel // 256))
|
|
threads = blocks * 256
|
|
policy = _POLICY_CACHE[key] = (threads, ((numel - 1) // (threads * 4) + 1) * 4)
|
|
return policy
|
|
|
|
|
|
def fill_exponential_rows(out: torch.Tensor, generators: list, rows: list[int] | None = None) -> torch.Tensor:
|
|
"""Draw ``out[rows[i]].exponential_(generator=generators[i])`` for all ``i`` in one launch.
|
|
|
|
``out`` is a contiguous float32 matrix. Without ``rows``, each generator
|
|
owns an equal contiguous slice of the flattened output (which may span
|
|
several codebook rows). ``None`` generators use the default generator in
|
|
order. NVIDIA uses the batched CUDA kernel; other platforms retain
|
|
ordered Torch draws, including direct Higgs Audio callers.
|
|
"""
|
|
if not current_platform.is_cuda():
|
|
targets = out.reshape(len(generators), -1) if rows is None else [out[row] for row in rows]
|
|
for target, generator in zip(targets, generators):
|
|
target.exponential_(generator=generator)
|
|
return out
|
|
|
|
numel = int(out.shape[-1]) if rows is not None else int(out.numel()) // len(generators)
|
|
device = out.device
|
|
threads, increment = torch_exponential_policy(numel, device)
|
|
default = None
|
|
seeds, offsets = [], []
|
|
for generator in generators:
|
|
if generator is None:
|
|
if default is None:
|
|
default = torch.cuda.default_generators[device.index or 0]
|
|
generator = default
|
|
offset = generator.get_offset()
|
|
generator.set_offset(offset + increment)
|
|
seed = generator.initial_seed()
|
|
seeds.append(seed - (1 << 64) if seed >= 1 << 63 else seed)
|
|
offsets.append(offset)
|
|
table = [seeds, offsets] if rows is None else [seeds, offsets, list(rows)]
|
|
state = torch.tensor(table, dtype=torch.int64, pin_memory=True).to(device, non_blocking=True)
|
|
block = 1024
|
|
_generator_exponential_kernel[(len(generators), triton.cdiv(numel, block))](
|
|
out,
|
|
state[2] if rows is not None else state[0],
|
|
state[0],
|
|
state[1],
|
|
numel,
|
|
threads,
|
|
HAS_ROWS=rows is not None,
|
|
BLOCK=block,
|
|
)
|
|
return out
|
|
|
|
|
|
def batched_seeded_exponential_supported(q: torch.Tensor, generators: dict) -> bool:
|
|
"""Whether the NVIDIA CUDA kernel reproduces the per-row loop for ``q``.
|
|
|
|
ROCm tensors also report ``is_cuda``, but HIP uses a different distribution
|
|
implementation and cannot compile the CUDA libdevice transform above.
|
|
Keep those draws on Torch's original per-row path, including RNG state.
|
|
"""
|
|
return (
|
|
current_platform.is_cuda()
|
|
and len(generators) > 1
|
|
and q.is_cuda
|
|
and q.dtype == torch.float32
|
|
and q.dim() == 2
|
|
and q.is_contiguous()
|
|
# A generator shared by two rows makes the loop order observable.
|
|
and len({id(g) for g in generators.values()}) == len(generators)
|
|
)
|