feat(multimodal): 完成阶段F运行管理与收尾验收
This commit is contained in:
@@ -0,0 +1,111 @@
|
||||
"""User-triggered installation of the fixed optional CUDA runtime on Windows."""
|
||||
import asyncio
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
|
||||
from app.config import BACKEND_DIR
|
||||
from app.errors import ApiError
|
||||
from app.local_models.process import ThreadedProcess
|
||||
|
||||
ROOT = BACKEND_DIR / '.venv-models-cuda'
|
||||
state = {'status': 'unchecked', 'stage': '', 'cuda_available': None}
|
||||
task = None
|
||||
|
||||
|
||||
def ready():
|
||||
return (ROOT / 'ready.json').is_file() and (ROOT / 'Scripts/python.exe').is_file()
|
||||
|
||||
|
||||
async def status():
|
||||
global task
|
||||
if state['status'] == 'unchecked':
|
||||
state.update(status='checking', stage='检查已有 CUDA 组件')
|
||||
task = asyncio.create_task(run(False))
|
||||
return {**state, 'supported': os.name == 'nt', 'custom_interpreter': bool(os.getenv('APP_MODEL_PYTHON'))}
|
||||
|
||||
|
||||
async def install():
|
||||
global task
|
||||
from app.local_models.runtime import runtime
|
||||
if os.name != 'nt':
|
||||
raise ApiError(422, 'PLATFORM_UNSUPPORTED', '此安装入口目前支持 Windows。')
|
||||
if task is not None and not task.done():
|
||||
return await status()
|
||||
if runtime.active or runtime.waiters:
|
||||
raise ApiError(409, 'MODEL_IN_USE', '请等待本地模型任务结束后再安装组件。')
|
||||
if state['status'] == 'installed':
|
||||
return await status()
|
||||
if not shutil.which('uv'):
|
||||
raise ApiError(422, 'UV_NOT_INSTALLED', '后端未找到 uv,请先安装 uv 并重启后端。')
|
||||
state.update(status='installing', stage='准备独立 CUDA 环境', error=None)
|
||||
task = asyncio.create_task(run(True))
|
||||
return await status()
|
||||
|
||||
|
||||
async def execute(args, timeout):
|
||||
process = ThreadedProcess(args, env={**os.environ, 'PYTHONIOENCODING': 'utf-8'},
|
||||
limit=8192, creationflags=0x08000000 if os.name == 'nt' else 0)
|
||||
process.stdin.close()
|
||||
lines = []
|
||||
try:
|
||||
async with asyncio.timeout(timeout):
|
||||
while line := await process.stdout.readline():
|
||||
value = line.decode('utf-8', errors='replace').strip()
|
||||
stages = {'COMPONENT:torch': '下载并安装 PyTorch CUDA(约 3 GB)',
|
||||
'COMPONENT:dependencies': '安装模型依赖', 'COMPONENT:verify': '验证运行组件'}
|
||||
if value in stages:
|
||||
state['stage'] = stages[value]
|
||||
lines = (lines + [value])[-4:]
|
||||
await process.wait()
|
||||
if process.returncode:
|
||||
raise RuntimeError('component command failed')
|
||||
return lines
|
||||
finally:
|
||||
if process.returncode is None:
|
||||
if os.name == 'nt':
|
||||
await asyncio.to_thread(subprocess.run, ['taskkill', '/PID', str(process.process.pid), '/T', '/F'],
|
||||
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL,
|
||||
creationflags=0x08000000)
|
||||
else:
|
||||
process.kill()
|
||||
await process.wait()
|
||||
await process.close()
|
||||
|
||||
|
||||
async def run(download):
|
||||
marker = ROOT / 'ready.json'
|
||||
try:
|
||||
if download:
|
||||
marker.unlink(missing_ok=True)
|
||||
await execute(['powershell.exe', '-NoProfile', '-NonInteractive', '-File',
|
||||
str(BACKEND_DIR / 'scripts/install-model-runtime.ps1'), '-Device', 'cuda',
|
||||
'-RuntimeDirectory', str(ROOT), '-QuietProgress'], 7200)
|
||||
python = ROOT / 'Scripts/python.exe'
|
||||
if not python.is_file():
|
||||
state.update(status='not_installed', stage='尚未安装')
|
||||
return
|
||||
result = await execute([str(python), '-c',
|
||||
'import json, torch, torchaudio, sentence_transformers, qwen_asr; '
|
||||
'assert torch.version.cuda; '
|
||||
'print(json.dumps({"torch":torch.__version__,"cuda_available":torch.cuda.is_available()}))'], 180)
|
||||
info = json.loads(result[-1])
|
||||
marker.write_text(json.dumps(info), encoding='utf-8')
|
||||
state.update(status='installed', stage='组件已安装', error=None, **info)
|
||||
except asyncio.CancelledError:
|
||||
marker.unlink(missing_ok=True)
|
||||
state.update(status='interrupted', stage='安装检查已中断,可重试')
|
||||
raise
|
||||
except Exception:
|
||||
marker.unlink(missing_ok=True)
|
||||
state.update(status='failed', stage='组件安装或验证失败',
|
||||
error='请检查网络、磁盘空间和 uv;可以重试。CPU 环境不受影响。')
|
||||
|
||||
|
||||
async def shutdown():
|
||||
if task is not None and not task.done():
|
||||
task.cancel()
|
||||
await asyncio.gather(task, return_exceptions=True)
|
||||
if state['status'] in {'checking', 'interrupted'}:
|
||||
state['status'] = 'unchecked'
|
||||
Reference in New Issue
Block a user