MiniMax-Music3-MLX / mlx_bridge.py
elishabjm's picture
MLX 4-bit port of the MiniMax-Music3 language model
7c52781 verified
Raw
History Blame Contribute Delete
3.56 kB
"""Pont MLX pour le language_model de MiniMax-Music3.
Le pipeline diffusers n'utilise du Qwen3ForCausalLM que trois points de contact :
language_model.model.embed_tokens(ids)
language_model.model(inputs_embeds=..., past_key_values=..., use_cache=True)
language_model.lm_head(hidden)
Cette classe les reimplemente sur un modele MLX quantifie. Le reste du pipeline
(transformer flow-matching, RVQ depth decoder, vocoder) reste en torch/MPS.
Les tenseurs echanges par pas de decodage sont minuscules, la conversion via numpy
ne pese rien face au forward du modele.
"""
import time
from collections import defaultdict
from pathlib import Path
from types import SimpleNamespace
import mlx.core as mx
import numpy as np
import torch
from mlx_lm.models.cache import make_prompt_cache
from mlx_lm.utils import load_model
STATS = defaultdict(float)
COUNTS = defaultdict(int)
class timed:
def __init__(self, key):
self.key = key
def __enter__(self):
self.t = time.perf_counter()
def __exit__(self, *exc):
STATS[self.key] += time.perf_counter() - self.t
COUNTS[self.key] += 1
def report():
if not STATS:
return
width = max(len(k) for k in STATS)
print("\nprofil MLX bridge:")
for key in sorted(STATS, key=STATS.get, reverse=True):
print(f" {key:<{width}} {STATS[key]:7.1f}s {COUNTS[key]:>6} appels")
def _to_mlx(tensor, dtype=mx.bfloat16):
return mx.array(tensor.detach().float().cpu().numpy()).astype(dtype)
def _to_torch(array, device, dtype):
return torch.from_numpy(np.array(array.astype(mx.float32))).to(device=device, dtype=dtype)
class _Embedding:
def __init__(self, bridge):
self._bridge = bridge
def __call__(self, ids):
b = self._bridge
with timed("embed"):
flat = mx.array(ids.detach().cpu().numpy().reshape(-1).astype(np.int64))
out = b.mlx_model.model.embed_tokens(flat)
out = _to_torch(out, b.device, b.dtype)
return out.reshape(*ids.shape, -1)
class _Inner:
"""Expose `.embed_tokens` et l'appel forward attendus par le pipeline."""
def __init__(self, bridge):
self._bridge = bridge
self.embed_tokens = _Embedding(bridge)
def __call__(self, inputs_embeds=None, past_key_values=None, use_cache=True, **kwargs):
b = self._bridge
cache = past_key_values
if cache is None:
cache = make_prompt_cache(b.mlx_model)
with timed("lm.in"):
h = _to_mlx(inputs_embeds)
with timed("lm.forward"):
out = b.mlx_model.model(None, cache=cache, input_embeddings=h)
mx.eval(out)
with timed("lm.out"):
hidden = _to_torch(out, b.device, b.dtype)
return SimpleNamespace(last_hidden_state=hidden, past_key_values=cache)
class MlxLanguageModel:
def __init__(self, mlx_path, device, dtype=torch.bfloat16):
self.mlx_model, self.mlx_config = load_model(Path(mlx_path), lazy=False)
self.device = torch.device(device)
self.dtype = dtype
self.config = SimpleNamespace(**self.mlx_config)
self.model = _Inner(self)
self._last_hidden_mlx = None
def lm_head(self, hidden):
with timed("lm_head"):
logits = self.mlx_model.lm_head(_to_mlx(hidden))
mx.eval(logits)
with timed("lm_head.out"):
return _to_torch(logits, self.device, torch.float32)
def to(self, *args, **kwargs):
return self
def eval(self):
return self