Instructions to use zero0303/maya2-mlx-dynamic-8bit with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- MLX
How to use zero0303/maya2-mlx-dynamic-8bit with MLX:
# Download the model from the Hub pip install huggingface_hub[hf_xet] hf download zero0303/maya2-mlx-dynamic-8bit --local-dir maya2-mlx-dynamic-8bit
- Notebooks
- Google Colab
- Kaggle
- Local Apps Settings
- LM Studio
- Atomic Chat
Download maya2_mlx/engine.py from zero0303/maya2-mlx-dynamic-8bit: direct link, hf CLI and curl.
- Browser
- Download file 16.9 kB
-
https://huggingface.co/zero0303/maya2-mlx-dynamic-8bit/resolve/main/maya2_mlx/engine.py
- Command line
-
hf download hf://zero0303/maya2-mlx-dynamic-8bit/maya2_mlx/engine.py
-
curl -L -o engine.py https://huggingface.co/zero0303/maya2-mlx-dynamic-8bit/resolve/main/maya2_mlx/engine.py
16.9 kB
| """Maya 2 generation for one request at a time: the official engine's per-piece loop (maya3/engine.py) on MLX. | |
| from maya2_mlx import Maya2 | |
| tts = Maya2() # models/maya2-8bit: dynamic 8-bit backbone, fp16 depth decoder, fp32 Mimi | |
| wav = tts.generate("Hello there.", lang="en", voice="Anirban", seed=7) # float32, 24 kHz, in [-1, 1] | |
| Same request semantics as Maya3TTS.generate (api.py:224-230 -> engine.py): split into ~20 s pieces, one fresh | |
| backbone context per piece, controlled stop, duration cap and loop guard, 0.15 s gap between pieces, int16 PCM / 32767. | |
| """ | |
| import math | |
| import os | |
| import random | |
| import time | |
| import mlx.core as mx | |
| import numpy as np | |
| from . import philox | |
| from . import quant as Q | |
| from . import weights as W | |
| from .backbone import Backbone | |
| from .depth import Depth | |
| from .mimi import Mimi | |
| from .prompt import Packer, VoiceRegistry, expected_frames, load_named_voices, load_prior, split_text | |
| PORT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) | |
| SR = 24000 # engine.py:42 | |
| FRAME = 1920 # engine.py:43 | |
| AEOS = 2048 # tts/tokens.py:23 | |
| REASONS = {1: "aeos", 2: "aeos_argmax", 3: "aeos_tau", 4: "aeos_hazard"} # engine.py:46 | |
| # EngineConfig defaults (engine.py:49-79) that the single-request loop reads | |
| CB0_TEMP, CB0_TOP_K, CB0_TOP_P, CB0_REP, CB0_REP_WIN = 0.9, 64, 1.0, 0.8, 12 | |
| DEPTH_TEMP, DEPTH_TOP_K = 0.7, 64 | |
| HARD_CAP, CAPACITY, SEED = 1500, 2048, 20261024 | |
| GAP_S, CHUNK_S = 0.15, 20.0 | |
| WARMUP_PIECES = 2 # Engine.warmup runs two one-piece requests before any user request (engine.py:243-249) | |
| class GenerationError(RuntimeError): | |
| """api.py:31-34""" | |
| def __init__(self, code, msg): | |
| super().__init__(f"{code}: {msg}") | |
| self.code = code | |
| def check_sampling(sp): | |
| """engine.py:110-128""" | |
| if not sp: | |
| return {} | |
| allowed = {"seed", "temperature", "top_k", "top_p", "repetition_penalty"} | |
| bad = set(sp) - allowed | |
| if bad: | |
| raise ValueError(f"unknown sampling keys {sorted(bad)}; allowed {sorted(allowed)}") | |
| out = {k: v for k, v in sp.items() if v is not None} | |
| if "temperature" in out and not 0.05 <= float(out["temperature"]) <= 2.0: | |
| raise ValueError("temperature must be in [0.05, 2.0]") | |
| if "top_k" in out and not 1 <= int(out["top_k"]) <= 2049: | |
| raise ValueError("top_k must be in [1, 2049]") | |
| if "top_p" in out and not 0.0 < float(out["top_p"]) <= 1.0: | |
| raise ValueError("top_p must be in (0, 1]") | |
| if "repetition_penalty" in out and not 0.0 <= float(out["repetition_penalty"]) <= 5.0: | |
| raise ValueError("repetition_penalty must be in [0, 5]") | |
| if "seed" in out: | |
| out["seed"] = int(out["seed"]) | |
| return out | |
| class Maya2: | |
| def __init__(self, models_dir=W.DEFAULT_DIR, strict=False): | |
| """strict=False: production precision (backbone bf16, depth fp16, Mimi fp32 without TF32). | |
| strict=True: fp32 everywhere (the parity gates).""" | |
| from maya3 import guards as G # the official file, unchanged | |
| self.G = G | |
| t0 = time.perf_counter() | |
| meta = W.load_meta(models_dir) | |
| self.meta = meta | |
| self.models_dir = models_dir | |
| self.bdt = mx.float32 if strict else mx.bfloat16 # engine.py:158 (model dtype) | |
| self.ddt = mx.float32 if strict else mx.float16 # engine.py:192, depth_dtype "fp16" | |
| self.backbone = Backbone(meta, self.bdt, models_dir, capacity=CAPACITY) | |
| self.recipe = Q.load_recipe(models_dir) | |
| if strict and self.recipe: | |
| raise ValueError("strict (fp32) runs use the bf16 release export, not quantized weights") | |
| aud = W.load(["audio_in.safetensors"], models_dir) | |
| table = Q.cast(Q.take(aud, "table.weight", self.recipe), self.bdt) | |
| fbw = aud["norm.weight"].astype(self.bdt) | |
| self.depth = Depth(meta, (table, fbw), self.ddt, DEPTH_TEMP, DEPTH_TOP_K, models_dir) | |
| self.depth.set_speaker(None) # named / plain: the null speaker memory | |
| self.mimi = Mimi(meta, models_dir) | |
| self.anchor = self._anchor(W.load(["anchor.safetensors"], models_dir)) | |
| self.packer = Packer(os.path.join(models_dir, "tokenizer.json"), meta["token_ids"]) | |
| self.prior = load_prior(os.path.join(PORT, "configs", "text_frames_prior.json")) | |
| self.dur = G.DurationPrior() | |
| self.gcfg = G.GuardConfig() | |
| self.registry = VoiceRegistry() | |
| load_named_voices(self.registry, os.path.join(models_dir, "voices.json")) | |
| self._seed_rng = random.Random(SEED) # engine.py:224-225 | |
| for _ in range(WARMUP_PIECES): | |
| self._seed_rng.randrange(1, 2 ** 31 - 1) | |
| self.load_s = time.perf_counter() - t0 | |
| self.last_info = None | |
| # ------------------------------------------------------------------ constants | |
| def _anchor(self, w): | |
| """The speaker anchor of a row with no speaker embedding (anchors.py:86-109): fc2(gelu(fc1(null_vec))), | |
| Linear = fp32 accumulation + bias, one rounding; GELU in fp32, one rounding. Model dtype.""" | |
| dt = self.bdt | |
| x = w["null_vec"].astype(dt)[None] | |
| h = (x.astype(mx.float32) @ w["fc1.weight"].astype(dt).astype(mx.float32).T | |
| + w["fc1.bias"].astype(dt).astype(mx.float32)).astype(dt) | |
| hf = h.astype(mx.float32) | |
| h = (hf * 0.5 * (1.0 + mx.erf(hf * math.sqrt(0.5)))).astype(dt) | |
| a = (h.astype(mx.float32) @ w["fc2.weight"].astype(dt).astype(mx.float32).T | |
| + w["fc2.bias"].astype(dt).astype(mx.float32)).astype(dt) | |
| mx.eval(a) | |
| return a[0] | |
| def voices(self): | |
| return self.registry.ids() | |
| # ------------------------------------------------------------------ public | |
| def generate(self, text, lang="en", voice=None, max_frames=None, return_info=False, **kw): | |
| """Synthesise text -> float32 waveform (24 kHz), as Maya3TTS.generate (api.py:224-230). | |
| kw: normalize, seed, temperature, top_k, top_p, repetition_penalty. (The official positional arguments | |
| ref_audio, ref_text, ref_lang and mode belong to cloning, which is not ported; pass the rest by keyword.)""" | |
| info = {} | |
| chunks = list(self.stream(text, lang, voice, max_frames=max_frames, info=info, **kw)) | |
| wav = (np.concatenate(chunks).astype(np.float32) / 32767.0) if chunks else np.zeros(0, np.float32) | |
| return (wav, info) if return_info else wav | |
| def stream(self, text, lang="en", voice=None, max_frames=None, normalize=False, info=None, seed=None, | |
| temperature=None, top_k=None, top_p=None, repetition_penalty=None): | |
| """Yield int16 PCM chunks (24 kHz) as they are generated (api.py:196-222); `info` (a dict) receives the end | |
| payload. Errors while preparing a piece raise GenerationError("invalid_request", ...) as the engine reports | |
| them (engine.py:462-468, api.py:219-220).""" | |
| v = self.registry.get(voice or "plain") # api.py:203 (before the sampling check) | |
| if normalize: # api.py:188-194 | |
| from maya3.text import normalise_text | |
| text = [normalise_text(t, lang) for t in text] if isinstance(text, (list, tuple)) \ | |
| else normalise_text(text, lang) | |
| sampling = check_sampling({"seed": seed, "temperature": temperature, "top_k": top_k, "top_p": top_p, | |
| "repetition_penalty": repetition_penalty}) # engine.py:293 | |
| if not isinstance(text, (list, tuple)): # engine.py:296-299 | |
| if not isinstance(text, str) or not text.strip(): | |
| raise ValueError("text must be a non-empty string") | |
| text = text.strip() | |
| pieces = split_text(text, lang, self.prior, CHUNK_S) if CHUNK_S > 0 else [text] | |
| else: | |
| pieces = list(text) | |
| req = {"frames": 0, "stops": [], "t0": time.perf_counter(), "t_first": None} | |
| k = 0 | |
| while True: | |
| try: | |
| plan = self._prepare(pieces[k], lang, v, max_frames, req) | |
| except Exception as exc: # engine.py:464-468 | |
| raise GenerationError("invalid_request", f"{type(exc).__name__}: {exc}") from exc | |
| for pcm in self._piece(plan, k, sampling, max_frames, req): | |
| yield pcm | |
| k += 1 | |
| if k < len(pieces) and not (max_frames and req["frames"] >= max_frames): # engine.py:641-645 | |
| yield np.zeros(int(GAP_S * SR), np.int16) | |
| else: | |
| break | |
| end = {"reason": req["stops"][-1]["reason"], "frames": req["frames"], "audio_s": req["frames"] * FRAME / SR, | |
| "ttfa_s": (req["t_first"] - req["t0"]) if req["t_first"] else None, | |
| "wall_s": time.perf_counter() - req["t0"], "pieces": len(pieces), "stops": req["stops"]} | |
| if info is not None: | |
| info.update(end) | |
| self.last_info = end | |
| # ------------------------------------------------------------------ one piece | |
| def _plan(self, text, lang, max_frames, req): | |
| """engine.py:394-418 + the capacity rule of engine.py:469-476.""" | |
| g = self.gcfg | |
| ef, er = expected_frames(self.prior, lang, len(text)) | |
| loose = min(HARD_CAP, int(2.0 * (ef + er) + 40)) | |
| cap = loose | |
| if g.enabled: | |
| cap = min(cap, max(int(ef * 0.5) + 8, self.dur.cap_frames(text, lang, g.dur_k, g.dur_margin_s))) | |
| if max_frames: | |
| cap = min(cap, max(1, int(max_frames) - req["frames"])) | |
| minimum = max(4, int(0.5 * ef)) | |
| if g.enabled and g.loop and g.aware_v2: | |
| suspend = self.G.suspend_windows_v2(text, lang, self.dur) | |
| else: | |
| suspend = self.G.tag_windows(text, lang, self.dur, g.tag_hold_s) \ | |
| if (g.enabled and g.loop and g.tag_aware and "[" in text) else () | |
| return cap, loose, minimum, suspend | |
| def _prompt(self, voice, text, lang): | |
| """Token ids and the composed prompt embeddings (s2_serve_controls.py:197-223, tts_model_v2.py:128-170): | |
| text embeddings, plus the null speaker anchor at the <|audio|> sentinel (the last position).""" | |
| ids = self.packer.prompt_ids(voice.render(text), lang) | |
| emb = self.backbone.embed_tokens(ids) | |
| last = emb[-1] + self.anchor # index_put accumulate: one add | |
| emb = mx.concatenate([emb[:-1], last[None]], 0) | |
| return ids, emb | |
| def _cb0(self, logits, hist, hz, can, sp, aeos_bias, noise): | |
| """Engine._cb0 (engine.py:652-694) for one seat. logits fp32 [2049]; hist int [12] (-1 empty); | |
| hz fp32 scalar; can bool. Returns (stop code, cb0, new hz) as mx arrays.""" | |
| V = logits.shape[0] | |
| idx = mx.arange(V) | |
| temp = float(sp.get("temperature") or CB0_TEMP) | |
| top_k = int(sp.get("top_k") or CB0_TOP_K) | |
| top_p = float(sp.get("top_p") or CB0_TOP_P) | |
| rep = float(sp["repetition_penalty"] if sp.get("repetition_penalty") is not None else CB0_REP) | |
| logits = logits / mx.array(temp, mx.float32) | |
| logits = logits + mx.where(idx == AEOS, mx.array(aeos_bias, mx.float32), mx.array(0.0, mx.float32)) | |
| if CB0_REP_WIN > 0: | |
| h = mx.array(hist, mx.int32) | |
| pen = mx.zeros((V,), mx.float32).at[mx.maximum(h, 0)].add((h >= 0).astype(mx.float32)) | |
| pen = mx.where(idx == AEOS, 0.0, pen) | |
| logits = logits - mx.array(rep, mx.float32) * (pen > 0).astype(mx.float32) | |
| srt = mx.sort(logits) # ascending: desc index i = V-1-i | |
| kth = srt[V - 1 - min(max(top_k - 1, 0), V - 1)] | |
| logits = mx.where(logits < kth, -mx.inf, logits) | |
| if top_p < 1.0: | |
| p = mx.softmax(logits, precise=True) | |
| si = mx.argsort(-p) | |
| sp_ = p[si] | |
| drop_sorted = (mx.cumsum(sp_) - sp_) > top_p | |
| drop = mx.zeros((V,), mx.int32).at[si].add(drop_sorted.astype(mx.int32)) > 0 # scatter back by si | |
| logits = mx.where(drop, -mx.inf, logits) | |
| probs = mx.softmax(logits, precise=True) | |
| pa = probs[AEOS] | |
| arg = mx.argmax(probs) | |
| hzv = mx.array(hz, mx.float32) | |
| if can: | |
| hzv = 1 - (1 - hzv) * (1 - pa) | |
| code = mx.array(0) | |
| if can: | |
| code = mx.where(hzv >= 0.6, 4, code) | |
| code = mx.where(pa >= 0.35, 3, code) | |
| code = mx.where(arg == AEOS, 2, code) | |
| q = mx.where(idx == AEOS, 0.0, probs) | |
| q = q / mx.maximum(q.sum(), 1e-12) | |
| cb0 = mx.minimum(mx.argmax(q / noise), AEOS - 1).astype(mx.int32) | |
| return code, cb0, hzv | |
| def _prepare(self, text, lang, voice, max_frames, req): | |
| """_plan (engine.py:400-418: the prompt is built first) and the capacity rule of _admit (:469-476).""" | |
| ids, emb = self._prompt(voice, text, lang) | |
| cap, loose, minimum, suspend = self._plan(text, lang, max_frames, req) | |
| n = len(ids) | |
| if n + cap + 2 > CAPACITY: | |
| cap = CAPACITY - n - 2 | |
| if cap < 8: | |
| raise ValueError(f"prompt of {n} tokens leaves no room in KV capacity {CAPACITY}") | |
| return emb, cap, loose, minimum, suspend | |
| def _piece(self, plan, k, sp, max_frames, req): | |
| g = self.gcfg | |
| emb, cap, loose, minimum, suspend = plan | |
| if "seed" in sp and sp["seed"] is not None: # engine.py:430-437 | |
| seed = (int(sp["seed"]) * 1000003 + k * 7919) % (2 ** 31 - 1) | |
| else: | |
| seed = self._seed_rng.randrange(1, 2 ** 31 - 1) | |
| row_seed = (seed * 69069 + 1) % (2 ** 31 - 1) | |
| self.backbone.reset() | |
| self.mimi.reset() | |
| hist = np.full(CB0_REP_WIN, -1, np.int64) # cb0 presence-penalty window | |
| ghist = np.full(g.hist, -1, np.int64) # loop-guard cb0 tail (_Seat.hist) | |
| hz = np.float32(0.0) | |
| aeos_bias = 0.0 | |
| frames, boost_at, n_boost, ctr = 0, None, 0, 0 | |
| h = self.backbone.forward(emb)[-1] | |
| reason = None | |
| while reason is None: | |
| noise = mx.array(philox.cb0_noise(seed, ctr)) | |
| uni = mx.array(philox.depth_uniform(row_seed, ctr)) | |
| code, cb0, hz_new = self._cb0(self.backbone.cb0_logits(h), hist, hz, frames >= minimum, sp, aeos_bias, | |
| noise) | |
| codes, fb = self.depth.frame(h, cb0, uni) | |
| pcm = self.mimi.decode_frame(codes) | |
| mx.eval(code, cb0, hz_new, codes, fb, pcm) | |
| ctr += 1 | |
| c0 = int(cb0.item()) | |
| hist = np.concatenate([hist[1:], [c0]]) | |
| hz = np.float32(hz_new.item()) | |
| stop = int(code.item()) | |
| if stop: # engine.py:576-578: not emitted | |
| reason = REASONS[stop] | |
| break | |
| frames += 1 | |
| req["frames"] += 1 | |
| if req["t_first"] is None: | |
| req["t_first"] = time.perf_counter() | |
| pcm16 = np.rint(np.clip(np.array(pcm, np.float32), -1, 1) * np.float32(32767)).astype(np.int16) | |
| yield pcm16 | |
| ghist[:-1] = ghist[1:] | |
| ghist[-1] = c0 | |
| if frames >= cap: # engine.py:586-593 | |
| if max_frames and req["frames"] >= max_frames: | |
| reason = "max_frames" | |
| elif g.enabled and cap < loose: | |
| reason = "cap_guard" | |
| else: | |
| reason = "cap" | |
| break | |
| if g.enabled and g.loop and frames >= g.start and frames % g.every == 0 and \ | |
| not any(a <= frames <= b for a, b in suspend): # engine.py:594-617 | |
| hit = self.G.loop_detect_batch(ghist[None], np.array([frames]), g)[0] | |
| if hit is not None and g.mode == "boost": | |
| if boost_at is None: | |
| boost_at = frames | |
| n_boost += 1 | |
| aeos_bias = g.boost | |
| if frames - boost_at >= g.backstop: | |
| reason = "loop_backstop" | |
| break | |
| elif hit is not None: | |
| reason = "loop_" + hit.split(":")[0] | |
| break | |
| elif boost_at is not None: | |
| aeos_bias = 0.0 | |
| boost_at = None | |
| h = self.backbone.forward(fb[None])[0] | |
| # engine.py:639, plus the piece's seed (port addition: the official stream is not reported) | |
| req["stops"].append({"reason": reason, "frames": frames, "cap": cap, "n_boost": n_boost, "seed": seed}) | |