Spaces:
Running on Zero
Running on Zero
| # SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. | |
| # SPDX-License-Identifier: MIT | |
| # | |
| # Permission is hereby granted, free of charge, to any person obtaining a | |
| # copy of this software and associated documentation files (the "Software"), | |
| # to deal in the Software without restriction, including without limitation | |
| # the rights to use, copy, modify, merge, publish, distribute, sublicense, | |
| # and/or sell copies of the Software, and to permit persons to whom the | |
| # Software is furnished to do so, subject to the following conditions: | |
| # | |
| # The above copyright notice and this permission notice shall be included in | |
| # all copies or substantial portions of the Software. | |
| # | |
| # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
| # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
| # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL | |
| # THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
| # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING | |
| # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER | |
| # DEALINGS IN THE SOFTWARE. | |
| # Copyright (c) Kyutai, all rights reserved. | |
| # This source code is licensed under the license found in the | |
| # LICENSE file in the root directory of this source tree. | |
| # Copyright (c) Meta Platforms, Inc. and affiliates. | |
| # All rights reserved. | |
| # | |
| # This source code is licensed under the license found in the | |
| # LICENSE file in the root directory of this source tree. | |
| from collections.abc import Callable | |
| from dataclasses import dataclass | |
| from functools import partial | |
| from os.path import splitext | |
| import logging | |
| import numpy as np | |
| import sys | |
| from typing import Optional, Union, List, Tuple, Callable, Iterator | |
| import sphn | |
| import torch | |
| from tqdm.auto import tqdm | |
| from ..utils.sampling import sample_token | |
| from ..utils.compile import CUDAGraphed | |
| from ..modules.streaming import StreamingStateDict, StreamingContainer, StreamingModule, load_streaming_state | |
| from ..modules.transformer import ( | |
| StreamingTransformer, | |
| create_norm_fn, | |
| ) | |
| logger = logging.getLogger(__name__) | |
| AUDIO_TOKENS_PER_STREAM = 8 | |
| FRAME_RATE_HZ = 12.5 | |
| SILENCE_TOKENS = np.array([948, 243, 1178, 546, 1736, 1030, 1978, 2008], dtype=np.int64) | |
| SINE_TOKENS = np.array([430, 1268, 381, 1611, 1095, 1495, 56, 472], dtype=np.int64) | |
| class LMOutput: | |
| # The logits are already re-aligned with the input codes | |
| # hence no extra shift is required, e.g. when computing CE | |
| logits: torch.Tensor # [B, K, T, card] | |
| mask: torch.Tensor # [B, K, T] | |
| text_logits: torch.Tensor # [B, 1, T, text_card] | |
| text_mask: torch.Tensor # [B, 1, T] | |
| def _delay_sequence(delays: List[int], tensor: torch.Tensor, padding: torch.Tensor) -> torch.Tensor: | |
| B, K, T = tensor.shape | |
| assert len(delays) == K, (len(delays), K) | |
| outs = [] | |
| for k, delay in enumerate(delays): | |
| assert delay >= 0 | |
| line = tensor[:, k].roll(delay, dims=1) | |
| if delay > 0: | |
| line[:, :delay] = padding[:, k] | |
| outs.append(line) | |
| return torch.stack(outs, dim=1) | |
| def _undelay_sequence(delays: List[int], tensor: torch.Tensor, | |
| fill_value: Union[int, float] = float('NaN')) -> Tuple[torch.Tensor, torch.Tensor]: | |
| B, K, T, *_ = tensor.shape | |
| assert len(delays) == K | |
| mask = torch.ones(B, K, T, dtype=torch.bool, device=tensor.device) | |
| outs = [] | |
| if all([delay == 0 for delay in delays]): | |
| return tensor, mask | |
| for k, delay in enumerate(delays): | |
| assert delay >= 0 | |
| line = tensor[:, k].roll(-delay, dims=1) | |
| if delay > 0: | |
| line[:, -delay:] = fill_value | |
| mask[:, k, -delay:] = 0 | |
| outs.append(line) | |
| return torch.stack(outs, dim=1), mask | |
| def create_sinewave(duration: float, sample_rate: int) -> np.ndarray: | |
| """Return a 440 Hz 'silent' sinewave of the given duration.""" | |
| t = np.linspace(0.0, duration, int(sample_rate * duration), endpoint=False) | |
| amplitude = 0.5 | |
| return amplitude * np.sin(2 * np.pi * 440.0 * t).astype(np.float32) | |
| def normalize_audio(wav: np.ndarray, sr: int, target_lufs: float) -> np.ndarray: | |
| """Normalize **mono** audio to a target LUFS level.""" | |
| import pyloudnorm as pyln | |
| # Ensure shape is (T,) | |
| if wav.ndim == 2 and wav.shape[0] == 1: | |
| wav = wav[0] | |
| meter = pyln.Meter(sr) | |
| loudness = meter.integrated_loudness(wav) | |
| return pyln.normalize.loudness(wav, loudness, target_lufs) | |
| def load_audio( | |
| filepath: str, sample_rate: int, | |
| ): | |
| """Yields audio samples in intervals of sample_interval_size""" | |
| sample_pcm, sample_sr = sphn.read(filepath) | |
| sample_pcm = sphn.resample( | |
| sample_pcm, src_sample_rate=sample_sr, dst_sample_rate=sample_rate | |
| ) # shape: (C, T) | |
| return sample_pcm | |
| def _iterate_audio(sample_pcm, sample_interval_size, max_len=sys.maxsize, pad=True): | |
| cnt = 0 | |
| while sample_pcm.shape[-1] > 0 and cnt < max_len: | |
| sample = sample_pcm[:, :sample_interval_size] | |
| sample_pcm = sample_pcm[:, sample_interval_size:] | |
| if sample_pcm.shape[-1] == 0 and pad: | |
| sample = np.concatenate( | |
| [ | |
| sample, | |
| np.zeros( | |
| ( | |
| sample.shape[0], | |
| sample_interval_size - sample.shape[-1], | |
| ) | |
| ), | |
| ], | |
| axis=1, | |
| ) | |
| cnt += 1 | |
| yield sample[0:1] # shape: (1, T) | |
| def encode_from_sphn(mimi, samples, max_batch=sys.maxsize): | |
| """ | |
| Takes an iterator of samples, batches them, encodes them; | |
| and yields the encoded samples one sample at a time in the same order. | |
| """ | |
| device = next(mimi.parameters()).device | |
| current_batch = [] | |
| done_flag = False | |
| # TO-DO: Fix the batching bug | |
| max_batch = 1 | |
| while True: | |
| try: | |
| sample = next(samples) | |
| tensor = torch.tensor(sample, dtype=torch.float32, device=device) | |
| tensor = tensor.unsqueeze(0) # shape: (1, C, T) | |
| current_batch.append(tensor) | |
| except StopIteration: | |
| done_flag = True | |
| if (not done_flag) and len(current_batch) < max_batch: | |
| continue | |
| if not current_batch: | |
| break | |
| batch = torch.cat(current_batch, dim=0) # shape: (B, C, T) | |
| encoded = mimi.encode(batch) # shape: (B, K, F) | |
| separated = torch.unbind(encoded, dim=0) # shape: (K, F) | |
| reshaped = [x.unsqueeze(0) for x in separated] # shape: (1, K, F) | |
| detached = [x.detach().clone() for x in reshaped] | |
| current_batch = [] | |
| yield from detached # shape: (1, K, F) | |
| if done_flag: | |
| break | |
| class ScaledEmbedding(torch.nn.Embedding): | |
| """Boost learning rate for embeddings (with `scale`). | |
| Args: | |
| norm (bool): if True, uses a layer norm after the embedding. | |
| zero_idx (int): special value indicating that the output should be exactly 0. | |
| """ | |
| def __init__(self, *args, norm: bool = False, zero_idx: int = -1, **kwargs): | |
| super().__init__(*args, **kwargs) | |
| self.norm = None | |
| if norm: | |
| self.norm = create_norm_fn("layer_norm", self.embedding_dim) | |
| assert zero_idx < 0, "Please use negative values for the zero_idx." | |
| self.zero_idx = zero_idx | |
| def forward(self, input, *args, **kwargs): | |
| is_zero = input == self.zero_idx | |
| zero = torch.zeros(1, dtype=input.dtype, device=input.device) | |
| input = input.clamp(min=0) | |
| y = super().forward(input, *args, **kwargs) | |
| if self.norm is not None: | |
| y = self.norm(y) | |
| y = torch.where(is_zero[..., None], zero, y) | |
| return y | |
| class LMModel(StreamingContainer): | |
| """Transformer-based language model on multiple streams of codes. | |
| Args: | |
| n_q (int): Number of parallel streams to model as input. | |
| dep_q (int): Number of parallel streams to model in the depformer. | |
| card (int): Cardinality, vocabulary size. | |
| text_card (int): Cardinality of the text vocabulary. | |
| dim (int): Dimension of the transformer encoder. | |
| num_heads (int): Number of heads for the transformer encoder. | |
| hidden_scale (int): Scale for hidden feed forward dimension of the transformer encoder. | |
| norm (str): Normalization method. | |
| norm_emb (bool): Whether to normalize embeddings. | |
| bias_proj (bool): Use bias for output projections. | |
| depformer_*: params used for the Depformer Transformer, all the other will be shared. | |
| depformer_multi_linear (bool): if True, uses one linear layer per codebook to project the | |
| output of the main transformer to the Depformer latent space. | |
| depformer_dim_feedforward (int| list[int]| None): If None, defaults to hidden_scale * depformer_dim. | |
| existing_text_padding_id (bool): if True, will use a different token for the initial text token, and | |
| the text padding token. | |
| same_initial (bool): if True, uses the same initial tokens for both text and audio mode. | |
| **kwargs: Additional parameters for the transformer encoder. | |
| """ | |
| def __init__( | |
| self, | |
| delays: List[int] = [0], | |
| n_q: int = 8, | |
| dep_q: int = 8, | |
| card: int = 1024, | |
| text_card: int = 32000, | |
| dim: int = 128, | |
| num_heads: int = 8, | |
| hidden_scale: int = 4, | |
| norm: str = "layer_norm", | |
| norm_emb: bool = False, | |
| bias_proj: bool = False, | |
| depformer_dim: int = 256, | |
| depformer_dim_feedforward: int | list[int] | None = None, | |
| depformer_multi_linear: bool = False, | |
| depformer_weights_per_step: bool = False, | |
| depformer_weights_per_step_schedule: list[int] | None = None, | |
| depformer_pos_emb: str = "sin", | |
| existing_text_padding_id: Optional[int] = None, | |
| context: Optional[int] = None, | |
| device=None, | |
| dtype=None, | |
| **kwargs, | |
| ): | |
| super().__init__() | |
| self.n_q = n_q | |
| self.dep_q = dep_q | |
| self.card = card | |
| self.text_card = text_card | |
| assert len(delays) == self.num_codebooks, "unexpected number of delays" | |
| self.delays = delays | |
| self.dim = dim | |
| self.existing_text_padding_id = existing_text_padding_id | |
| self.context = context | |
| self.depformer_weights_per_step_schedule = depformer_weights_per_step_schedule | |
| if depformer_weights_per_step_schedule is not None: | |
| assert len(depformer_weights_per_step_schedule) == dep_q | |
| kwargs["context"] = context | |
| EmbeddingFactory = partial( | |
| ScaledEmbedding, | |
| norm=norm_emb, | |
| device=device, | |
| dtype=dtype, | |
| zero_idx=self.zero_token_id, | |
| ) | |
| self.EmbeddingFactory = EmbeddingFactory | |
| self.emb = torch.nn.ModuleList( | |
| [EmbeddingFactory(self.card + 1, dim) for _ in range(n_q)] | |
| ) | |
| # Text card + padding token (if not in the original tokenizer) | |
| extra_text = self.existing_text_padding_id is None | |
| # Unlike for audio, here we authorize the model to output the special token. | |
| self.text_emb = EmbeddingFactory(text_card + 1, dim) | |
| self.text_linear = torch.nn.Linear(dim, text_card + extra_text, bias=bias_proj) | |
| depformer_prefix = "depformer_" | |
| main_kwargs = { | |
| k: v for k, v in kwargs.items() if not k.startswith(depformer_prefix) | |
| } | |
| self.transformer = StreamingTransformer( | |
| d_model=dim, | |
| num_heads=num_heads, | |
| dim_feedforward=int(hidden_scale * dim), | |
| norm=norm, | |
| device=device, | |
| dtype=dtype, | |
| **main_kwargs, | |
| ) | |
| self.out_norm = create_norm_fn(norm, dim) | |
| self.depformer_multi_linear = depformer_multi_linear | |
| kwargs_dep = main_kwargs.copy() | |
| kwargs_dep.update( | |
| { | |
| k.removeprefix(depformer_prefix): v | |
| for k, v in kwargs.items() | |
| if k.startswith(depformer_prefix) | |
| } | |
| ) | |
| kwargs_dep["positional_embedding"] = depformer_pos_emb | |
| kwargs_dep["context"] = None | |
| if depformer_weights_per_step: | |
| kwargs_dep["weights_per_step"] = dep_q | |
| if depformer_multi_linear: | |
| # One linear layer per codebook to project different informations from the main model. | |
| self.depformer_in = torch.nn.ModuleList( | |
| [torch.nn.Linear(dim, depformer_dim, bias=False) for _ in range(dep_q)] | |
| ) | |
| else: | |
| self.depformer_in = torch.nn.ModuleList( | |
| [torch.nn.Linear(dim, depformer_dim, bias=False)] | |
| ) | |
| # Only using up to dep_q - 1 because the last codebook is never an input to Depformer. | |
| self.depformer_emb = torch.nn.ModuleList( | |
| [EmbeddingFactory(self.card + 1, depformer_dim) for _ in range(dep_q - 1)] | |
| ) | |
| self.depformer_text_emb = EmbeddingFactory(text_card + 1, depformer_dim) | |
| if depformer_dim_feedforward is None: | |
| depformer_dim_feedforward = int(hidden_scale * depformer_dim) | |
| self.depformer = StreamingTransformer( | |
| d_model=depformer_dim, | |
| dim_feedforward=depformer_dim_feedforward, | |
| norm=norm, | |
| device=device, | |
| dtype=dtype, | |
| **kwargs_dep, | |
| ) | |
| self.depformer.set_streaming_propagate(False) | |
| dim = depformer_dim # we will directly apply the next linears to the output of the Depformer. | |
| self.linears = torch.nn.ModuleList( | |
| [torch.nn.Linear(dim, self.card, bias=bias_proj) for _ in range(dep_q)] | |
| ) | |
| def initial_token_id(self) -> int: | |
| """Token id for the start of sequence (audio).""" | |
| return self.card | |
| def text_initial_token_id(self) -> int: | |
| """Token id for the start of sequence (text).""" | |
| return self.text_card | |
| def text_padding_token_id(self) -> int: | |
| """Token id for text padding.""" | |
| if self.existing_text_padding_id is None: | |
| return self.text_card | |
| else: | |
| return self.existing_text_padding_id | |
| def end_of_text_padding_id(self) -> int: | |
| """Token id for optionally marking the last padding step for a word.""" | |
| return 0 | |
| def zero_token_id(self) -> int: | |
| """Special value in the input tokens, indicating that no sampling should | |
| happen for that value, and no input should be given to the model.""" | |
| return -1 | |
| def ungenerated_token_id(self) -> int: | |
| """Special value that can be provided in the prompt to indicate that this specific | |
| value should be predicted and sampled. This allows for partial teacher forcing, by generating | |
| one modality, with the other one fixed. | |
| """ | |
| return -2 | |
| def device(self): | |
| first_param = next(iter(self.parameters())) | |
| return first_param.device | |
| def num_codebooks(self) -> int: | |
| return self.n_q + 1 | |
| def num_audio_codebooks(self) -> int: | |
| return self.n_q | |
| def audio_offset(self) -> int: | |
| return 1 | |
| def _get_initial_token(self) -> torch.Tensor: | |
| # Returns the initial token that will be fed to the model to predict the very first timestep. | |
| # The output shape will be [B, K, 1]. | |
| device = next(iter(self.parameters())).device | |
| zero = torch.full( | |
| [1, 1, 1], self.zero_token_id, device=device, dtype=torch.long | |
| ) | |
| special = torch.full_like(zero, self.initial_token_id) | |
| text_special = torch.full_like(zero, self.text_initial_token_id) | |
| audio_token = special | |
| text_token = text_special | |
| audio_token = audio_token.expand(-1, self.num_audio_codebooks, -1) | |
| token = torch.cat([text_token, audio_token], dim=1) | |
| return token | |
| def embed_codes(self, sequence: torch.Tensor) -> torch.Tensor: | |
| B, K, S = sequence.shape | |
| assert ( | |
| K == self.num_codebooks | |
| ), f"Sequence shape {sequence.shape} must match the number of codebooks." | |
| input_sequence = sequence | |
| input_ = None | |
| for cb_index in range(self.num_audio_codebooks): | |
| audio_emb = self.emb[cb_index]( | |
| input_sequence[:, cb_index + self.audio_offset] | |
| ) | |
| input_ = audio_emb if input_ is None else input_ + audio_emb | |
| text_emb = self.text_emb(input_sequence[:, 0]) | |
| input_ = text_emb if input_ is None else input_ + text_emb | |
| return input_ | |
| def forward_codes( | |
| self, | |
| sequence: torch.Tensor, | |
| ) -> tuple[torch.Tensor, torch.Tensor]: | |
| return self.forward_embeddings(self.embed_codes(sequence)) | |
| def forward_embeddings(self, input: torch.Tensor) -> tuple[torch.Tensor, torch.Tensor]: | |
| # print("EMBED:", input[0, 0, :10].float().cpu().tolist()) # DEBUG | |
| transformer_out = self.transformer(input) | |
| if self.out_norm: | |
| transformer_out = self.out_norm(transformer_out) | |
| assert isinstance(transformer_out, torch.Tensor) | |
| text_logits = self.text_linear(transformer_out) | |
| text_logits = text_logits[:, None] | |
| return transformer_out, text_logits | |
| def forward_depformer( | |
| self, | |
| depformer_cb_index: int, | |
| sequence: torch.Tensor, | |
| transformer_out: torch.Tensor, | |
| ) -> torch.Tensor: | |
| B, K, S = sequence.shape | |
| assert ( | |
| K == 1 | |
| ), f"Codebooks for Depformer streaming should be passed 1 by 1, got {K}." | |
| assert ( | |
| S == 1 | |
| ), f"Steps for Depformer streaming should be passed 1 by 1, got {S}." | |
| assert ( | |
| transformer_out.shape[1] == 1 | |
| ), "Transformer out should be a for a single step." | |
| last_token_input: Optional[torch.Tensor] = None | |
| depformer_input = transformer_out | |
| if self.depformer_multi_linear: | |
| depformer_input = self.depformer_in[depformer_cb_index](depformer_input) | |
| else: | |
| depformer_input = self.depformer_in[0](depformer_input) | |
| if depformer_cb_index == 0: | |
| last_token_input = self.depformer_text_emb(sequence[:, 0]) | |
| else: | |
| last_token_input = self.depformer_emb[depformer_cb_index - 1]( | |
| sequence[:, 0] | |
| ) | |
| depformer_input = depformer_input + last_token_input | |
| assert depformer_input.shape[1] == 1 | |
| # depformer_input is [B, 1, depformer_dim]. | |
| # The streaming state of the depformer ensures that the proper layer is run. | |
| dep_output = self.depformer(depformer_input) | |
| logits = self.linears[depformer_cb_index](dep_output) | |
| logits = logits[:, None] | |
| assert logits.dim() == 4, logits.shape # [B, Ka, S, card] | |
| return logits | |
| def forward_depformer_training( | |
| self, | |
| sequence: torch.Tensor, | |
| transformer_out: torch.Tensor, | |
| ) -> torch.Tensor: | |
| B, K, T = sequence.shape | |
| Ka = self.dep_q | |
| assert ( | |
| K == self.num_codebooks | |
| ), f"Codebooks for Depformer training should be passed all at once, got {K,}." | |
| depformer_inputs = [] | |
| for cb_index in range(Ka): | |
| if self.depformer_multi_linear: | |
| linear_index = cb_index | |
| if self.depformer_weights_per_step_schedule is not None: | |
| linear_index = self.depformer_weights_per_step_schedule[cb_index] | |
| transformer_in = self.depformer_in[linear_index](transformer_out) | |
| else: | |
| transformer_in = self.depformer_in[0](transformer_out) | |
| if cb_index == 0: | |
| token_in = self.depformer_text_emb(sequence[:, 0]) | |
| else: | |
| token_in = self.depformer_emb[cb_index - 1](sequence[:, cb_index + self.audio_offset - 1]) | |
| depformer_inputs.append(token_in + transformer_in) | |
| depformer_input = torch.stack(depformer_inputs, 2) | |
| # depformer_input is [B, T, K, depformer_dim], reshaping to [B * T, K, D] | |
| depformer_input = depformer_input.view(B * T, Ka, -1) | |
| depformer_output = self.depformer(depformer_input) | |
| all_logits = [] | |
| for cb_index in range(Ka): | |
| logits = self.linears[cb_index](depformer_output[:, cb_index]) | |
| all_logits.append(logits.view(B, T, -1)) | |
| logits = torch.stack(all_logits, 1) | |
| assert logits.dim() == 4, logits.shape # [B, Ka, T, card] | |
| return logits | |
| def forward_train(self, codes: torch.Tensor): | |
| B, K, T = codes.shape | |
| # Delaying codes and removing the last time step that will never be an input. | |
| initial = self._get_initial_token().expand(B, -1, -1) | |
| delayed_codes = _delay_sequence(self.delays, codes, initial) | |
| # Inserting the empty tokens for the first time step. | |
| delayed_codes = torch.cat([initial, delayed_codes], dim=2) | |
| # LLM Backbone | |
| transformer_out, text_logits = self.forward_codes(delayed_codes[:, :, :-1]) | |
| logits = self.forward_depformer_training(delayed_codes[:, :, 1:], transformer_out) | |
| # map back the logits on pattern sequence to logits on original codes: [B, K, S, card] -> [B, K, T, card] | |
| # and provide the corresponding mask over invalid positions of tokens. We will with NaN values invalid positions | |
| # to ensure they properly handled. | |
| logits, logits_mask = _undelay_sequence( | |
| self.delays[self.audio_offset:self.audio_offset + self.dep_q], | |
| logits, fill_value=float('NaN')) | |
| logits_mask &= (codes[:, self.audio_offset: self.audio_offset + self.dep_q] != self.zero_token_id) | |
| text_logits, text_logits_mask = _undelay_sequence(self.delays[:1], text_logits, fill_value=float('NaN')) | |
| text_logits_mask &= (codes[:, :1] != self.zero_token_id) | |
| return LMOutput(logits, logits_mask, text_logits, text_logits_mask) | |
| class _LMGenState: | |
| cache: torch.Tensor | |
| provided: torch.Tensor | |
| initial: torch.Tensor | |
| graphed_main: CUDAGraphed | |
| graphed_embeddings: CUDAGraphed | |
| graphed_depth: CUDAGraphed | |
| offset: int = 0 | |
| def reset(self): | |
| self.offset = 0 | |
| self.provided[:] = False | |
| def create_loss_report( | |
| state_cache: torch.Tensor, | |
| lm_model: LMModel, | |
| text_logits: torch.Tensor, | |
| audio_logits: torch.Tensor, | |
| target: torch.Tensor, | |
| sampled_text_token: torch.Tensor, | |
| sampled_audio_tokens: torch.Tensor, | |
| target_position: int, | |
| ) -> dict[str, torch.Tensor]: | |
| report = {} | |
| B = state_cache.shape[0] | |
| # model_tokens is the sampled output from model_logits | |
| model_tokens = torch.zeros_like(state_cache[:, :, target_position]) | |
| model_tokens[:, 0] = sampled_text_token | |
| model_tokens[:, 1 : lm_model.dep_q + 1] = sampled_audio_tokens | |
| report.update( | |
| { | |
| "forced_tokens": torch.zeros((B, lm_model.dep_q + 1)), | |
| "model_tokens": torch.zeros((B, lm_model.dep_q + 1)), | |
| "ranks_of_forced": torch.zeros((B, lm_model.dep_q + 1)), | |
| "losses": torch.zeros((B, lm_model.dep_q+1)), | |
| } | |
| ) | |
| report["model_tokens"] = model_tokens.clone() | |
| report["forced_tokens"] = target.clone() | |
| # Text Channel | |
| text_logits = text_logits.squeeze(dim=1).squeeze(dim=1) | |
| target = target[:, 0].squeeze(1).clone() | |
| text_probs = torch.softmax(text_logits, dim=-1) | |
| text_ranks = torch.argsort(text_probs, dim=-1, descending=True) | |
| for b in range(B): | |
| forced_token = target[b].item() | |
| try: | |
| rank = (text_ranks[b] == forced_token).nonzero().item() | |
| except RuntimeError: | |
| rank = lm_model.zero_token_id | |
| report["ranks_of_forced"][b, 0] = rank | |
| target[target == lm_model.text_initial_token_id] = -100 | |
| text_loss = torch.nn.functional.cross_entropy( | |
| text_logits, | |
| target, | |
| ignore_index=-100, | |
| ) | |
| report["losses"][:, 0] = text_loss | |
| # Audio Channels | |
| for k in range(lm_model.dep_q): | |
| target = target[:, k+1].squeeze(1).clone() | |
| channel_logits = audio_logits[:, k, :] | |
| audio_probs = torch.softmax(channel_logits, dim=-1) | |
| audio_ranks = torch.argsort(audio_probs, dim=-1, descending=True) | |
| for b in range(B): | |
| forced_token = target[b].item() | |
| try: | |
| rank = (audio_ranks[b] == forced_token).nonzero().item() | |
| except RuntimeError: | |
| rank = lm_model.zero_token_id | |
| report["ranks_of_forced"][b, k + 1] = rank | |
| target[target == lm_model.initial_token_id] = -100 | |
| audio_loss = torch.nn.functional.cross_entropy( | |
| channel_logits, | |
| target, | |
| ignore_index=-100, | |
| ) | |
| report["losses"][:, k + 1] = audio_loss | |
| return report | |
| class LMGen(StreamingModule[_LMGenState]): | |
| def __init__( | |
| self, | |
| lm_model: LMModel, | |
| device: str | torch.device, | |
| use_sampling: bool = True, | |
| temp: float = 0.8, | |
| temp_text: float = 0.7, | |
| top_k: int = 250, | |
| top_k_text: int = 25, | |
| check: bool = False, | |
| report_loss: bool = False, | |
| return_logits: bool = False, | |
| audio_silence_frame_cnt: int = 1, | |
| text_prompt_tokens: Optional[list[int]] = None, | |
| save_voice_prompt_embeddings: bool = False, | |
| sample_rate: int = 32000, | |
| frame_rate: int = FRAME_RATE_HZ, | |
| ): | |
| assert not lm_model.training, "generation shouldn't be used in training mode." | |
| super().__init__() | |
| self.lm_model = lm_model | |
| self.use_sampling = use_sampling | |
| self.temp = temp | |
| self.temp_text = temp_text | |
| self.top_k = top_k | |
| self.top_k_text = top_k_text | |
| self.text_prompt_tokens = text_prompt_tokens | |
| self.audio_silence_frame_cnt = audio_silence_frame_cnt | |
| self.voice_prompt = None | |
| self.zero_text_code = 3 | |
| self._frame_rate = frame_rate | |
| self._sample_rate = sample_rate | |
| self._frame_size = int(self._sample_rate / self._frame_rate) | |
| self._zero_frame = torch.zeros(1, 1, self._frame_size, device=device) | |
| duration = self._frame_size / self._sample_rate | |
| sine = create_sinewave(duration, self._sample_rate) | |
| self._sine_frame = torch.tensor(sine, device=device).unsqueeze(0).unsqueeze(0) # (1,1,T) | |
| self.check = check | |
| self.report_loss = report_loss | |
| if report_loss: | |
| return_logits = True | |
| self.return_logits = return_logits | |
| self.max_delay = max( | |
| lm_model.delays | |
| ) # with delays, we need to generate a few more time steps. | |
| self.delays_cuda = torch.tensor( | |
| lm_model.delays, device=lm_model.device, dtype=torch.long | |
| ) | |
| self.save_voice_prompt_embeddings = save_voice_prompt_embeddings | |
| self.voice_prompt_audio: Optional[torch.Tensor] = None | |
| self.voice_prompt_cache: Optional[torch.Tensor] = None | |
| self.voice_prompt_embeddings: Optional[torch.Tensor] = None | |
| #self.voice_prompt_mimi_streaming_state: Optional[StreamingStateDict] = None | |
| def _init_streaming_state(self, batch_size: int) -> _LMGenState: | |
| lm_model = self.lm_model | |
| initial = lm_model._get_initial_token() | |
| cache = torch.full( | |
| (batch_size, self.lm_model.num_codebooks, self.max_delay + 3), | |
| lm_model.ungenerated_token_id, | |
| device=lm_model.device, | |
| dtype=torch.long, | |
| ) | |
| provided = torch.full( | |
| (batch_size, self.lm_model.num_codebooks, self.max_delay + 3), | |
| False, | |
| device=lm_model.device, | |
| dtype=torch.bool | |
| ) | |
| disable = lm_model.device.type != 'cuda' | |
| # disable = True # DEBUG | |
| graphed_main = CUDAGraphed(lm_model.forward_codes, disable=disable) | |
| graphed_embeddings = CUDAGraphed(lm_model.forward_embeddings, disable=disable) | |
| graphed_depth = CUDAGraphed(self.depformer_step, disable=disable) | |
| return _LMGenState(cache, provided, initial, graphed_main, graphed_embeddings, graphed_depth) | |
| def prepare_step_input(self, | |
| input_tokens: torch.Tensor=None, | |
| moshi_tokens:torch.Tensor=None, | |
| text_token:torch.Tensor=None, | |
| ): | |
| state = self._streaming_state | |
| if state is None: | |
| raise RuntimeError( | |
| "You should wrap those calls with a `with lm_gen.streaming(): ...`." | |
| ) | |
| lm_model = self.lm_model | |
| # audio_tokens_per_stream = lm_model.dep_q//2 | |
| needed_tokens = lm_model.num_codebooks - AUDIO_TOKENS_PER_STREAM - 1 | |
| CT = state.cache.shape[2] | |
| #### | |
| # Fill Cache with provided tokens at state.offset (target) + delays | |
| if input_tokens is not None: | |
| assert input_tokens.dim() == 3, "Shape should be [B, K, T]." | |
| B, Ki, S = input_tokens.shape | |
| assert S == 1, "Only support being given steps one by one." | |
| assert ( | |
| Ki == needed_tokens | |
| ), f"We expect {needed_tokens} tokens from the user stream, got {Ki}." | |
| for q_other in range(input_tokens.shape[1]): | |
| k = AUDIO_TOKENS_PER_STREAM + 1 + q_other | |
| delay = lm_model.delays[k] | |
| write_position = (state.offset + delay) % CT | |
| state.cache[:, k, write_position : write_position + 1] = input_tokens[:, q_other] | |
| state.provided[:, k, write_position : write_position + 1] = True | |
| if moshi_tokens is not None: | |
| assert moshi_tokens.dim() == 3, "Shape should be [B, K, T]." | |
| B, Ki, S = moshi_tokens.shape | |
| assert S == 1, "Only support being given steps one by one." | |
| assert ( | |
| Ki == needed_tokens | |
| ), f"We expect {needed_tokens} tokens from the moshi stream, got {Ki}." | |
| for q_moshi in range(moshi_tokens.shape[1]): | |
| k = 1 + q_moshi | |
| delay = lm_model.delays[k] | |
| write_position = (state.offset + delay) % CT | |
| state.cache[:, k, write_position : write_position + 1] = moshi_tokens[:, q_moshi] | |
| state.provided[:, k, write_position : write_position + 1] = True | |
| if text_token is not None: | |
| write_position = (state.offset + lm_model.delays[0]) % CT | |
| state.cache[:, 0, write_position] = text_token | |
| state.provided[:, 0, write_position] = True | |
| for k, delay in enumerate(lm_model.delays): | |
| # Only for the very beginning, we extend the initial token for the acoustic | |
| # token that are delayed, and thus have no good value to take. | |
| if state.offset <= delay: | |
| state.cache[:, k, state.offset % CT] = state.initial[:, k, 0] | |
| state.provided[:, k, state.offset % CT] = True | |
| #### | |
| # Perform inference at state.offset - 1 (model_input); forcing with tokens at state.offset (target) when provided | |
| if state.offset == 0: | |
| # We can't report loss or force depth tranformer tokens until we're at step 2 | |
| # And we need to initialize the delay-0 cache where it's not provided for step 2 | |
| state.cache[:, :, 0] = state.initial[:, :, 0] # torch.where(state.provided[:, :, 0], state.cache[:, :, 0], state.initial[:, :, 0]) | |
| state.offset += 1 | |
| return None | |
| model_input_position = (state.offset-1) % CT | |
| target_position = state.offset % CT | |
| input_ = state.cache[:, :, model_input_position : model_input_position + 1] | |
| target_ = state.cache[:, :, target_position : target_position + 1] | |
| provided_ = state.provided[:, :, target_position : target_position + 1] | |
| if self.check: | |
| # Check that we are not feeding in any value that is not generated yet. | |
| assert not (input_ == lm_model.ungenerated_token_id).any(), ( | |
| state.offset, | |
| input_, | |
| ) | |
| assert (input_[:, lm_model.audio_offset :] <= lm_model.card).all(), input_ | |
| assert (input_[:, :1] <= lm_model.text_card).all() | |
| return input_, provided_, target_, model_input_position, target_position | |
| def step(self, input_tokens: torch.Tensor=None, moshi_tokens:torch.Tensor=None, text_token:torch.Tensor=None, | |
| return_embeddings: bool=False) \ | |
| -> torch.Tensor | tuple[torch.Tensor, torch.Tensor] | tuple[torch.Tensor, dict[str, torch.Tensor]]: | |
| state = self._streaming_state | |
| lm_model = self.lm_model | |
| prepared_inputs = self.prepare_step_input( | |
| input_tokens, moshi_tokens, text_token, | |
| ) | |
| # print("INPUT:", None if input_tokens is None else input_tokens.squeeze().cpu().tolist()) # DEBUG | |
| # print("MOSHI:", None if moshi_tokens is None else moshi_tokens.squeeze().cpu().tolist()) # DEBUG | |
| if prepared_inputs is None: | |
| return (None, None) if self.report_loss or self.return_logits else None | |
| input_, provided_, target_, model_input_position, target_position = prepared_inputs | |
| if self.check: | |
| # Check that we are not feeding in any value that is not generated yet. | |
| assert not (input_ == lm_model.ungenerated_token_id).any(), ( | |
| state.offset, | |
| input_, | |
| ) | |
| assert (input_[:, lm_model.audio_offset :] <= lm_model.card).all(), input_ | |
| assert (input_[:, :1] <= lm_model.text_card).all() | |
| embeddings = None | |
| if return_embeddings: | |
| embeddings = self.lm_model.embed_codes(input_) | |
| transformer_out, text_logits = state.graphed_main(input_) | |
| output = self.process_transformer_output( | |
| transformer_out, | |
| text_logits, | |
| provided_, | |
| target_, | |
| model_input_position, | |
| target_position, | |
| ) | |
| if return_embeddings: | |
| return output, embeddings | |
| return output | |
| def step_embeddings(self, embeddings: torch.Tensor): | |
| state = self._streaming_state | |
| lm_model = self.lm_model | |
| needed_input_tokens = lm_model.num_codebooks - AUDIO_TOKENS_PER_STREAM - 1 | |
| _dummy_audio_token = lm_model._get_initial_token() | |
| while True: | |
| prepared_inputs = self.prepare_step_input( | |
| input_tokens=_dummy_audio_token[:, 1:1+needed_input_tokens], moshi_tokens=_dummy_audio_token[:, 1+needed_input_tokens:], text_token=self.zero_text_code, | |
| ) | |
| if prepared_inputs is not None: | |
| break | |
| _, provided_, target_, model_input_position, target_position = prepared_inputs | |
| transformer_out, text_logits = state.graphed_embeddings(embeddings) | |
| return self.process_transformer_output( | |
| transformer_out, | |
| text_logits, | |
| provided_, | |
| target_, | |
| model_input_position, | |
| target_position, | |
| ) | |
| def process_transformer_output(self, transformer_out, text_logits, provided_, target_, model_input_position, target_position): | |
| state = self._streaming_state | |
| lm_model = self.lm_model | |
| # Shape of text_logits should be [B, K_text=1, T=1, Card_text] | |
| sampled_text_token = sample_token( | |
| text_logits.float(), | |
| self.use_sampling, | |
| self.temp_text, | |
| self.top_k_text, | |
| ) | |
| assert sampled_text_token.dim() == 3, sampled_text_token.shape | |
| assert sampled_text_token.shape[2] == 1 | |
| assert sampled_text_token.shape[1] == 1, "Only one text stream supported." | |
| sampled_text_token = sampled_text_token[:, 0, 0] # shape is [B] | |
| next_text_token = torch.where(provided_[:, 0, 0], target_[:, 0, 0], sampled_text_token) | |
| if self.return_logits: | |
| sampled_audio_tokens, audio_logits = state.graphed_depth(next_text_token, transformer_out, target_[:,lm_model.audio_offset:,0], provided_[:,lm_model.audio_offset:,0]) # [B, K_audio, Card_audio] | |
| else: | |
| sampled_audio_tokens = state.graphed_depth(next_text_token, transformer_out, target_[:,lm_model.audio_offset:,0], provided_[:,lm_model.audio_offset:,0]) | |
| state.provided[:, :, model_input_position] = False | |
| #### | |
| # Fill cache with generated tokens at state.offset (where not provided) | |
| state.cache[:, 0, target_position] = torch.where( | |
| ~state.provided[:, 0, target_position], | |
| sampled_text_token, | |
| state.cache[:, 0, target_position], | |
| ) | |
| state.cache[:, 1 : lm_model.dep_q + 1, target_position] = torch.where( | |
| ~state.provided[:, 1 : lm_model.dep_q + 1, target_position], | |
| sampled_audio_tokens, | |
| state.cache[:, 1 : lm_model.dep_q + 1, target_position], | |
| ) | |
| #### | |
| # Calculate loss of model logits (based on state.offset - 1) compared to target (state.offset) | |
| report = {} | |
| if self.report_loss: | |
| report = create_loss_report( | |
| state_cache=state.cache, | |
| lm_model=lm_model, | |
| text_logits=text_logits, | |
| audio_logits=audio_logits, | |
| target=target_, | |
| sampled_text_token=sampled_text_token, | |
| sampled_audio_tokens=sampled_audio_tokens, | |
| target_position=target_position, | |
| ) | |
| #### | |
| # Collect outputs for state.offset - max_delay | |
| if state.offset <= self.max_delay: | |
| state.offset += 1 | |
| if self.report_loss: | |
| return None, report | |
| if self.return_logits: | |
| return None, None | |
| else: | |
| return None | |
| B = state.cache.shape[0] | |
| CT = state.cache.shape[2] | |
| gen_delays_cuda = self.delays_cuda[: lm_model.dep_q + 1] | |
| index = ( | |
| ((state.offset - self.max_delay + gen_delays_cuda) % CT) | |
| .view(1, -1, 1) | |
| .expand(B, -1, 1) | |
| ) | |
| out = state.cache.gather(dim=2, index=index) | |
| state.offset += 1 | |
| if self.report_loss: | |
| return out, report | |
| elif self.return_logits and not self.report_loss: | |
| return out, (text_logits.clone(), audio_logits.clone()) | |
| else: | |
| return out | |
| def load_voice_prompt(self, voice_prompt: str): | |
| self.voice_prompt = voice_prompt | |
| raw_audio = load_audio( | |
| voice_prompt, self._sample_rate, | |
| ) # shape: (1, T) for mono | |
| # Normalize to -24 LUFS (mono-safe) | |
| raw_audio = normalize_audio(raw_audio, self._sample_rate, -24.0) | |
| # Keep shape (1, T) because your encoder expects channels-first | |
| if raw_audio.ndim == 1: | |
| raw_audio = raw_audio[None, :] | |
| self.voice_prompt_audio = raw_audio | |
| self.voice_prompt_cache: Optional[torch.Tensor] = None | |
| self.voice_prompt_embeddings: Optional[torch.Tensor] = None | |
| def load_voice_prompt_embeddings(self, path: str): | |
| self.voice_prompt = path | |
| state = torch.load(path) | |
| self.voice_prompt_audio = None | |
| self.voice_prompt_embeddings = state["embeddings"].to(self.lm_model.device) | |
| self.voice_prompt_cache = state["cache"].to(self.lm_model.device) | |
| def _encode_zero_frame(self) -> torch.Tensor: | |
| return torch.as_tensor( | |
| SILENCE_TOKENS, | |
| dtype=torch.long, | |
| device=self.lm_model.device, | |
| ).view(1, 8, 1) | |
| def _encode_sine_frame(self) -> torch.Tensor: | |
| return torch.as_tensor( | |
| SINE_TOKENS, | |
| dtype=torch.long, | |
| device=self.lm_model.device, | |
| ).view(1, 8, 1) | |
| def _encode_voice_prompt_frames(self, mimi): | |
| return encode_from_sphn( | |
| mimi, | |
| _iterate_audio( | |
| self.voice_prompt_audio, | |
| sample_interval_size=self._frame_size, | |
| pad=True, | |
| ), | |
| max_batch=1, | |
| ) | |
| def _step_voice_prompt_frame(self, | |
| voice_prompt_frame_tokens: torch.Tensor, | |
| saved_embeddings: Optional[list[torch.Tensor]]=None, | |
| ): | |
| # Always use zero_text_code during voice prompt | |
| out = self.step( | |
| moshi_tokens=voice_prompt_frame_tokens, | |
| text_token=self.zero_text_code, | |
| input_tokens=self._encode_sine_frame(), | |
| return_embeddings=self.save_voice_prompt_embeddings, | |
| ) | |
| if out is not None and self.save_voice_prompt_embeddings: | |
| _, embeddings = out | |
| saved_embeddings.append(embeddings) | |
| def _step_voice_prompt_core(self, mimi) -> Iterator[None]: | |
| """Shared core for stepping through the voice prompt. | |
| This generator yields at each *checkpoint* where the async wrapper may want to | |
| consult `is_alive`. The core itself is intentionally unaware of connection state. | |
| """ | |
| if self.voice_prompt_embeddings is not None: | |
| # Replay stored voice prompt embeddings | |
| for next_embed in self.voice_prompt_embeddings: | |
| yield | |
| self.step_embeddings(next_embed) | |
| state = self._streaming_state | |
| state.cache.copy_(self.voice_prompt_cache) | |
| return | |
| elif self.voice_prompt_audio is not None: | |
| saved_embeddings = [] | |
| for voice_prompt_frame_tokens in self._encode_voice_prompt_frames(mimi): | |
| yield | |
| self._step_voice_prompt_frame( | |
| voice_prompt_frame_tokens, | |
| saved_embeddings | |
| ) | |
| # One last checkpoint before any optional save (nice-to-have for async disconnect) | |
| yield | |
| if self.save_voice_prompt_embeddings: | |
| # Offset int(self._streaming_state.offset) is not needed since calling step() for len(voice_prompt_frame_tokens) | |
| # and calling step_embeddings() for len(voice_prompt_embeddings) will increment offset by the same amount | |
| torch.save( | |
| { | |
| "embeddings": torch.stack(saved_embeddings, dim=0).detach().cpu(), | |
| "cache": self._streaming_state.cache | |
| }, | |
| splitext(self.voice_prompt)[0] + ".pt", | |
| ) | |
| print('Done loading voice prompt.') | |
| def _step_voice_prompt(self, mimi): | |
| # Sync path intentionally does not support `is_alive` / disconnect checks. | |
| for _ in self._step_voice_prompt_core(mimi): | |
| pass | |
| async def _step_voice_prompt_async(self, mimi, is_alive: Optional[Callable]=None): | |
| for _ in self._step_voice_prompt_core(mimi): | |
| if is_alive is not None and not await is_alive(): | |
| break | |
| def _step_audio_silence_core(self) -> Iterator[None]: | |
| # For slots of silence (default 0.5s) after voice/text prompts | |
| # (agent text, user audio, agent audio) : (PADs, silence, sine) | |
| for _ in range(self.audio_silence_frame_cnt): | |
| yield | |
| self.step( | |
| moshi_tokens=self._encode_zero_frame(), | |
| text_token=self.zero_text_code, | |
| input_tokens=self._encode_sine_frame(), | |
| ) | |
| print('Done loading audio silence.') | |
| def _step_audio_silence(self): | |
| # Sync path intentionally does not support `is_alive` / disconnect checks. | |
| for _ in self._step_audio_silence_core(): | |
| pass | |
| async def _step_audio_silence_async(self, is_alive: Optional[Callable]=None): | |
| for _ in self._step_audio_silence_core(): | |
| if is_alive is not None and not await is_alive(): | |
| break | |
| def _step_text_prompt_core(self) -> Iterator[None]: | |
| for text_prompt_token in self.text_prompt_tokens: | |
| yield | |
| self.step( | |
| moshi_tokens=self._encode_zero_frame(), | |
| text_token=text_prompt_token, | |
| input_tokens=self._encode_sine_frame(), | |
| ) | |
| print('Done loading text prompt.') | |
| def _step_text_prompt(self): | |
| # Sync path intentionally does not support `is_alive` / disconnect checks. | |
| for _ in self._step_text_prompt_core(): | |
| pass | |
| async def _step_text_prompt_async(self, is_alive: Optional[Callable]=None): | |
| for _ in self._step_text_prompt_core(): | |
| if is_alive is not None and not await is_alive(): | |
| break | |
| async def step_system_prompts_async(self, mimi, is_alive: Optional[Callable]=None): | |
| await self._step_voice_prompt_async(mimi, is_alive) | |
| await self._step_audio_silence_async(is_alive) | |
| await self._step_text_prompt_async(is_alive) | |
| await self._step_audio_silence_async(is_alive) | |
| def step_system_prompts(self, mimi): | |
| self._step_voice_prompt(mimi) | |
| self._step_audio_silence() | |
| self._step_text_prompt() | |
| self._step_audio_silence() | |
| def depformer_step( | |
| self, | |
| text_token: torch.Tensor, | |
| transformer_out: torch.Tensor, | |
| audio_tokens: torch.Tensor, | |
| audio_provided: torch.Tensor | |
| ) -> torch.Tensor: | |
| (B,) = text_token.shape | |
| prev_token = text_token | |
| lm_model = self.lm_model | |
| depformer_tokens: list[torch.Tensor] = [] | |
| depformer_logits: list[torch.Tensor] = [] | |
| assert not lm_model.depformer.is_streaming | |
| with lm_model.depformer.streaming(B): | |
| for cb_index in range(lm_model.dep_q): | |
| input_ = prev_token[:, None, None] | |
| logits = lm_model.forward_depformer(cb_index, input_, transformer_out) | |
| if self.return_logits: | |
| assert logits.shape == (B, 1, 1, lm_model.card), logits.shape | |
| ret_logits = logits.squeeze(dim=1).squeeze(dim=1) | |
| assert ret_logits.shape == (B, lm_model.card), ret_logits.shape | |
| depformer_logits.append(ret_logits.float()) | |
| next_token = sample_token( | |
| logits.float(), | |
| self.use_sampling, | |
| self.temp, | |
| self.top_k, | |
| ) | |
| assert next_token.shape == (B, 1, 1) | |
| next_token = next_token[:, 0, 0] # shape is B | |
| prev_token = torch.where( | |
| audio_provided[:, cb_index], | |
| audio_tokens[:, cb_index], | |
| next_token, | |
| ) | |
| depformer_tokens.append(next_token) | |
| assert len(depformer_tokens) == lm_model.dep_q, ( | |
| len(depformer_tokens), | |
| lm_model.dep_q, | |
| ) | |
| tokens = torch.stack(depformer_tokens, dim=1) | |
| assert tokens.shape == (B, lm_model.dep_q), tokens.shape | |
| if self.return_logits: | |
| all_logits = torch.stack(depformer_logits, dim=1) | |
| assert all_logits.shape == (B, lm_model.dep_q, lm_model.card), all_logits.shape | |
| return tokens, all_logits | |
| else: | |
| return tokens | |