simba9's picture
demo: self-contained Gradio app (run locally)
2a076e4 verified
Raw
History Blame Contribute Delete
9.52 kB
import torch
import torch.nn as nn
import torchtune
from torchtune.models import llama3_2
from voxtream.config import SpeechGeneratorConfig
from voxtream.utils.sampling import sample_top_k, sample_top_p
def get_llama3_2(
num_layers: int,
num_heads: int,
num_kv_heads: int,
embed_dim: int,
intermediate_dim: int,
vocab_size: int = 128_256,
max_seq_len: int = 2048,
) -> torchtune.modules.transformer.TransformerDecoder:
return llama3_2.llama3_2(
vocab_size=vocab_size,
num_layers=num_layers,
num_heads=num_heads,
num_kv_heads=num_kv_heads,
embed_dim=embed_dim,
max_seq_len=max_seq_len,
intermediate_dim=intermediate_dim,
)
MODEL_POOL = {
"phone_former": get_llama3_2(
num_layers=6, num_heads=8, num_kv_heads=2, embed_dim=1024, intermediate_dim=4096
),
"temp_former": get_llama3_2(
num_layers=12,
num_heads=16,
num_kv_heads=4,
embed_dim=1024,
intermediate_dim=4096,
),
"dep_former_csm": get_llama3_2(
num_layers=4, num_heads=8, num_kv_heads=2, embed_dim=1024, intermediate_dim=8192
),
}
def prepare_transformer(model):
embed_dim = model.tok_embeddings.embedding_dim
model.tok_embeddings = nn.Identity()
model.output = nn.Identity()
return model, embed_dim
def sample_acoustic_token(
logits: torch.Tensor,
temperature: float,
cfg_ac_gamma: float = None,
) -> torch.Tensor:
# CFG
if cfg_ac_gamma is not None:
assert logits.size(0) == 2, "CFG requires batch size of 2 (cond + uncond)"
logits_cond, logits_uncond = torch.split(logits, 1, dim=0)
logits = logits_cond * cfg_ac_gamma + (1 - cfg_ac_gamma) * logits_uncond
probs = torch.nn.functional.softmax(logits / temperature, dim=-1)
sampled_token = torch.argmax(probs, dim=-1, keepdim=True)
return sampled_token
def sample_semantic_token(
config: SpeechGeneratorConfig,
logits: torch.Tensor,
num_states: int,
codebook_size: int,
cfg_gamma: float = None,
cur_spk_rate_cnt: torch.Tensor = None,
target_spk_rate_cnt: torch.Tensor = None,
spk_rate_weight: float = None,
) -> torch.Tensor:
# CFG
if cfg_gamma is not None:
assert logits.size(0) == 2, "CFG requires batch size of 2 (cond + uncond)"
logits_cond, logits_uncond = torch.split(logits, 1, dim=0)
logits = logits_cond * cfg_gamma + (1 - cfg_gamma) * logits_uncond
joint_logits_2d = logits_cond.view(-1, num_states, codebook_size)
joint_logits_2d_cfg = logits.view(-1, num_states, codebook_size)
else:
joint_logits_2d = logits.view(-1, num_states, codebook_size)
joint_logits_2d_cfg = joint_logits_2d
spk_rate_logits = torch.logsumexp(joint_logits_2d, dim=-1)
spk_rate_probs = torch.nn.functional.softmax(
spk_rate_logits / config.temperature, dim=-1
)
# exp(beta * (ln(P_target) - ln(P_accumulated))) * P_current
if target_spk_rate_cnt is not None:
target_spk_rate_dist = target_spk_rate_cnt / target_spk_rate_cnt.sum()
cur_spk_rate_dist = cur_spk_rate_cnt / cur_spk_rate_cnt.sum()
distance = torch.log10(target_spk_rate_dist) - torch.log10(cur_spk_rate_dist)
spk_rate_shift = torch.exp(spk_rate_weight * distance)
spk_rate_probs = spk_rate_probs * spk_rate_shift.unsqueeze(0)
spk_rate_probs = spk_rate_probs / spk_rate_probs.sum(dim=-1, keepdim=True)
spk_rate_token = sample_top_p(spk_rate_probs, config.top_p)
if cur_spk_rate_cnt is not None:
cur_spk_rate_cnt[spk_rate_token.squeeze()] += 1
spk_rate_index = spk_rate_token.unsqueeze(-1).expand(
-1, -1, joint_logits_2d.size(-1)
)
semantic_logits = torch.gather(
joint_logits_2d_cfg, dim=1, index=spk_rate_index
).squeeze(1)
semantic_probs = torch.nn.functional.softmax(
semantic_logits / config.temperature, dim=-1
)
semantic_token = sample_top_k(semantic_probs, config.topk)
return semantic_token, spk_rate_token.squeeze(1), cur_spk_rate_cnt
def create_mask(seq_len: int, window_size: int, look_ahead: int = 0) -> torch.Tensor:
mask = torch.zeros((seq_len, seq_len), dtype=torch.bool)
for i in range(seq_len):
start = max(0, i - window_size + 1)
end = i + look_ahead + 1
if look_ahead == -1:
start = max(0, i - window_size // 2 + 1)
end = i + window_size // 2 + 1
mask[i, start:end] = True
return mask
def create_dynamic_la_masks(
max_look_ahead: int,
window_size: int,
max_seq_len: int,
extra_look_aheads: tuple = (),
):
"""Стек LA-масок: плотно 1..max_look_ahead (стриминговый диапазон) +
опционально разреженные дальние точки (offline-режим: LA ~ вся фраза).
Возвращает (маски, список la-значений в порядке стека)."""
la_values = list(range(1, max_look_ahead + 1))
la_values += [la for la in extra_look_aheads if la > max_look_ahead]
masks = [create_mask(max_seq_len, window_size, min(la, max_seq_len)) for la in la_values]
# (num_look_aheads, max_seq_len, max_seq_len), la_values
return torch.stack(masks, dim=0), la_values
def get_mask(mask: torch.Tensor, input_pos: torch.Tensor) -> torch.Tensor:
"""
Args:
mask: (max_seq_len, max_seq_len)
input_pos: (batch_size, seq_len)
Returns:
(batch_size, seq_len, seq_len)
"""
r = mask[input_pos, : len(input_pos[0])]
return r
def index_causal_mask(mask: torch.Tensor, input_pos: torch.Tensor):
"""
Args:
mask: (max_seq_len, max_seq_len)
input_pos: (batch_size, seq_len)
Returns:
(batch_size, seq_len, max_seq_len)
"""
r = mask[input_pos, :]
return r
def remove_punctuation(
data: torch.Tensor, indices_to_remove: torch.Tensor
) -> torch.Tensor:
"""
data: [B, S, D]
indices_to_remove: [B, R] padded with -1; may include invalids or duplicates.
Returns: [B, S, D], where requested indices are removed (per batch) and
the tail is padded with data[:, -1, :].
"""
B, S, D = data.shape
device = data.device
# 1) Build keep_mask [B, S]
valid = (indices_to_remove >= 0) & (indices_to_remove < S) # [B, R]
idx = indices_to_remove.clamp_min(0).clamp_max(S - 1) # [B, R]
batch = torch.arange(B, device=device).unsqueeze(1).expand_as(idx) # [B, R]
keep_mask = torch.ones((B, S), dtype=torch.bool, device=device)
keep_mask[batch[valid], idx[valid]] = False # mark removals
# 2) Compute target positions for kept tokens (stable order)
# For kept positions, pos = cumsum(keep_mask) - 1; undefined for removed.
pos = keep_mask.cumsum(dim=1) - 1 # [B, S], 0-based
pos = pos.clamp_min(0) # avoid negatives where masked
# 3) Scatter kept rows into compacted positions
# We’ll scatter-add the source masked by keep_mask to avoid overwriting problems.
out = data.new_zeros(B, S, D) # zeros for now
src = data * keep_mask.unsqueeze(-1) # zero out removed rows
out.scatter_add_(
dim=1, index=pos.unsqueeze(-1).expand(-1, -1, D), src=src
) # [B, S, D]
# 4) Tail padding: replace positions >= num_kept with the last row of each batch
num_kept = keep_mask.sum(dim=1) # [B]
arangeS = torch.arange(S, device=device).unsqueeze(0) # [1, S]
tail_mask = arangeS >= num_kept.unsqueeze(1) # [B, S]
pad_row = data[:, -1:, :].expand(B, S, D) # repeat last row along S
out = torch.where(tail_mask.unsqueeze(-1), pad_row, out) # fill tail
return out
def patch_kv_cache_for_cuda_graph() -> None:
"""Avoid Python-side tensor asserts during CUDA graph capture.
torchtune's KVCache.update uses a Python assert on a CUDA tensor, which
triggers a disallowed sync during capture. We skip that assert only while
capturing and rely on warmup/non-capture calls to catch misuse.
"""
try:
from torchtune.modules.kv_cache import KVCache
except Exception:
return
if getattr(KVCache.update, "_voxtream_cuda_graph_patched", False):
return
orig_update = KVCache.update
orig_reset = KVCache.reset
def _update(self, k_val, v_val):
if torch.cuda.is_current_stream_capturing():
bsz, _, seq_len, _ = k_val.shape
if bsz > self.k_cache.shape[0]:
raise ValueError(
f"The current cache has been setup with a batch size of {self.k_cache.shape[0]}"
f", but found new key tensors with batch size {k_val.shape[0]}!"
)
k_out = self.k_cache
v_out = self.v_cache
k_out[:, :, self.cache_pos[:seq_len]] = k_val
v_out[:, :, self.cache_pos[:seq_len]] = v_val
self.cache_pos += seq_len
return k_out, v_out
return orig_update(self, k_val, v_val)
def _reset(self) -> None:
if torch.cuda.is_current_stream_capturing():
self.k_cache.zero_()
self.v_cache.zero_()
# Avoid .item() on CUDA tensor during capture.
tmp = self.cache_pos - self.cache_pos[0]
self.cache_pos.copy_(tmp)
return
return orig_reset(self)
_update._voxtream_cuda_graph_patched = True
KVCache.update = _update
KVCache.reset = _reset