Spaces:
Running on Zero
Running on Zero
| """ | |
| UltraSharp V2 — 图像超分辨率 Gradio 应用 | |
| ========================================== | |
| ## 模型来源 | |
| 默认从 Kim2091/UltraSharpV2 公开仓库下载 4x-UltraSharpV2.pth, | |
| 自动缓存到 ~/.cache/huggingface/hub/,无需手动上传。 | |
| ## 可选环境变量 | |
| MODEL_REPO_ID 覆盖默认仓库(默认 Kim2091/UltraSharpV2) | |
| MODEL_FILENAME 覆盖默认文件名(默认 4x-UltraSharpV2.pth) | |
| HF_ENDPOINT 镜像站,如 https://hf-mirror.com(国内加速) | |
| HF_TOKEN 私有仓库的 token(公开仓库无需设置) | |
| ## 本地运行 | |
| python app.py | |
| # 国内镜像: HF_ENDPOINT=https://hf-mirror.com python app.py | |
| ## 部署到 HuggingFace Space | |
| 1. 在 Space 设置中将 Hardware 选为 ZeroGPU | |
| 2. 无需设置 Secrets(模型来自公开仓库) | |
| 3. 如需国内镜像,添加 Secret: HF_ENDPOINT = https://hf-mirror.com | |
| """ | |
| import os | |
| import asyncio | |
| import asyncio.base_events | |
| import gradio as gr | |
| from model_loader import UltraSharpV2 | |
| # --------------------------------------------------------------------------- | |
| # 修复 Python 3.12 asyncio 事件循环 GC 时的 "Invalid file descriptor: -1" 报错 | |
| # | |
| # 根因: Gradio / spaces 在 import 阶段会创建临时事件循环,这些循环被 GC | |
| # 回收时 __del__ → close() → _close_self_pipe() 尝试对已关闭的 socket | |
| # (fd=-1) 执行 _remove_reader,触发 ValueError。属于 CPython 3.12 的 | |
| # 已知问题,对功能无害但日志很吵。此处 patch __del__ 静默吞掉该异常。 | |
| # --------------------------------------------------------------------------- | |
| _orig_loop_del = asyncio.base_events.BaseEventLoop.__del__ | |
| def _safe_loop_del(self): | |
| try: | |
| _orig_loop_del(self) | |
| except Exception: | |
| pass | |
| asyncio.base_events.BaseEventLoop.__del__ = _safe_loop_del | |
| # --------------------------------------------------------------------------- | |
| # ZeroGPU 兼容层 | |
| # --------------------------------------------------------------------------- | |
| try: | |
| import spaces | |
| _zerogpu = spaces.GPU(duration=120) # 最长 GPU 占用 120s | |
| IN_ZEROGPU = bool(os.environ.get("SPACES_ZERO_GPU")) | |
| except ImportError: | |
| spaces = None | |
| _zerogpu = None | |
| IN_ZEROGPU = False | |
| def _gpu(fn): | |
| """安全地应用 @spaces.GPU 装饰器(本地开发时退化为无操作)。""" | |
| return _zerogpu(fn) if _zerogpu is not None else fn | |
| # --------------------------------------------------------------------------- | |
| # 模型:始终在 CPU 上加载(ZeroGPU 启动时 GPU 不可用) | |
| # --------------------------------------------------------------------------- | |
| model = UltraSharpV2(device="cpu") | |
| # --------------------------------------------------------------------------- | |
| # 推理参数(RTX PRO 6000 Blackwell / 48GB — 无需省显存) | |
| # --------------------------------------------------------------------------- | |
| _TILE_SIZE = 1024 | |
| _TILE_OVERLAP = 48 | |
| # --------------------------------------------------------------------------- | |
| # 推理函数(生成器模式 — ZeroGPU 硬性要求) | |
| # --------------------------------------------------------------------------- | |
| def on_upscale(image, target_scale): | |
| if image is None: | |
| yield None, "请先上传图片" | |
| return | |
| model.to_cuda() | |
| try: | |
| result, elapsed = model.upscale( | |
| image, _TILE_SIZE, _TILE_OVERLAP, float(target_scale) | |
| ) | |
| finally: | |
| model.to_cpu() | |
| yield result, f"耗时: {elapsed:.2f}s" | |
| # --------------------------------------------------------------------------- | |
| # Gradio UI | |
| # --------------------------------------------------------------------------- | |
| with gr.Blocks(title="UltraSharp V2") as demo: | |
| device_display = "ZeroGPU" if IN_ZEROGPU else model.device.upper() | |
| gr.Markdown("# UltraSharp V2 - 图像超分辨率") | |
| gr.Markdown(f"**运行设备**: {device_display} | **模型原生倍率**: {model.scale}x") | |
| with gr.Row(): | |
| with gr.Column(scale=1): | |
| input_img = gr.Image(label="输入图片", type="pil", height=400) | |
| target_scale = gr.Slider( | |
| label="放大倍率", | |
| minimum=1.0, | |
| maximum=4.0, | |
| value=4.0, | |
| step=0.05, | |
| info="> 模型原生倍率时, 输出先 4x 推理再 Lanczos 缩放", | |
| ) | |
| with gr.Column(scale=1): | |
| run_btn = gr.Button("开始推理", variant="primary") | |
| output_img = gr.Image(label="推理结果", height=400) | |
| status = gr.Textbox(label="状态", interactive=False) | |
| run_btn.click( | |
| fn=on_upscale, | |
| inputs=[input_img, target_scale], | |
| outputs=[output_img, status], | |
| ) | |
| # --------------------------------------------------------------------------- | |
| # ZeroGPU 必须启用 queue(默认并发 1,队列上限 10) | |
| # --------------------------------------------------------------------------- | |
| demo.queue(max_size=10, default_concurrency_limit=1) | |
| if __name__ == "__main__": | |
| demo.launch() | |