Spaces:
Running on Zero
Running on Zero
fix(lyrics): fallback to transformers/MPS when MLX fails
Browse filesMLX load failure and the worker-thread stream crash both now fall
back to Qwen 2.5 7B via transformers on MPS. Fallback at load time
catches missing/incompatible MLX weights; fallback at generate time
catches the RuntimeError: There is no Stream(gpu, 0) bug. The global
singleton is swapped on first failure so subsequent calls skip MLX.
- lyrics_lm.py +39 -41
lyrics_lm.py
CHANGED
|
@@ -24,12 +24,15 @@ import re
|
|
| 24 |
from dataclasses import dataclass
|
| 25 |
from typing import Any
|
| 26 |
|
|
|
|
|
|
|
| 27 |
import ace_pipeline as ap
|
| 28 |
|
| 29 |
_DEFAULT_MAC_ID = "mlx-community/Qwen2.5-7B-Instruct-4bit"
|
| 30 |
_DEFAULT_CUDA_ID = "Qwen/Qwen2.5-7B-Instruct"
|
| 31 |
|
| 32 |
_LM = None # lazy module-level singleton
|
|
|
|
| 33 |
|
| 34 |
|
| 35 |
def build_system_prompt() -> str:
|
|
@@ -98,30 +101,36 @@ def _get_lm():
|
|
| 98 |
return _LM
|
| 99 |
|
| 100 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 101 |
def _load_lm():
|
| 102 |
"""Construct the per-device LM wrapper.
|
| 103 |
|
| 104 |
-
On MPS
|
| 105 |
-
|
| 106 |
-
``apply_chat_template`` for the prompt.
|
| 107 |
"""
|
| 108 |
device = ap.detect_device()
|
| 109 |
if device == "mps":
|
| 110 |
-
|
| 111 |
-
|
| 112 |
-
model, tokenizer = load(_DEFAULT_MAC_ID)
|
| 113 |
-
return _MLXLM(model=model, tokenizer=tokenizer)
|
| 114 |
|
| 115 |
-
|
| 116 |
-
|
| 117 |
-
|
|
|
|
|
|
|
| 118 |
|
| 119 |
-
|
| 120 |
-
|
| 121 |
-
model = AutoModelForCausalLM.from_pretrained(_DEFAULT_CUDA_ID, torch_dtype=dtype)
|
| 122 |
-
if device == "cuda":
|
| 123 |
-
model = model.to("cuda")
|
| 124 |
-
return _HFLM(model=model, tokenizer=tok)
|
| 125 |
|
| 126 |
|
| 127 |
@dataclass
|
|
@@ -136,36 +145,25 @@ class _MLXLM:
|
|
| 136 |
import mlx_lm.generate as mlx_gen_mod # type: ignore[import-not-found]
|
| 137 |
from mlx_lm import generate # type: ignore[import-not-found]
|
| 138 |
|
| 139 |
-
# Qwen's ChatML template — mlx-lm doesn't expose apply_chat_template
|
| 140 |
-
# the way HF does, so build the prompt manually here.
|
| 141 |
prompt = (
|
| 142 |
f"<|im_start|>system\n{system}<|im_end|>\n"
|
| 143 |
f"<|im_start|>user\n{user}<|im_end|>\n"
|
| 144 |
f"<|im_start|>assistant\n"
|
| 145 |
)
|
| 146 |
-
|
| 147 |
-
|
| 148 |
-
|
| 149 |
-
|
| 150 |
-
|
| 151 |
-
|
| 152 |
-
|
| 153 |
-
|
| 154 |
-
|
| 155 |
-
|
| 156 |
-
|
| 157 |
-
|
| 158 |
-
|
| 159 |
-
|
| 160 |
-
# no two lyrics drafts run concurrently.
|
| 161 |
-
with mx.stream(mx.gpu):
|
| 162 |
-
mlx_gen_mod.generation_stream = mx.new_stream(mx.default_device())
|
| 163 |
-
return generate(
|
| 164 |
-
self.model,
|
| 165 |
-
self.tokenizer,
|
| 166 |
-
prompt=prompt,
|
| 167 |
-
max_tokens=int(kw.get("max_new_tokens", 600)),
|
| 168 |
-
)
|
| 169 |
|
| 170 |
|
| 171 |
@dataclass
|
|
|
|
| 24 |
from dataclasses import dataclass
|
| 25 |
from typing import Any
|
| 26 |
|
| 27 |
+
import logging
|
| 28 |
+
|
| 29 |
import ace_pipeline as ap
|
| 30 |
|
| 31 |
_DEFAULT_MAC_ID = "mlx-community/Qwen2.5-7B-Instruct-4bit"
|
| 32 |
_DEFAULT_CUDA_ID = "Qwen/Qwen2.5-7B-Instruct"
|
| 33 |
|
| 34 |
_LM = None # lazy module-level singleton
|
| 35 |
+
_log = logging.getLogger("ams.lyrics")
|
| 36 |
|
| 37 |
|
| 38 |
def build_system_prompt() -> str:
|
|
|
|
| 101 |
return _LM
|
| 102 |
|
| 103 |
|
| 104 |
+
def _load_hflm(device: str) -> "_HFLM":
|
| 105 |
+
"""Load Qwen 2.5 7B via transformers on the given device (mps/cuda/cpu)."""
|
| 106 |
+
import torch
|
| 107 |
+
from transformers import AutoModelForCausalLM, AutoTokenizer
|
| 108 |
+
|
| 109 |
+
dtype = torch.bfloat16 if device in ("cuda", "mps") else torch.float32
|
| 110 |
+
tok = AutoTokenizer.from_pretrained(_DEFAULT_CUDA_ID)
|
| 111 |
+
model = AutoModelForCausalLM.from_pretrained(_DEFAULT_CUDA_ID, torch_dtype=dtype).to(device)
|
| 112 |
+
return _HFLM(model=model, tokenizer=tok)
|
| 113 |
+
|
| 114 |
+
|
| 115 |
def _load_lm():
|
| 116 |
"""Construct the per-device LM wrapper.
|
| 117 |
|
| 118 |
+
On MPS, try mlx-lm first (4-bit, fast). If MLX fails to load, fall back
|
| 119 |
+
to transformers on MPS. On CUDA/CPU use transformers directly.
|
|
|
|
| 120 |
"""
|
| 121 |
device = ap.detect_device()
|
| 122 |
if device == "mps":
|
| 123 |
+
try:
|
| 124 |
+
from mlx_lm import load # type: ignore[import-not-found]
|
|
|
|
|
|
|
| 125 |
|
| 126 |
+
model, tokenizer = load(_DEFAULT_MAC_ID)
|
| 127 |
+
return _MLXLM(model=model, tokenizer=tokenizer)
|
| 128 |
+
except Exception as exc:
|
| 129 |
+
_log.warning("MLX load failed (%s); falling back to transformers on MPS", exc)
|
| 130 |
+
return _load_hflm("mps")
|
| 131 |
|
| 132 |
+
# CUDA / CPU path.
|
| 133 |
+
return _load_hflm(device)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 134 |
|
| 135 |
|
| 136 |
@dataclass
|
|
|
|
| 145 |
import mlx_lm.generate as mlx_gen_mod # type: ignore[import-not-found]
|
| 146 |
from mlx_lm import generate # type: ignore[import-not-found]
|
| 147 |
|
|
|
|
|
|
|
| 148 |
prompt = (
|
| 149 |
f"<|im_start|>system\n{system}<|im_end|>\n"
|
| 150 |
f"<|im_start|>user\n{user}<|im_end|>\n"
|
| 151 |
f"<|im_start|>assistant\n"
|
| 152 |
)
|
| 153 |
+
try:
|
| 154 |
+
with mx.stream(mx.gpu):
|
| 155 |
+
mlx_gen_mod.generation_stream = mx.new_stream(mx.default_device())
|
| 156 |
+
return generate(
|
| 157 |
+
self.model,
|
| 158 |
+
self.tokenizer,
|
| 159 |
+
prompt=prompt,
|
| 160 |
+
max_tokens=int(kw.get("max_new_tokens", 600)),
|
| 161 |
+
)
|
| 162 |
+
except RuntimeError as exc:
|
| 163 |
+
_log.warning("MLX generate failed (%s); switching to transformers on MPS", exc)
|
| 164 |
+
global _LM
|
| 165 |
+
_LM = _load_hflm("mps")
|
| 166 |
+
return _LM.generate(system, user, **kw)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 167 |
|
| 168 |
|
| 169 |
@dataclass
|