| """Olive PyTorchModel loaders for Qwen3-TTS sub-models (12 Hz). |
| |
| Loaded via the vendored qwen_tts package (codes/) under transformers 4.57.3. |
| optimize.py declares the deps as PEP-723 inline metadata and runs with `uv run`, |
| so the shared 5.10.2 venv is untouched. |
| |
| Sub-models |
| ---------- |
| TTS model (qwen3_tts): |
| talker β Qwen3TTSTalkerModel (28 L, MROPE) + codec_head β first-codebook |
| logits / hidden_states (the LLM sub-part) |
| code_predictor β 5 L, 16 per-group heads β residual code groups |
| speaker_encoder β ECAPA-TDNN speaker embedding (custom-voice / clone) [optional] |
| |
| Tokenizer (qwen3_tts_tokenizer_12hz): |
| tok_encoder β waveform β codes |
| tok_decoder β codes β waveform |
| """ |
| import math |
| import os |
| import sys |
|
|
| import torch |
| import torch.nn as nn |
|
|
|
|
| def _patch_mimi_static_padding(): |
| """Make Mimi conv 'same-length' padding pure-Python so torch.export sees |
| concrete conv output lengths. |
| |
| Stock `MimiConv1d._get_extra_padding_for_conv1d` computes the extra padding as |
| a 0-dim *tensor* (torch.ceil(...).to(int64)). Under torch.export each value is |
| realized via `.item()` β an unbacked symint (u0β¦u29). Those flow into the frame |
| count, so the RVQ `torch.cdist` later branches on an unbacked `frames > 25` |
| (its matmul heuristic) and export aborts (GuardOnDataDependentSymNode). |
| |
| With a static input length the padding is a constant, so compute it in Python |
| ints; conv output lengths then become concrete and the cdist guard resolves. |
| Idempotent. |
| """ |
| from transformers.models.mimi import modeling_mimi as mm |
| if getattr(mm.MimiConv1d, "_static_pad_patched", False): |
| return |
|
|
| def _get_extra_padding_for_conv1d(self, hidden_states): |
| |
| length = int(hidden_states.shape[-1]) |
| kernel = int(self.kernel_size) |
| stride = int(self.stride) |
| pad_total = int(self.padding_total) |
| n_frames = (length - kernel + pad_total) / stride + 1 |
| n_frames = math.ceil(n_frames) - 1 |
| ideal_length = n_frames * stride + kernel - pad_total |
| return ideal_length - length |
|
|
| mm.MimiConv1d._get_extra_padding_for_conv1d = _get_extra_padding_for_conv1d |
| mm.MimiConv1d._static_pad_patched = True |
|
|
|
|
| def _intify_mimi_convs(model): |
| """Replace MimiConv1d's tensor buffers (stride/kernel_size/padding_total) and |
| the derived padding_left/right with plain Python ints. |
| |
| In this model these are registered as 0-dim int64 buffers. Reading them inside |
| forward (even via int()) becomes a tensor `.item()` under torch.export β an |
| unbacked symint. Converting them to real ints up front keeps the conv padding |
| math fully static. |
| """ |
| from transformers.models.mimi import modeling_mimi as mm |
| for m in model.modules(): |
| if isinstance(m, mm.MimiConv1d): |
| for name in ("stride", "kernel_size", "padding_total"): |
| val = int(getattr(m, name)) |
| if name in m._buffers: |
| del m._buffers[name] |
| setattr(m, name, val) |
| m.padding_right = int(m.padding_total) // 2 |
| m.padding_left = int(m.padding_total) - m.padding_right |
| return model |
|
|
| _HERE = os.path.dirname(os.path.abspath(__file__)) |
| if os.path.join(_HERE, "codes") not in sys.path: |
| sys.path.insert(0, os.path.join(_HERE, "codes")) |
|
|
|
|
| |
|
|
| |
| |
| |
| |
| _DIMS = {"hidden": 2048, "n_layers": 28, "n_kv": 8, "head_dim": 128, "n_groups": 16} |
|
|
|
|
| def _load_tts(model_path: str): |
| from qwen_tts.core.models.modeling_qwen3_tts import Qwen3TTSForConditionalGeneration |
| model = Qwen3TTSForConditionalGeneration.from_pretrained( |
| model_path, dtype=torch.float32, attn_implementation="eager") |
| model.eval() |
| tc = model.config.talker_config |
| _DIMS.update( |
| hidden=tc.hidden_size, |
| n_layers=tc.num_hidden_layers, |
| n_kv=getattr(tc, "num_key_value_heads", tc.num_attention_heads), |
| head_dim=getattr(tc, "head_dim", tc.hidden_size // tc.num_attention_heads), |
| n_groups=tc.num_code_groups, |
| ) |
| return model |
|
|
|
|
| def _load_tokenizer(tok_path: str): |
| from qwen_tts.core.tokenizer_12hz.modeling_qwen3_tts_tokenizer_v2 import ( |
| Qwen3TTSTokenizerV2Model) |
| tok = Qwen3TTSTokenizerV2Model.from_pretrained( |
| tok_path, dtype=torch.float32, attn_implementation="eager") |
| tok.eval() |
| return tok |
|
|
|
|
| def _tts_dims(model): |
| tc = model.config.talker_config |
| return dict(hidden=tc.hidden_size, text_vocab=tc.text_vocab_size, |
| codec_vocab=tc.vocab_size, n_groups=tc.num_code_groups, |
| cp_hidden=tc.code_predictor_config.hidden_size, |
| cp_vocab=tc.code_predictor_config.vocab_size) |
|
|
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| class TextEmbedWrapper(nn.Module): |
| """text_ids [B,T] β text_projection(text_embedding(ids)) [B,T,hidden]. |
| |
| Same call as generate(): talker.text_projection(talker.get_text_embeddings()(ids)). |
| Covers tts_bos/eos/pad embeds too β those are just specific text ids. |
| """ |
| def __init__(self, talker): |
| super().__init__() |
| self.text_embedding = talker.get_text_embeddings() |
| self.text_projection = talker.text_projection |
|
|
| def forward(self, text_ids): |
| return self.text_projection(self.text_embedding(text_ids)) |
|
|
|
|
| class CodecEmbedWrapper(nn.Module): |
| """codec_ids [B,T] β codec_embedding(ids) [B,T,hidden] (first-codebook embed).""" |
| def __init__(self, talker): |
| super().__init__() |
| self.codec_embedding = talker.model.codec_embedding |
|
|
| def forward(self, codec_ids): |
| return self.codec_embedding(codec_ids) |
|
|
|
|
| def get_text_embed_model(model_path=None): |
| return TextEmbedWrapper(_load_tts(model_path).talker).eval() |
|
|
|
|
| def get_text_embed_io_config(model=None): |
| return { |
| "input_names": ["text_ids"], "output_names": ["text_embeds"], |
| "input_shapes": [[1, 16]], "input_types": ["int64"], |
| "dynamic_axes": {"text_ids": {0: "batch", 1: "seq"}, |
| "text_embeds": {0: "batch", 1: "seq"}}, |
| } |
|
|
|
|
| def get_text_embed_dummy_inputs(model=None): |
| return {"text_ids": torch.randint(0, 1000, (1, 16), dtype=torch.int64)} |
|
|
|
|
| def get_codec_embed_model(model_path=None): |
| return CodecEmbedWrapper(_load_tts(model_path).talker).eval() |
|
|
|
|
| def get_codec_embed_io_config(model=None): |
| return { |
| "input_names": ["codec_ids"], "output_names": ["codec_embeds"], |
| "input_shapes": [[1, 16]], "input_types": ["int64"], |
| "dynamic_axes": {"codec_ids": {0: "batch", 1: "seq"}, |
| "codec_embeds": {0: "batch", 1: "seq"}}, |
| } |
|
|
|
|
| def get_codec_embed_dummy_inputs(model=None): |
| return {"codec_ids": torch.randint(0, 2048, (1, 16), dtype=torch.int64)} |
|
|
|
|
| |
| |
| |
|
|
| class TalkerWrapper(nn.Module): |
| """inputs_embeds [B,T,H] + position_ids [3,B,T] (MROPE) β |
| (codec logits [B,T,V], last_hidden_state [B,T,H]). |
| |
| Both outputs are needed for generation: the logits give the first-codebook |
| token, and last_hidden_state is the `past_hidden` that conditions the |
| code_predictor for the residual codes (see modeling forward, line ~1670). |
| use_cache=False (no-cache forward β the AR loop re-runs the full prefix). |
| """ |
| def __init__(self, talker): |
| super().__init__() |
| self.model = talker.model |
| self.codec_head = talker.codec_head |
|
|
| def forward(self, inputs_embeds, position_ids, attention_mask): |
| out = self.model(inputs_embeds=inputs_embeds, position_ids=position_ids, |
| attention_mask=attention_mask, use_cache=False, |
| return_dict=True) |
| hidden = out.last_hidden_state |
| return self.codec_head(hidden), hidden |
|
|
|
|
| def get_talker_model(model_path=None): |
| return TalkerWrapper(_load_tts(model_path).talker).eval() |
|
|
|
|
| def get_talker_io_config(model=None): |
| return { |
| "input_names": ["inputs_embeds", "position_ids", "attention_mask"], |
| "output_names": ["logits", "hidden_states"], |
| "input_shapes": [[1, 32, _DIMS["hidden"]], [3, 1, 32], [1, 32]], |
| "input_types": ["float32", "int64", "int64"], |
| "dynamic_axes": { |
| "inputs_embeds": {0: "batch", 1: "seq"}, |
| "position_ids": {1: "batch", 2: "seq"}, |
| "attention_mask": {0: "batch", 1: "seq"}, |
| "logits": {0: "batch", 1: "seq"}, |
| "hidden_states": {0: "batch", 1: "seq"}, |
| }, |
| |
| |
| |
| "dynamic_shapes": { |
| "inputs_embeds": {0: "batch", 1: "seq"}, |
| "position_ids": {1: "batch", 2: "seq"}, |
| "attention_mask": {0: "batch", 1: "seq"}, |
| }, |
| } |
|
|
|
|
| def get_talker_dummy_inputs(model=None): |
| return { |
| "inputs_embeds": torch.randn(1, 32, _DIMS["hidden"], dtype=torch.float32), |
| "position_ids": torch.zeros(3, 1, 32, dtype=torch.long), |
| "attention_mask": torch.ones(1, 32, dtype=torch.long), |
| } |
|
|
|
|
| |
| |
| |
|
|
| class CodePredictorWrapper(nn.Module): |
| """Teacher-forced residual code predictor (parity-faithful). |
| |
| Mirrors `Qwen3TTSTalkerForConditionalGeneration.forward_sub_talker_finetune` |
| + `code_predictor.forward_finetune`: |
| seq = [ talker_hidden, |
| talker.codec_embedding(code0), |
| cp.codec_embedding[i-1](code_i) for i in 1..14 ] # [B,16,2048] |
| β small_to_mtp_projection β [B,16,1024] β predictor decoder |
| β lm_head[i-1](h[:, i]) for i in 1..15 β logits [B,15,vocab]. |
| |
| All embedding/projection stages are IN-GRAPH (no Python-side model math). |
| Needs all 16 codes up front, so this is for parity, not generation. |
| """ |
| def __init__(self, talker): |
| super().__init__() |
| self.talker_codec_embedding = talker.model.codec_embedding |
| self.cp = talker.code_predictor |
| self.n_groups = self.cp.config.num_code_groups |
|
|
| def forward(self, talker_hidden, codec_ids): |
| parts = [talker_hidden.unsqueeze(1), |
| self.talker_codec_embedding(codec_ids[:, :1])] |
| for i in range(1, self.n_groups - 1): |
| parts.append(self.cp.model.codec_embedding[i - 1](codec_ids[:, i:i + 1])) |
| emb = torch.cat(parts, dim=1) |
| emb = self.cp.small_to_mtp_projection(emb) |
| out = self.cp.model(inputs_embeds=emb, use_cache=False, return_dict=True) |
| h = out.last_hidden_state |
| logits = [self.cp.lm_head[i - 1](h[:, i]) for i in range(1, self.n_groups)] |
| return torch.stack(logits, dim=1) |
|
|
|
|
| def get_code_predictor_model(model_path=None): |
| return CodePredictorWrapper(_load_tts(model_path).talker).eval() |
|
|
|
|
| def get_code_predictor_io_config(model=None): |
| return { |
| "input_names": ["talker_hidden", "codec_ids"], |
| "output_names": ["group_logits"], |
| "input_shapes": [[1, _DIMS["hidden"]], [1, _DIMS["n_groups"]]], |
| "input_types": ["float32", "int64"], |
| "dynamic_axes": {"talker_hidden": {0: "batch"}, |
| "codec_ids": {0: "batch"}, |
| "group_logits": {0: "batch"}}, |
| } |
|
|
|
|
| def get_code_predictor_dummy_inputs(model=None): |
| return {"talker_hidden": torch.randn(1, _DIMS["hidden"], dtype=torch.float32), |
| "codec_ids": torch.randint(0, 2048, (1, _DIMS["n_groups"]), dtype=torch.int64)} |
|
|
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| class ResidualEmbedWrapper(nn.Module): |
| """codec_ids [B,16] β codec_hiddens.sum(1) [B,hidden] (next talker input).""" |
| def __init__(self, talker): |
| super().__init__() |
| self.talker_codec_embedding = talker.model.codec_embedding |
| self.cp_codec_embedding = talker.code_predictor.model.codec_embedding |
| self.n_groups = talker.code_predictor.config.num_code_groups |
|
|
| def forward(self, codec_ids): |
| acc = self.talker_codec_embedding(codec_ids[:, 0]) |
| for i in range(self.n_groups - 1): |
| acc = acc + self.cp_codec_embedding[i](codec_ids[:, i + 1]) |
| return acc |
|
|
|
|
| def get_residual_embed_model(model_path=None): |
| return ResidualEmbedWrapper(_load_tts(model_path).talker).eval() |
|
|
|
|
| def get_residual_embed_io_config(model=None): |
| return { |
| "input_names": ["codec_ids"], "output_names": ["step_embed"], |
| "input_shapes": [[1, _DIMS["n_groups"]]], "input_types": ["int64"], |
| "dynamic_axes": {"codec_ids": {0: "batch"}, "step_embed": {0: "batch"}}, |
| } |
|
|
|
|
| def get_residual_embed_dummy_inputs(model=None): |
| return {"codec_ids": torch.randint(0, 2048, (1, _DIMS["n_groups"]), dtype=torch.int64)} |
|
|
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| class TalkerCacheWrapper(nn.Module): |
| def __init__(self, talker): |
| super().__init__() |
| self.model = talker.model |
| self.codec_head = talker.codec_head |
|
|
| def forward(self, inputs_embeds, position_ids, attention_mask, past_kv): |
| |
| |
| |
| from transformers import DynamicCache |
| legacy = tuple((past_kv[i][0], past_kv[i][1]) for i in range(len(past_kv))) |
| dc = DynamicCache.from_legacy_cache(legacy) |
| out = self.model(inputs_embeds=inputs_embeds, position_ids=position_ids, |
| attention_mask=attention_mask, past_key_values=dc, |
| use_cache=True, return_dict=True) |
| hidden = out.last_hidden_state |
| present = out.past_key_values.to_legacy_cache() |
| return self.codec_head(hidden), hidden, present |
|
|
|
|
| def _talker_cache_dims(model): |
| tc = model.config.talker_config |
| n_kv = getattr(tc, "num_key_value_heads", tc.num_attention_heads) |
| hd = getattr(tc, "head_dim", tc.hidden_size // tc.num_attention_heads) |
| return tc.num_hidden_layers, n_kv, hd |
|
|
|
|
| def get_talker_cache_model(model_path=None): |
| return TalkerCacheWrapper(_load_tts(model_path).talker).eval() |
|
|
|
|
| def get_talker_cache_io_config(model=None): |
| return { |
| "input_names": ["inputs_embeds", "position_ids", "attention_mask", "past_kv"], |
| "output_names": ["logits", "hidden_states", "present"], |
| "dynamic_axes": { |
| "inputs_embeds": {0: "batch", 1: "cur"}, |
| "position_ids": {1: "batch", 2: "cur"}, |
| "attention_mask": {0: "batch", 1: "total"}, |
| }, |
| "dynamic_shapes": _talker_cache_dynamic_shapes(), |
| } |
|
|
|
|
| def _talker_cache_dynamic_shapes(n_layers=None): |
| |
| |
| n_layers = n_layers or _DIMS["n_layers"] |
| return { |
| "inputs_embeds": {0: "batch", 1: "cur"}, |
| "position_ids": {1: "batch", 2: "cur"}, |
| "attention_mask": {0: "batch", 1: "total"}, |
| "past_kv": [[{2: "past"}, {2: "past"}] for _ in range(n_layers)], |
| } |
|
|
|
|
| def get_talker_cache_dummy_inputs(model_path=None): |
| L, n_kv, hd, H = _DIMS["n_layers"], _DIMS["n_kv"], _DIMS["head_dim"], _DIMS["hidden"] |
| cur, past = 8, 4 |
| past_kv = [[torch.randn(1, n_kv, past, hd, dtype=torch.float32), |
| torch.randn(1, n_kv, past, hd, dtype=torch.float32)] for _ in range(L)] |
| return { |
| "inputs_embeds": torch.randn(1, cur, H, dtype=torch.float32), |
| "position_ids": torch.arange(past, past + cur).view(1, 1, -1).expand(3, 1, -1).contiguous(), |
| "attention_mask": torch.ones(1, past + cur, dtype=torch.long), |
| "past_kv": past_kv, |
| } |
|
|
|
|
| |
| |
| |
|
|
| class TokEncoderWrapper(nn.Module): |
| """audio [B,1,T] β codes [B, T_frames, 16]. |
| |
| Mirrors MimiModel._encode_frame on tok.encoder (a MimiModel), bypassing the |
| high-level model.encode() β whose streaming/padding bookkeeping (padding_cache, |
| get_audio_codes_mask) uses data-dependent ops that torch.export can't trace and |
| which crash inside Mimi.encode. Explicit attention_mask avoids create_causal_mask's |
| untraceable path; export with the dynamo exporter + static input length. |
| """ |
| def __init__(self, tok): |
| super().__init__() |
| self.enc = tok.encoder |
| self.valid = tok.encoder_valid_num_quantizers |
|
|
| def forward(self, audio): |
| emb = self.enc.encoder(audio) |
| hidden = emb.transpose(1, 2) |
| attn = torch.ones(hidden.shape[0], hidden.shape[1], dtype=torch.long) |
| out = self.enc.encoder_transformer( |
| hidden, attention_mask=attn, use_cache=False, return_dict=True) |
| emb = out[0].transpose(1, 2) |
| emb = self.enc.downsample(emb) |
| codes = self.enc.quantizer.encode(emb) |
| codes = codes.transpose(0, 1)[:, :self.valid] |
| return codes.transpose(1, 2) |
|
|
|
|
| class TokDecoderWrapper(nn.Module): |
| def __init__(self, tok): |
| super().__init__() |
| self.tok = tok |
|
|
| def forward(self, audio_codes): |
| |
| |
| codes = torch.clamp(audio_codes, min=0).transpose(1, 2) |
| wav = self.tok.decoder(codes) |
| return wav |
|
|
|
|
| def get_tok_encoder_model(model_path=None): |
| _patch_mimi_static_padding() |
| tok = _intify_mimi_convs(_load_tokenizer(model_path)) |
| return TokEncoderWrapper(tok).eval() |
|
|
|
|
| def get_tok_encoder_io_config(model=None): |
| return { |
| "input_names": ["audio"], |
| "output_names": ["audio_codes"], |
| |
| |
| "input_shapes": [[1, 1, 24000]], |
| "input_types": ["float32"], |
| "dynamic_axes": {"audio": {0: "batch"}, |
| "audio_codes": {0: "batch"}}, |
| } |
|
|
|
|
| def get_tok_encoder_dummy_inputs(model=None): |
| return {"audio": torch.randn(1, 1, 24000, dtype=torch.float32)} |
|
|
|
|
| def get_tok_decoder_model(model_path=None): |
| return TokDecoderWrapper(_load_tokenizer(model_path)).eval() |
|
|
|
|
| def get_tok_decoder_io_config(model=None): |
| |
| return { |
| "input_names": ["audio_codes"], |
| "output_names": ["waveform"], |
| "input_shapes": [[1, 25, 16]], |
| "input_types": ["int64"], |
| "dynamic_axes": {"audio_codes": {0: "batch", 1: "frames"}, |
| "waveform": {0: "batch", 2: "samples"}}, |
| } |
|
|
|
|
| def get_tok_decoder_dummy_inputs(model=None): |
| return {"audio_codes": torch.randint(0, 2048, (1, 25, 16), dtype=torch.int64)} |
|
|
|
|
| |
| |
| |
| |
| |
| |
|
|
| class SpeakerEncoderWrapper(nn.Module): |
| """audio [B, T] (mono, 24 kHz float32) β speaker x-vector [B, D]. |
| |
| Reimplements `mel_spectrogram` (modeling L399) inline WITHOUT its data-dependent |
| debug branches (`if torch.min(y) < -1.0: print(...)`) which break torch.export |
| (GuardOnDataDependentSymNode). Numerically identical: same librosa mel filterbank, |
| reflect pad, hann STFT, magnitude, log compression (clamp 1e-5). |
| """ |
| _NFFT, _HOP, _WIN, _MELS, _FMIN, _FMAX, _SR = 1024, 256, 1024, 128, 0, 12000, 24000 |
|
|
| def __init__(self, model): |
| super().__init__() |
| if getattr(model, "speaker_encoder", None) is None: |
| raise ValueError("model has no speaker_encoder (not a `base` checkpoint)") |
| self.speaker_encoder = model.speaker_encoder |
| from librosa.filters import mel as librosa_mel_fn |
| mb = librosa_mel_fn(sr=self._SR, n_fft=self._NFFT, n_mels=self._MELS, |
| fmin=self._FMIN, fmax=self._FMAX) |
| self.register_buffer("mel_basis", torch.from_numpy(mb).float(), persistent=False) |
| self.register_buffer("hann", torch.hann_window(self._WIN), persistent=False) |
|
|
| def forward(self, audio): |
| pad = (self._NFFT - self._HOP) // 2 |
| y = torch.nn.functional.pad(audio.unsqueeze(1), (pad, pad), mode="reflect").squeeze(1) |
| spec = torch.stft(y, self._NFFT, hop_length=self._HOP, win_length=self._WIN, |
| window=self.hann, center=False, pad_mode="reflect", |
| normalized=False, onesided=True, return_complex=True) |
| spec = torch.sqrt(torch.view_as_real(spec).pow(2).sum(-1) + 1e-9) |
| mel = torch.matmul(self.mel_basis, spec) |
| mel = torch.log(torch.clamp(mel, min=1e-5)) |
| mels = mel.transpose(1, 2) |
| return self.speaker_encoder(mels)[0] |
|
|
|
|
| def get_speaker_encoder_model(model_path=None): |
| return SpeakerEncoderWrapper(_load_tts(model_path)).eval() |
|
|
|
|
| def get_speaker_encoder_io_config(model=None): |
| return { |
| "input_names": ["audio"], "output_names": ["speaker_embedding"], |
| "input_shapes": [[1, 144000]], "input_types": ["float32"], |
| "dynamic_axes": {"audio": {0: "batch", 1: "samples"}, |
| "speaker_embedding": {0: "batch"}}, |
| "dynamic_shapes": {"audio": {0: "batch", 1: "samples"}}, |
| } |
|
|
|
|
| def get_speaker_encoder_dummy_inputs(model=None): |
| return {"audio": torch.randn(1, 144000, dtype=torch.float32)} |
|
|