Text-to-Speech
Transformers
Safetensors
Kabyle
agbalu_speech
feature-extraction
kabyle
taqbaylit
berber
amazigh
speech-synthesis
voice-cloning
cross-lingual-voice-cloning
expressive-speech
low-resource
custom_code
Instructions to use agbalu/Matoub-4B with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use agbalu/Matoub-4B with Transformers:
# Use a pipeline as a high-level helper from transformers import pipeline pipe = pipeline("text-to-speech", model="agbalu/Matoub-4B", trust_remote_code=True)# pip install -U transformers accelerate # Load model directly from transformers import AutoModel model = AutoModel.from_pretrained("agbalu/Matoub-4B", trust_remote_code=True, device_map="auto") - Notebooks
- Google Colab
- Kaggle
Download inference.py from agbalu/Matoub-4B: direct link, hf CLI and curl.
- Browser
- Download file 9.29 kB
-
https://huggingface.co/agbalu/Matoub-4B/resolve/main/inference.py
- Command line
-
hf download hf://agbalu/Matoub-4B/inference.py
-
curl -L -o inference.py https://huggingface.co/agbalu/Matoub-4B/resolve/main/inference.py
9.29 kB
| """Generate native delayed codec streams with isolated request state.""" | |
| from __future__ import annotations | |
| import time | |
| from dataclasses import dataclass | |
| import torch | |
| from pydantic import Field | |
| from torch import Tensor | |
| from transformers.cache_utils import Cache | |
| from transformers.modeling_outputs import BaseModelOutputWithPast | |
| from .backbone import Generator | |
| from .configuration import Specification | |
| from .layout import BOC, CODEBOOKS, EOC, SequenceLayout, reverse_delay | |
| MAX_CONTEXT_POSITIONS = 8192 | |
| COLLAPSE_FRAMES = 25 | |
| class GenerationError(RuntimeError): | |
| def __init__(self, message: str, trace: dict[str, object]) -> None: | |
| super().__init__(message) | |
| self.trace = trace | |
| class LoopState: | |
| is_recovering: bool = False | |
| class Sampling(Specification): | |
| temperature: float = Field(default=0.8, ge=0) | |
| top_p: float = Field(default=0.8, gt=0, le=1) | |
| top_k: int = Field(default=30, ge=0) | |
| max_new_positions: int = Field(default=1500, ge=8) | |
| seed: int = Field(default=20261102, ge=0) | |
| class Decoding(Specification): | |
| alignment_strength: float = Field(default=0, ge=0, le=3) | |
| plausible_tokens: int = Field(default=25, ge=1, le=1026) | |
| recover_loops: bool = False | |
| def alignment_logits(expert: Tensor, amateur: Tensor, settings: Decoding) -> Tensor: | |
| if expert.shape != amateur.shape: | |
| message = f'ECCD logit shapes differ: {tuple(expert.shape)}, {tuple(amateur.shape)}' | |
| raise ValueError(message) | |
| if settings.alignment_strength == 0: | |
| return expert | |
| positive = expert.float().log_softmax(-1) | |
| negative = amateur.float().log_softmax(-1) | |
| indices = positive.topk(min(settings.plausible_tokens, positive.shape[-1]), dim=-1).indices | |
| head = torch.zeros_like(positive, dtype=torch.bool).scatter_(-1, indices, True) | |
| compatibility = negative.exp().masked_fill(~head, 0).sum(-1, keepdim=True) | |
| enhancement = (positive - negative).clamp_min(0).masked_fill(~head, 0) | |
| return positive + settings.alignment_strength * (1 - compatibility) * enhancement | |
| def recover_repetition(logits: Tensor, row: Tensor, history: Tensor, generator: torch.Generator, state: LoopState | None=None) -> tuple[Tensor, bool]: | |
| collapsed = history.numel() >= COLLAPSE_FRAMES and bool(history[-COLLAPSE_FRAMES:].eq(row[0]).all()) | |
| if state is not None: | |
| state.is_recovering |= collapsed | |
| collapsed = state.is_recovering and bool(history[-10:].eq(row[0]).any()) | |
| if not collapsed: | |
| state.is_recovering = False | |
| if not collapsed or int(row[0]) >= BOC: | |
| return (row, False) | |
| alternative = logits[0].float().clone() | |
| alternative[row[0]] = float('-inf') | |
| result = row.clone() | |
| result[0] = torch.multinomial(alternative.softmax(-1), 1, generator=generator)[0] | |
| return (result, True) | |
| class SamplerState: | |
| delay_count: int = 0 | |
| eoc_countdown: int | None = None | |
| is_done: bool = False | |
| def sample_row(logits: Tensor, settings: Sampling, generator: torch.Generator) -> Tensor: | |
| if settings.temperature == 0: | |
| return logits.argmax(dim=-1) | |
| scaled = logits.float() / settings.temperature | |
| if settings.top_k: | |
| threshold = scaled.topk(min(settings.top_k, scaled.shape[-1]), dim=-1).values[:, -1:] | |
| scaled = scaled.masked_fill(scaled < threshold, float('-inf')) | |
| ordered, indices = scaled.sort(descending=True, dim=-1) | |
| remove = ordered.softmax(dim=-1).cumsum(dim=-1) > settings.top_p | |
| remove[:, 1:] = remove[:, :-1].clone() | |
| remove[:, 0] = False | |
| mask = torch.zeros_like(remove).scatter(-1, indices, remove) | |
| probabilities = scaled.masked_fill(mask, float('-inf')).softmax(dim=-1) | |
| return torch.multinomial(probabilities, 1, generator=generator).squeeze(-1) | |
| def advance_sampler(codes: Tensor, state: SamplerState) -> Tensor: | |
| result = codes.clone() | |
| if state.delay_count < CODEBOOKS: | |
| state.delay_count += 1 | |
| result[state.delay_count:] = BOC | |
| elif state.eoc_countdown is not None: | |
| state.eoc_countdown -= 1 | |
| state.is_done = state.eoc_countdown <= 0 | |
| elif int(result[0].item()) == EOC: | |
| state.eoc_countdown = CODEBOOKS - 2 | |
| return result | |
| def unpack_output(output: object) -> tuple[Tensor, Cache]: | |
| if not isinstance(output, BaseModelOutputWithPast): | |
| message = f'invalid decoder output: {type(output).__name__}' | |
| raise TypeError(message) | |
| hidden, cache = (output.last_hidden_state, output.past_key_values) | |
| if not isinstance(hidden, Tensor) or not isinstance(cache, Cache): | |
| message = 'cached generation requires hidden states and an explicit KV cache' | |
| raise TypeError(message) | |
| return (hidden[:, -1], cache) | |
| def generate_codes(model: Generator, prompt: SequenceLayout, settings: Sampling, decoding: Decoding | None=None, contrast_prompt: SequenceLayout | None=None, *, deadline_monotonic: float | None=None) -> tuple[Tensor, dict[str, object]]: | |
| decoding = decoding or Decoding() | |
| if prompt.positions + settings.max_new_positions > MAX_CONTEXT_POSITIONS: | |
| message = f'generation context exceeds {MAX_CONTEXT_POSITIONS}: prompt={prompt.positions}' | |
| message += f', budget={settings.max_new_positions}' | |
| raise ValueError(message) | |
| device = model.audio_embedding.weight.device | |
| if decoding.alignment_strength and contrast_prompt is None: | |
| message = 'ECCD requires a text-ablated contrast prompt' | |
| raise ValueError(message) | |
| previous_mode = model.training | |
| model.train(False) | |
| generator = torch.Generator(device=device).manual_seed(settings.seed) | |
| try: | |
| with torch.autocast(device.type, dtype=torch.bfloat16, enabled=device.type == 'cuda'): | |
| return decode_stream(model, prompt, settings, decoding, contrast_prompt, generator, deadline_monotonic) | |
| finally: | |
| model.train(previous_mode) | |
| def prefill(model: Generator, prompt: SequenceLayout) -> tuple[Tensor, Cache]: | |
| device = model.audio_embedding.weight.device | |
| ids = prompt.text_ids.to(device).unsqueeze(0) | |
| codes = prompt.audio_codes.to(device).unsqueeze(0) | |
| mask = prompt.audio_mask.to(device).reshape(1, -1, 1) | |
| text: Tensor = model.body.embed_tokens(ids) | |
| embeddings = torch.where(mask, model.embed_audio(codes), text) | |
| return unpack_output(model.body(inputs_embeds=embeddings, use_cache=True)) | |
| def decode_stream(model: Generator, prompt: SequenceLayout, settings: Sampling, decoding: Decoding, contrast_prompt: SequenceLayout | None, generator: torch.Generator, deadline_monotonic: float | None) -> tuple[Tensor, dict[str, object]]: | |
| device = model.audio_embedding.weight.device | |
| hidden, cache = prefill(model, prompt) | |
| contrast = prefill(model, contrast_prompt) if decoding.alignment_strength and contrast_prompt else None | |
| state = SamplerState() | |
| rows = torch.empty((settings.max_new_positions, CODEBOOKS), device=device, dtype=torch.long) | |
| replacements = 0 | |
| loop_state = LoopState() | |
| count = 0 | |
| for position in range(settings.max_new_positions): | |
| if deadline_monotonic is not None and time.monotonic() >= deadline_monotonic: | |
| message = f'speech deadline exceeded at generated position={position}' | |
| raise TimeoutError(message) | |
| logits = model.classify(hidden)[0] | |
| if contrast is not None: | |
| guided = alignment_logits(logits[:1], model.classify(contrast[0])[0, :1], decoding) | |
| logits = torch.cat((guided, logits[1:]), dim=0) | |
| sampled = sample_row(logits, settings, generator) | |
| if decoding.recover_loops: | |
| sampled, recovered = recover_repetition(logits, sampled, rows[:position, 0], generator, loop_state) | |
| replacements += int(recovered) | |
| row = advance_sampler(sampled, state) | |
| rows[position] = row | |
| count = position + 1 | |
| if state.is_done: | |
| break | |
| embedded = model.embed_audio(row.reshape(1, 1, -1)) | |
| hidden, cache = unpack_output(model.body(inputs_embeds=embedded, past_key_values=cache, use_cache=True)) | |
| if contrast is not None: | |
| contrast = unpack_output(model.body(inputs_embeds=embedded, past_key_values=contrast[1], use_cache=True)) | |
| if not state.is_done: | |
| message = f'generation hit hard cap={settings.max_new_positions} without native termination' | |
| raise GenerationError(message, {'terminated': False, 'trace': rows[:count].cpu().tolist(), 'generated_positions': count, 'sampling': settings.model_dump(), 'decoding': decoding.model_dump(), 'repetition_resamples': replacements}) | |
| delayed = rows[:count].cpu() | |
| first_eoc = delayed[:, 0].eq(EOC).nonzero() | |
| if first_eoc.numel() == 0: | |
| message = 'native termination has no first-stream EOC' | |
| raise RuntimeError(message) | |
| frames = int(first_eoc[0, 0].item()) | |
| needed = frames + CODEBOOKS - 1 | |
| if delayed.shape[0] < needed: | |
| message = f'incomplete delayed wind-down: rows={delayed.shape[0]}, needed={needed}' | |
| raise RuntimeError(message) | |
| result = reverse_delay(delayed[:needed], frames) | |
| return (result, {'terminated': True, 'frames': frames, 'trace': delayed.tolist(), 'sampling': settings.model_dump(), 'decoding': decoding.model_dump(), 'repetition_resamples': replacements, 'generated_positions': count}) | |