Skip to content

Commit 9f43d0b

Browse files
committed
fix: avoid idle mlx batch generation
1 parent a0c9d8b commit 9f43d0b

1 file changed

Lines changed: 30 additions & 4 deletions

File tree

xinference/model/llm/mlx/core.py

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -213,7 +213,8 @@ def _get_or_create_generator(self, temperature: float, top_p: float):
213213

214214
def _ensure_background_worker(self, gen_dict):
215215
"""Ensure background worker is running for this generator."""
216-
if gen_dict["task"] is None:
216+
task = gen_dict["task"]
217+
if task is None or task.done():
217218
loop = asyncio.get_event_loop()
218219
gen_dict["task"] = loop.create_task(self._background_worker(gen_dict))
219220

@@ -226,6 +227,13 @@ async def _background_worker(self, gen_dict):
226227

227228
while True:
228229
try:
230+
async with self._get_lock():
231+
has_active_requests = bool(gen_dict["active"])
232+
233+
if not has_active_requests:
234+
await asyncio.sleep(0.001)
235+
continue
236+
229237
self._reset_mlx_lm_generation_stream()
230238
# Get next batch of results for ALL active requests
231239
# Use different API based on mlx-lm version
@@ -268,6 +276,11 @@ async def _background_worker(self, gen_dict):
268276
f"Request {result.uid} finished, removed from active set"
269277
)
270278

279+
has_active_requests = bool(gen_dict["active"])
280+
281+
if not has_active_requests:
282+
self._synchronize_mlx()
283+
271284
# Small delay to prevent busy waiting
272285
await asyncio.sleep(0.0001)
273286

@@ -305,6 +318,19 @@ def _new_mlx_thread_local_stream():
305318
return mx.new_thread_local_stream(device)
306319
return mx.new_stream(device)
307320

321+
@staticmethod
322+
def _synchronize_mlx():
323+
try:
324+
import mlx.core as mx
325+
326+
synchronize = getattr(mx, "synchronize", None)
327+
if synchronize is not None:
328+
synchronize()
329+
else:
330+
mx.eval(mx.array([0]))
331+
except Exception:
332+
logger.debug("Failed to synchronize MLX", exc_info=True)
333+
308334
@staticmethod
309335
def _reset_generation_stream(module_name: str):
310336
try:
@@ -372,9 +398,6 @@ async def generate_stream(
372398
gen_dict = self._get_or_create_generator(temperature, top_p)
373399
batch_generator = gen_dict["generator"]
374400

375-
# Ensure background worker is running for this generator
376-
self._ensure_background_worker(gen_dict)
377-
378401
# Prepare request
379402
assert self._tokenizer_ref is not None
380403
prompt_tokens = self._tokenizer_ref.encode(prompt)
@@ -419,6 +442,9 @@ async def generate_stream(
419442
)
420443
del gen_dict["pending"][inserted_uid]
421444

445+
# Ensure background worker is running after the request is visible.
446+
self._ensure_background_worker(gen_dict)
447+
422448
try:
423449
# Track generated text
424450
generated_tokens = []

0 commit comments

Comments
 (0)