From 938c7eb9b8d16d1e8b3380b94865efa23efa251f Mon Sep 17 00:00:00 2001 From: taking-lying-flat <1615405@qq.com> Date: Wed, 9 Sep 2026 19:18:07 +0800 Subject: [PATCH] fix(infer): run synchronous inference on a dedicated event loop thread --- swift/infer_engine/infer_engine.py | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/swift/infer_engine/infer_engine.py b/swift/infer_engine/infer_engine.py index fd54f0017b..2714a0411f 100644 --- a/swift/infer_engine/infer_engine.py +++ b/swift/infer_engine/infer_engine.py @@ -3,14 +3,14 @@ import concurrent.futures import os from queue import Queue -from threading import Thread +from threading import Lock, Thread from tqdm import tqdm from typing import Any, Dict, Iterator, List, Optional, Union from swift.metrics import Metric from swift.model import get_ckpt_dir from swift.template import Template, get_template -from swift.utils import Processor, ProcessorMixin, get_logger +from swift.utils import Processor, ProcessorMixin, get_logger, start_event_loop_in_daemon from .base import BaseInferEngine from .protocol import (ChatCompletionMessageToolCall, ChatCompletionResponse, ChatCompletionStreamResponse, InferRequest, RequestConfig, UsageInfo) @@ -20,6 +20,8 @@ class InferEngine(BaseInferEngine, ProcessorMixin): + _event_loop_lock = Lock() + def __init__(self, template: Template): processor = template.processor self.template = template @@ -83,12 +85,12 @@ def _get_event_loop(self) -> asyncio.AbstractEventLoop: output-handler task on the first loop that drives them and never recreate it, so running a later batch on a fresh loop would leave that task parked on a stopped loop and hang forever. """ - loop = getattr(self, '_event_loop', None) - if loop is None or loop.is_closed(): - loop = asyncio.new_event_loop() - asyncio.set_event_loop(loop) - self._event_loop = loop - return loop + with self._event_loop_lock: + loop = getattr(self, '_event_loop', None) + if loop is None or loop.is_closed(): + self._event_loop_thread, loop, _ = start_event_loop_in_daemon(name='InferEngine') + self._event_loop = loop + return loop def async_iter_to_iter(self, async_iter, prog_bar, metrics) -> Iterator: queue = Queue() @@ -103,8 +105,7 @@ async def _run_async_iter(): queue.put(None) loop = self._get_event_loop() - thread = Thread(target=lambda: loop.run_until_complete(_run_async_iter())) - thread.start() + asyncio.run_coroutine_threadsafe(_run_async_iter(), loop) pre_output = None while True: output = queue.get() @@ -146,7 +147,8 @@ async def _new_run(task): return res new_tasks = [_new_run(task) for task in tasks] - return self._get_event_loop().run_until_complete(self.batch_run(new_tasks)) + loop = self._get_event_loop() + return asyncio.run_coroutine_threadsafe(self.batch_run(new_tasks), loop).result() @staticmethod def _get_usage_info(num_prompt_tokens: int, num_generated_tokens: int) -> UsageInfo: