"""``ChatterboxFlashT3`` — block-diffusion T3 decoder for Chatterbox-Flash. We extend Chatterbox-TTS's :class:`~chatterbox.models.t3.t3.T3` decoder with a single extra ``[MASK]`` token (``id = V``) added to the speech embedding, and replace the autoregressive ``inference()`` path with a block-diffusion ``generate()`` method. The decoder is otherwise unchanged: same Llama backbone, same conditioning encoder, same text / speech heads. This keeps the released checkpoint compatible with the public ``chatterbox-tts`` weights for everything but the expanded ``speech_emb`` (which is initialized by copying the V rows from the pretrained checkpoint and adding one normal-initialized row for ``[MASK]``). The ``generate()`` method here implements the production configuration described in the paper: * dual-stream-free single-stream KV cache (``[prefix | speech]``); * FlashInfer paged KV cache + CUDA-graph capture of the per-block forward; * zero-text-batch classifier-free guidance with ``zero_all`` null prefix (text and conditioning zeroed, speech-x_t duplicated); the only PMI/CFG combination is ``pmi_cfg`` — ``(1+w)·pmi_c − w·pmi_u`` on the PMI scale, no other mode exposed; * **pmi_count_early** outlier rule — OmniVoice's r_n count schedule (controlled by ``omnivoice_schedule_t_shift``) as the per-step **floor**, plus the paper's time-shifted PMI-quantile ``q_k = max(0, 1 - τ · (k+1)/K)`` as an early-decoding **ceiling**. When ``time_shift_tau == 0`` the schedule reduces to plain pmi_count (fixed-K, no early decoding); * prior-calibrated PMI scoring ``s_i = log p_i(\\hat x_i) - log p̄(\\hat x_i)`` with the **precomputed unconditional block prior** ``p̄`` — a forward on ``[SOS, MASK × block_size]`` with no conditioning, lazy-cached on the model object so it's evaluated at most once per ``(block_size, dtype, device)`` triple over the lifetime of the model; * optional Gumbel ``position_temperature`` perturbing the PMI ranking for stochastic top-k position selection (OmniVoice §3.4 uses T=5). Training / experimental code paths (full block-diffusion forward, dual-stream, speaker probe, magi attention, sdpa/flex inference backends, alternative CFG modes and outlier methods, remasking, …) are intentionally omitted. """ from __future__ import annotations import contextlib import logging import math from typing import Literal, Optional import torch import torch.nn as nn import torch.nn.functional as F from torch import Tensor from chatterbox.models.t3.t3 import T3 as _ChatterboxT3 from chatterbox.models.t3.modules.cond_enc import T3Cond from chatterbox.models.t3.modules.t3_config import T3Config from .cfg_guidance import apply_zero_text_cfg_from_logits, pmi_cfg_combine from .engines import FLASHINFER_AVAILABLE, InferenceEngine, build_engine from .calibration import compute_pmi logger = logging.getLogger(__name__) # --------------------------------------------------------------------------- # # Sampling helpers # --------------------------------------------------------------------------- # def _gumbel_sample(logits: Tensor, temperature: float) -> Tensor: scaled = logits / temperature u = torch.rand_like(scaled) gumbel = -torch.log(-torch.log(u + 1e-10) + 1e-10) return scaled + gumbel def _sample_at_temperature( logits: Tensor, temperature: float, *, sampling: Literal["multinomial", "gumbel"] = "multinomial", ) -> Tensor: if sampling == "gumbel": return _gumbel_sample(logits, temperature).argmax(dim=-1) probs = F.softmax(logits / temperature, dim=-1) return torch.multinomial(probs, num_samples=1).squeeze(-1) # --------------------------------------------------------------------------- # # Prefix-shaping helpers (CFG ``zero_all`` null branch) # --------------------------------------------------------------------------- # def _cond_emb_zero_all(ce: Tensor) -> Tensor: """Zero the entire conditioning prefix (paper's ``zero_all`` null branch).""" return torch.zeros_like(ce) def _zero_text_content_keep_pad( text_emb: Tensor, text_tokens: Tensor, text_token_lens: Tensor | None, ) -> Tensor: """Null-text replacement: non-pad text positions go to 0, pad positions copy. Pad positions are detected from ``text_tokens == -100`` and / or ``text_token_lens`` when supplied. This keeps the sequence length identical so the CFG forward can be batched 2x. """ squeeze_batch = text_tokens.ndim == 1 toks = text_tokens.unsqueeze(0) if squeeze_batch else text_tokens emb = text_emb.unsqueeze(0) if text_emb.ndim == 2 else text_emb Btxt, Tt = toks.shape[0], toks.size(1) device = toks.device is_pad = toks.eq(-100) if text_token_lens is not None: ttl = text_token_lens.to(device=device, dtype=torch.long).reshape(-1) if ttl.numel() == 1 and Btxt > 1: ttl = ttl.expand(Btxt) is_pad = is_pad | (torch.arange(Tt, device=device)[None, :] >= ttl[:, None]) out = emb.clone() out.masked_fill_((~is_pad).unsqueeze(-1), 0.0) return out.squeeze(0) if squeeze_batch else out # --------------------------------------------------------------------------- # # OmniVoice §3.4 r_n count schedule # --------------------------------------------------------------------------- # def _omnivoice_unmask_schedule( n_total_mask: int, num_steps: int, t_shift: float, ) -> list[int]: """Per-step unmask **counts** under OmniVoice's time-shifted ``r_n`` curve. Cumulative fraction unmasked by the end of step ``s`` is ``t_s = r_n(s/K, τ)`` where ``r_n(s, τ) = τ * s / (1 + (τ - 1) * s)`` (eq. (3) of OmniVoice). We then convert the cumulative target into per-step integer counts by cumulative rounding (``target = round(N * t_{s+1})``, ``num_s = max(0, target - cum)``), capped at the remaining masks. The last step always closes out whatever is left. Returns a list of length ``num_steps`` summing to ``n_total_mask``. """ K = max(1, int(num_steps)) N = max(0, int(n_total_mask)) if N == 0: return [0] * K if K == 1: return [N] tau = float(t_shift) if tau <= 0: # Degenerate: split as evenly as possible, last step takes remainder. base = N // K out = [base] * K out[-1] += N - sum(out) return out ts = [tau * (s / K) / (1.0 + (tau - 1.0) * (s / K)) for s in range(K + 1)] counts: list[int] = [] cum = 0 rem = N for s in range(K): if s == K - 1: num = rem else: target = int(round(N * ts[s + 1])) num = max(0, target - cum) num = min(num, rem) counts.append(num) cum += num rem -= num return counts # --------------------------------------------------------------------------- # # Speech-block step (block-diffusion inner loop) # --------------------------------------------------------------------------- # def _pmi_count_early_step_unmask( blk_logits_2d: Tensor, blk_toks_1d: Tensor, *, bl: int, k: int, K: int, MASK: int, device: torch.device, temperature: float, temperature_sampling: Literal["multinomial", "gumbel"], omnivoice_unmask_schedule_k: int, time_shift_tau: float, prior_override_v: Tensor | None, block_marginal_prior: Tensor | None, pmi_cfg_probs_c_bl: Tensor | None, pmi_cfg_probs_u_bl: Tensor | None, cfg_scale_for_pmi: float, position_temperature: float, ) -> dict: """One denoising step for a single sample block — ``pmi_count_early``. Per-step unmask count combines OmniVoice's count schedule (floor) with the paper's PMI-quantile (ceiling): n_sched = omnivoice_unmask_schedule_k (OmniVoice r_n at step k) q_k = max(0, 1 - time_shift_tau * (k+1) / K) tau_k = Quantile({pmi_i}_{i in M}, q_k) (if time_shift_tau>0) n_quant = |{i in M : pmi_i >= tau_k}| (else 0) n_unmask = min(max(n_sched, n_quant), m_cur) The top ``n_unmask`` masked positions by PMI get unmasked. Properties: * ``time_shift_tau == 0`` reduces to plain pmi_count (fixed-K, no early decoding); per-step count is exactly ``n_sched``. * ``time_shift_tau > 0`` may unmask more than ``n_sched`` when PMI is concentrated, allowing the block to early-finish (n_mask==0) before step ``K-1`` — the *only* source of step savings above the baseline. PMI scoring: * Numerator: probabilities from the conditional softmax of the outer block logits (``probs_bl``). * Denominator: ``prior_override_v`` (precomputed unconditional block prior) when provided, otherwise the running per-block marginal frozen at step 0 (``block_marginal_prior``). * Under CFG (``cfg_scale > 0``) the caller passes ``pmi_cfg_probs_{c,u}_bl`` and the CFG-combined PMI ``(1+w) * pmi_c - w * pmi_u`` is used (this is the only PMI/CFG combination supported — no other ``cfg_prior_mode`` is exposed). Gumbel position scoring (OmniVoice §3.4 T=5): when ``position_temperature > 0`` we add Gumbel noise scaled by ``T`` to masked positions' PMI before ranking, turning deterministic top-k into stochastic top-k without replacement. """ probs_raw = F.softmax(blk_logits_2d, dim=-1) if temperature > 0: sampled = _sample_at_temperature( blk_logits_2d, temperature, sampling=temperature_sampling, ) else: sampled = blk_logits_2d.argmax(dim=-1) ar = torch.arange(bl, device=device) is_mask = blk_toks_1d == MASK # ---- PMI scoring ---- probs_bl = probs_raw if k == 0 and block_marginal_prior is None: block_marginal_prior = probs_bl.mean(dim=0) if pmi_cfg_probs_c_bl is not None and pmi_cfg_probs_u_bl is not None: mp_c = ( prior_override_v if prior_override_v is not None else pmi_cfg_probs_c_bl.mean(dim=0) ) mp_u = ( prior_override_v if prior_override_v is not None else pmi_cfg_probs_u_bl.mean(dim=0) ) pmi_c = compute_pmi(pmi_cfg_probs_c_bl, sampled, marginal_prior=mp_c) pmi_u = compute_pmi(pmi_cfg_probs_u_bl, sampled, marginal_prior=mp_u) pmi = pmi_cfg_combine(pmi_c, pmi_u, cfg_scale_for_pmi) else: _mp = ( prior_override_v if prior_override_v is not None else block_marginal_prior ) pmi = compute_pmi(probs_bl, sampled, marginal_prior=_mp) if position_temperature > 0.0: noise = _gumbel_sample(torch.zeros_like(pmi), position_temperature) pmi = torch.where(is_mask, pmi + noise, pmi) # ---- Outlier rule (pmi_count_early): max(count-schedule, quantile) ---- if k == K - 1: do_unmask = is_mask else: masked_idx = ar[is_mask] m_cur = int(masked_idx.numel()) do_unmask = torch.zeros_like(is_mask, dtype=torch.bool) if m_cur > 0: pmi_m = pmi[masked_idx] n_sched = max(0, min(int(omnivoice_unmask_schedule_k), m_cur)) if time_shift_tau > 0.0: q_shifted = max(0.0, 1.0 - time_shift_tau * (k + 1) / K) tau_k = torch.quantile(pmi_m.float(), q_shifted).to(dtype=pmi_m.dtype) n_quant = int((pmi_m >= tau_k).sum().item()) else: n_quant = 0 n_unmask = min(max(n_sched, n_quant), m_cur) if n_unmask >= m_cur: do_unmask[masked_idx] = True elif n_unmask > 0: _, _topi = pmi_m.topk(n_unmask) do_unmask[masked_idx[_topi]] = True xt_step = torch.where(do_unmask, sampled, blk_toks_1d) n_mask = int((xt_step == MASK).sum().item()) return dict( xt_step=xt_step, n_mask=n_mask, block_marginal_prior=block_marginal_prior, ) # --------------------------------------------------------------------------- # # ChatterboxFlashT3 # --------------------------------------------------------------------------- # class ChatterboxFlashT3(_ChatterboxT3): """Chatterbox-TTS T3 decoder + [MASK] token + block-diffusion generate(). The Llama backbone, conditioning encoder, text / speech embeddings and heads come straight from the parent ``chatterbox.models.t3.t3.T3``. We only add the extra mask-token row in :attr:`speech_emb` and override :meth:`generate` with the block-diffusion inference loop. """ def __init__( self, hp: T3Config | None = None, *, drf_block_size: int = 16, ) -> None: super().__init__(hp=hp) if self.is_gpt: raise NotImplementedError( "ChatterboxFlashT3 currently supports the Llama backbone only " "(hp.llama_config_name='Llama_520M').", ) self._mask_token_id: int = self.hp.speech_tokens_dict_size self.drf_block_size = int(drf_block_size) # Extend speech_emb by one row for the [MASK] token (id = V). old_speech_emb = self.speech_emb self.speech_emb = nn.Embedding(self.hp.speech_tokens_dict_size + 1, self.dim) with torch.no_grad(): self.speech_emb.weight[: self.hp.speech_tokens_dict_size].copy_( old_speech_emb.weight, ) nn.init.normal_( self.speech_emb.weight[self.hp.speech_tokens_dict_size :], std=0.02, ) # Backward-compat alias so :class:`FlashInferEngine` can read # ``model.tfmr.config`` (chatterbox T3 only exposes ``.cfg``). self.config = self.cfg # Inference engine cache (re-used between calls when shape compatible). self._cached_engine: InferenceEngine | None = None # ------------------------------------------------------------------ # # Properties / helpers # ------------------------------------------------------------------ # @property def mask_token_id(self) -> int: return self._mask_token_id @property def is_llama(self) -> bool: return not self.is_gpt def set_block_size(self, block_size: int) -> None: self.drf_block_size = int(block_size) def prime_uncond_block_prior( self, dtype: torch.dtype, device: torch.device | str, block_size: int | None = None, ) -> None: """Eagerly compute & cache the PMI unconditional block prior. Called at load time so the first ``generate()`` does not pay for the one-shot prior forward — keeping it out of the timed / RTF loop. Uses ``self.drf_block_size`` unless ``block_size`` is given; a later ``generate()`` with a different block size recomputes automatically (the cache is keyed on ``(block_size, dtype, device)``). """ device = torch.device(device) if isinstance(device, str) else device bs = int(block_size) if block_size is not None else int(self.drf_block_size) with torch.no_grad(): self._compute_uncond_block_prior( bs, int(self.hp.start_speech_token), int(self.mask_token_id), dtype, device, ) def _embed_speech_tokens(self, tokens: Tensor) -> Tensor: """Embed speech tokens (including the extra ``[MASK]`` id).""" emb = self.speech_emb(tokens) if self.hp.input_pos_emb == "learned": emb = emb + self.speech_pos_emb(tokens) return emb # ------------------------------------------------------------------ # # Precomputed unconditional block prior (PMI denominator) # ------------------------------------------------------------------ # def _compute_uncond_block_prior( self, block_size: int, SOS: int, MASK: int, dtype: torch.dtype, device: torch.device, ) -> Tensor: """PMI prior from a forward on ``[SOS, MASK × block_size]`` with **no conditioning at all** — no speaker, no text, no prompt speech, not even zero-filled embeddings of those. Captures the model's intrinsic marginal over speech tokens at the very start of an utterance. The result depends only on ``(block_size, SOS, MASK, dtype, device)`` and the (frozen, inference-time) model weights, so we **cache** it on ``self._uncond_block_prior_cache`` keyed by that tuple. The actual forward runs at most once per ``(block_size, dtype, device)`` triple across the entire lifetime of the model object — subsequent ``generate()`` calls reuse the cached ``(V,)`` tensor. Invalidated implicitly when the model is recreated (e.g. on checkpoint reload). """ cache: dict = getattr(self, "_uncond_block_prior_cache", None) if cache is None: cache = {} self._uncond_block_prior_cache = cache key = (int(block_size), int(SOS), int(MASK), str(dtype), str(device)) if key in cache: return cache[key] n_tok = 1 + int(block_size) tok_seq = torch.cat( [ torch.full((1, 1), int(SOS), device=device, dtype=torch.long), torch.full( (1, int(block_size)), int(MASK), device=device, dtype=torch.long, ), ], dim=1, ) seq_emb = self._embed_speech_tokens(tok_seq).to(dtype) position_ids = torch.arange(n_tok, device=device).unsqueeze(0) # The FlashInfer engine swaps the LlamaAttention forward in-place; # we explicitly route this one-shot prior pass through HuggingFace's # SDPA implementation so it never touches the FlashInfer paged cache. cfg = self.tfmr.config old_impl = getattr(cfg, "_attn_implementation", "sdpa") cfg._attn_implementation = "sdpa" try: out = self.tfmr( input_ids=None, inputs_embeds=seq_emb, position_ids=position_ids, use_cache=False, return_dict=True, ) finally: cfg._attn_implementation = old_impl # Standard token-shift: logits for mask position i come from hidden # at position i (SOS → mask 0, mask k-1 → mask k). shift_hidden = out.last_hidden_state[:, : int(block_size), :] logits = self.speech_head(shift_hidden) probs = F.softmax(logits.float(), dim=-1) prior = probs.mean(dim=1).squeeze(0).detach() cache[key] = prior return prior # ------------------------------------------------------------------ # # State-dict surgery (lets the model accept either a pure chatterbox # checkpoint of size V or a Chatterbox-Flash checkpoint of size V+1) # ------------------------------------------------------------------ # def _load_from_state_dict( self, state_dict, prefix, *args, **kwargs, ): # noqa: D401 key = prefix + "speech_emb.weight" if key in state_dict: ckpt_w = state_dict[key] model_v = self.speech_emb.weight.shape[0] if ckpt_w.shape[0] == model_v - 1: logger.info( "Expanding speech_emb from %d to %d (adding [MASK] token).", ckpt_w.shape[0], model_v, ) expanded = self.speech_emb.weight.detach().clone() expanded[: ckpt_w.shape[0]] = ckpt_w state_dict[key] = expanded return super()._load_from_state_dict(state_dict, prefix, *args, **kwargs) # ------------------------------------------------------------------ # # Prefix forward (run once per generation, cached in FlashInfer) # ------------------------------------------------------------------ # def _build_prefix_emb( self, cond_emb: Tensor, text_tokens: Tensor, device: torch.device, dtype: torch.dtype, ) -> Tensor: """Build ``[cond | text | ]`` embedding.""" text_emb = self.text_emb(text_tokens) if self.hp.input_pos_emb == "learned": text_emb = text_emb + self.text_pos_emb(text_tokens) sos = torch.full( (1, 1), self.hp.start_speech_token, device=device, dtype=torch.long, ) sos_emb = self._embed_speech_tokens(sos) return torch.cat([cond_emb, text_emb, sos_emb], dim=1).to(dtype) # ------------------------------------------------------------------ # # Block-diffusion generate # ------------------------------------------------------------------ # @torch.inference_mode() def generate( self, *, t3_cond: "T3Cond | list[T3Cond] | tuple[T3Cond, ...]", text_tokens: "torch.Tensor | list[torch.Tensor]", text_token_lens: "torch.Tensor | None" = None, total_speech_len: "int | list[int]" = 0, num_steps: int = 10, temperature: float = 0.6, temperature_sampling: Literal["multinomial", "gumbel"] = "multinomial", time_shift_tau: float = 0.1, omnivoice_schedule_t_shift: float = 0.5, cfg_scale: float = 1.0, position_temperature: float = 5.0, pmi_uncond_prior_precompute: bool = True, batch_size: int = 1, page_size: int = 16, flashinfer_reserve_max_seq: int | None = None, use_cuda_graph: bool = True, backend: Literal["auto", "flashinfer", "torch"] = "auto", ) -> Tensor: """Block-wise denoising generation matching the paper's best config. Inputs accept either a single sample (``text_tokens`` is a tensor and ``t3_cond`` is a :class:`T3Cond`) or a heterogeneous batch (``text_tokens`` is a list, ``t3_cond`` is a list of :class:`T3Cond`). Both modes are dispatched through the same FlashInfer block-decoding loop. Heterogeneous batches pad to ``max(total_speech_len)`` and stop each row once it emits ``hp.stop_speech_token``. Decoding-relevant parameters (paper defaults): ``num_steps`` Maximum denoising iterations per block (``K``). Default 10. ``time_shift_tau`` Early-decoding aggressiveness in ``q_k = max(0, 1 - τ·(k+1)/K)``. ``0.0`` disables early decoding (every block uses exactly the OmniVoice count schedule). Default ``0.1`` (paper). ``omnivoice_schedule_t_shift`` OmniVoice r_n schedule parameter that controls the per-step unmask count floor. Default ``0.5``. ``cfg_scale`` CFG strength. When ``> 0`` the forward batch is doubled with a null branch (``zero_text_batch`` + ``zero_all`` null prefix + text/cond zeroed + speech-x_t duplicated). Token sampling uses the guided distribution; PMI scoring is combined on the PMI scale via ``(1+w)·pmi_c − w·pmi_u`` (``pmi_cfg``) — this is the only supported PMI/CFG combination, no other mode is exposed. ``position_temperature`` Gumbel temperature added to PMI for stochastic top-k position ranking. ``0`` = deterministic argmax-top-k; default ``5.0`` (OmniVoice §3.4 T=5). ``pmi_uncond_prior_precompute`` When ``True`` (default), the PMI denominator is the unconditional block prior computed once via :meth:`_compute_uncond_block_prior` and cached on the model. When ``False``, falls back to the running per-block marginal from step 0. Returns ------- torch.LongTensor Speech token ids of shape ``(B, T)`` with ``T <= max(total_speech_len)``. """ device = self.device BS = self.drf_block_size K = num_steps MASK = self.mask_token_id dtype = next(self.parameters()).dtype stop_tok = self.hp.stop_speech_token # ---- cross-text vs same-text batching ---- _cross_text = isinstance(text_tokens, list) if _cross_text: B = len(text_tokens) _speech_lens: list[int] = ( list(total_speech_len) if isinstance(total_speech_len, list) else [total_speech_len] * B ) N = max(_speech_lens) if _speech_lens else 0 _per_sample_num_blocks = [math.ceil(sl / BS) for sl in _speech_lens] num_blocks = max(_per_sample_num_blocks) if _per_sample_num_blocks else 0 else: B = max(1, batch_size) _speech_lens = [] N = total_speech_len if isinstance(total_speech_len, int) else total_speech_len[0] num_blocks = math.ceil(N / BS) if N > 0 else 0 _per_sample_num_blocks = [] if N <= 0: return torch.empty((B, 0), device=device, dtype=torch.long) B_usr = B _ztb = cfg_scale > 0 B_fwd = (2 * B_usr) if _ztb else B_usr # ---- prefix(es) ---- t3_is_seq = isinstance(t3_cond, (list, tuple)) if t3_is_seq and not _cross_text: raise ValueError( "t3_cond as a list/tuple is only supported when text_tokens is a list.", ) cond_embs: list[Tensor] | None = None cond_emb_single: Tensor | None = None if _cross_text: if t3_is_seq: if len(t3_cond) != len(text_tokens): raise ValueError( f"len(t3_cond) ({len(t3_cond)}) must match " f"len(text_tokens) ({len(text_tokens)}).", ) cond_embs = [self.prepare_conditioning(c) for c in t3_cond] else: cond_emb_single = self.prepare_conditioning(t3_cond) else: cond_emb_single = self.prepare_conditioning(t3_cond) prefix_emb_cond: Tensor | list[Tensor] prefix_emb_null: Tensor | list[Tensor] | None = None _prefix_lens: list[int] if _cross_text: cond_list: list[Tensor] = [] null_list: list[Tensor] = [] lens: list[int] = [] for i, tt in enumerate(text_tokens): ce = cond_embs[i] if cond_embs is not None else cond_emb_single p = self._build_prefix_emb(ce, tt, device, dtype) cond_list.append(p) lens.append(p.size(1)) if _ztb: ce_null = _cond_emb_zero_all(ce) text_emb = self.text_emb(tt) if self.hp.input_pos_emb == "learned": text_emb = text_emb + self.text_pos_emb(tt) text_emb_zero = _zero_text_content_keep_pad( text_emb, tt, None, ) sos = torch.full( (1, 1), self.hp.start_speech_token, device=device, dtype=torch.long, ) sos_emb = self._embed_speech_tokens(sos) p_null = torch.cat( [ce_null, text_emb_zero, sos_emb], dim=1, ).to(dtype) null_list.append(p_null) prefix_emb_cond = cond_list lp = max(lens) _prefix_lens = list(lens) if _ztb: prefix_emb_null = null_list _prefix_lens = _prefix_lens + [p.size(1) for p in null_list] else: assert cond_emb_single is not None prefix_emb = self._build_prefix_emb( cond_emb_single, text_tokens, device, dtype, ) lp = prefix_emb.size(1) if B_usr > 1: prefix_emb = prefix_emb.expand(B_usr, -1, -1).contiguous() prefix_emb_cond = prefix_emb _prefix_lens = [lp] * B_fwd if _ztb: cond_for_null = _cond_emb_zero_all(cond_emb_single) text_emb = self.text_emb(text_tokens) if self.hp.input_pos_emb == "learned": text_emb = text_emb + self.text_pos_emb(text_tokens) text_emb_zero = _zero_text_content_keep_pad( text_emb, text_tokens, text_token_lens, ) sos = torch.full( (1, 1), self.hp.start_speech_token, device=device, dtype=torch.long, ) sos_emb = self._embed_speech_tokens(sos) p_null = torch.cat( [cond_for_null, text_emb_zero, sos_emb], dim=1, ).to(dtype) if B_usr > 1: p_null = p_null.expand(B_usr, -1, -1).contiguous() prefix_emb_null = p_null # ---- speech buffer (mask everywhere; we fill it block by block) ---- xt = torch.full((B_fwd, N), MASK, device=device, dtype=torch.long) # ---- precomputed unconditional block prior (PMI denominator) ---- # Single forward on [SOS, MASK × BS] with no conditioning, lazy-cached # on the model. Reused as PMI denominator for every step of every # block in this generate() call (and across subsequent calls). SOS = int(self.hp.start_speech_token) prior_override_v: Tensor | None = None if pmi_uncond_prior_precompute: prior_override_v = self._compute_uncond_block_prior( BS, SOS, MASK, dtype, device, ) # ---- inference engine (FlashInfer if available, else torch SDPA) ---- engine_max_seq = max(lp + N + 64, flashinfer_reserve_max_seq or 0) cached = self._cached_engine if ( cached is not None and cached.can_reuse( engine_max_seq, dtype, batch_size=B_fwd, page_size=page_size, ) ): engine = cached engine.reset() else: if ( use_cuda_graph and cached is not None and getattr(cached, "_max_seq_len", 0) < engine_max_seq and getattr(cached, "_batch_size", -1) == B_fwd and getattr(cached, "_dtype", None) == dtype ): # Grow allocation in 512-token buckets so we don't re-instantiate # the engine every time the sequence length nudges up by one. engine_max_seq = max( ((engine_max_seq + 511) // 512) * 512, int(getattr(cached, "_max_seq_len", 0) * 1.5), ) engine = build_engine( self, engine_max_seq, dtype, backend=backend, batch_size=B_fwd, page_size=page_size, ) if _ztb: assert prefix_emb_null is not None if isinstance(prefix_emb_cond, list): assert isinstance(prefix_emb_null, list) pfx_for_engine = [*prefix_emb_cond, *prefix_emb_null] else: assert isinstance(prefix_emb_null, torch.Tensor) pfx_for_engine = [ *[prefix_emb_cond[bi : bi + 1] for bi in range(B_usr)], *[prefix_emb_null[bi : bi + 1] for bi in range(B_usr)], ] shift_ctx = engine.prefix_forward(pfx_for_engine) else: shift_ctx = engine.prefix_forward(prefix_emb_cond) if use_cuda_graph: # ``capture_cuda_graph`` is a no-op on the torch SDPA fallback. engine.capture_cuda_graph(BS, speech_head=self.speech_head) self._cached_engine = engine # ---- block-by-block decoding ---- # ``_row_eos[bi]`` — row has emitted EOS, skip remaining blocks. # ``_block_done[bi]`` — inner k-loop finished for this row in the # current block (reset at every block boundary; not a sequence-level # flag). _row_eos = ( torch.zeros(B_usr, dtype=torch.bool, device=device) if B_usr > 1 else None ) _block_done = ( torch.zeros(B_usr, dtype=torch.bool, device=device) if B_usr > 1 else None ) block_marginal_priors: list[Tensor | None] = [None] * B_usr for b_idx in range(num_blocks): bs_ = b_idx * BS be_ = min(bs_ + BS, N) bl = be_ - bs_ # OmniVoice r_n count schedule for this block's K steps. Used as # the per-step unmask **floor** by the pmi_count_early outlier # rule. Computed once per block since ``bl`` only changes at the # tail. _omnivoice_sched = _omnivoice_unmask_schedule( n_total_mask=bl, num_steps=K, t_shift=float(omnivoice_schedule_t_shift), ) if _block_done is not None: _block_done.zero_() if _cross_text: for bi in range(B_usr): if ( b_idx >= _per_sample_num_blocks[bi] or (_row_eos is not None and bool(_row_eos[bi])) ): _block_done[bi] = True elif _row_eos is not None: for bi in range(B_usr): if bool(_row_eos[bi]): _block_done[bi] = True for k in range(K): # Per-row cache-start positions (cross-text & null-prefix paths # may differ between rows; same-text reuses a scalar). if _cross_text: cache_start: int | list[int] = [ _prefix_lens[bi] + bs_ for bi in range(B_fwd) ] else: cache_start = lp + bs_ sp_blk = self._embed_speech_tokens(xt[:, bs_:be_]).to(dtype) use_graph = engine.has_cuda_graph and bl == BS if use_graph: if k == 0: engine.set_shift_ctx(shift_ctx) blk_hidden, graph_logits = engine.block_forward_graph( sp_blk, cache_start, ) if graph_logits is not None: blk_logits = graph_logits else: shift_hidden = torch.cat( [shift_ctx, blk_hidden[:, : bl - 1]], dim=1, ) blk_logits = self.speech_head(shift_hidden) else: blk_hidden = engine.block_forward(sp_blk, cache_start) shift_hidden = torch.cat( [shift_ctx, blk_hidden[:, : bl - 1]], dim=1, ) blk_logits = self.speech_head(shift_hidden) pmi_cfg_probs_c: Tensor | None = None pmi_cfg_probs_u: Tensor | None = None if _ztb: logits_cond = blk_logits[:B_usr] logits_uncond = blk_logits[B_usr : 2 * B_usr] # Always pmi_cfg: PMI scores are combined on the PMI # scale via (1+w)·pmi_c − w·pmi_u. No other CFG/PMI # combination is exposed. pmi_cfg_probs_c = F.softmax(logits_cond, dim=-1) pmi_cfg_probs_u = F.softmax(logits_uncond, dim=-1) blk_logits = apply_zero_text_cfg_from_logits( logits_cond, logits_uncond, cfg_scale, ) step_break = False for bi in range(B_usr): if _block_done is not None and _block_done[bi]: continue pmi_c_bi = None if pmi_cfg_probs_c is None else pmi_cfg_probs_c[bi] pmi_u_bi = None if pmi_cfg_probs_u is None else pmi_cfg_probs_u[bi] r = _pmi_count_early_step_unmask( blk_logits[bi], xt[bi, bs_:be_], bl=bl, k=k, K=K, MASK=MASK, device=device, temperature=temperature, temperature_sampling=temperature_sampling, omnivoice_unmask_schedule_k=_omnivoice_sched[k], time_shift_tau=time_shift_tau, prior_override_v=prior_override_v, block_marginal_prior=block_marginal_priors[bi], pmi_cfg_probs_c_bl=pmi_c_bi, pmi_cfg_probs_u_bl=pmi_u_bi, cfg_scale_for_pmi=cfg_scale, position_temperature=position_temperature, ) xt[bi, bs_:be_] = r["xt_step"] block_marginal_priors[bi] = r["block_marginal_prior"] if _ztb: xt[B_usr + bi, bs_:be_] = r["xt_step"] row_hit_eos = bool( (xt[bi, bs_:be_] == stop_tok).any().item(), ) if r["n_mask"] == 0 or row_hit_eos: if _block_done is not None: _block_done[bi] = True if row_hit_eos and _row_eos is not None: _row_eos[bi] = True if B_usr == 1 and row_hit_eos: step_break = True elif B_usr == 1 and r["n_mask"] == 0: step_break = True if step_break: break if _block_done is not None and bool(_block_done.all().item()): break # Mid-block finalize: write the committed (possibly partial) tokens # of this block to the KV cache so the next block sees clean context. if b_idx < num_blocks - 1: fin_emb = self._embed_speech_tokens(xt[:, bs_:be_]).to(dtype) if _cross_text: fin_cs: int | list[int] = [ _prefix_lens[bi] + bs_ for bi in range(B_fwd) ] else: fin_cs = lp + bs_ fin_hidden = engine.block_forward(fin_emb, fin_cs) engine.advance_cache(bl) shift_ctx = fin_hidden[:, bl - 1 : bl, :].clone() # Early termination if all (same-text) rows have finished. if B_usr == 1: eos_pos = (xt[0, bs_:be_] == stop_tok).nonzero(as_tuple=True)[0] if len(eos_pos) > 0: return xt[:B_usr, : bs_ + eos_pos[0].item()] elif _row_eos is not None and bool(_row_eos.all().item()): break return xt[:B_usr]