techfreakworm commited on
Commit
dc09969
·
unverified ·
1 Parent(s): 158626f

fix(lyrics): fallback to transformers/MPS when MLX fails

Browse files

MLX 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.

Files changed (1) hide show
  1. 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 we use ``mlx-lm`` which expects a model ID and returns
105
- ``(model, tokenizer)``. On CUDA / CPU we use ``transformers`` with
106
- ``apply_chat_template`` for the prompt.
107
  """
108
  device = ap.detect_device()
109
  if device == "mps":
110
- from mlx_lm import load # type: ignore[import-not-found]
111
-
112
- model, tokenizer = load(_DEFAULT_MAC_ID)
113
- return _MLXLM(model=model, tokenizer=tokenizer)
114
 
115
- # CUDA / CPU fallback path. Use bfloat16 on CUDA, float32 on CPU.
116
- import torch
117
- from transformers import AutoModelForCausalLM, AutoTokenizer
 
 
118
 
119
- tok = AutoTokenizer.from_pretrained(_DEFAULT_CUDA_ID)
120
- dtype = torch.bfloat16 if device == "cuda" else torch.float32
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
- # Gradio runs handlers in anyio worker threads. MLX maintains a
147
- # *per-thread* default stream and a module-level ``generation_stream``
148
- # that was created at mlx_lm import time on the MAIN thread. Both
149
- # need to be valid in the *current* (worker) thread or
150
- # ``wired_limit().__exit__`` crashes with "There is no Stream(gpu, 0)
151
- # in current thread" when it calls ``mx.synchronize(generation_stream)``.
152
- #
153
- # Two-part fix:
154
- # 1. ``mx.stream(mx.gpu)`` wrap installs the default GPU stream
155
- # for the current thread for the duration of the call.
156
- # 2. Re-assign ``mlx_lm.generate.generation_stream`` to a stream
157
- # created in the *current* thread so ``mx.synchronize`` doesn't
158
- # reach across thread boundaries. The reassignment is safe
159
- # because Gradio's queue runs at default_concurrency_limit=1 —
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