UltraSharpV2 / app.py
1lch2
fix error log
1c02f7a
Raw
History Blame Contribute Delete
5.05 kB
"""
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 硬性要求)
# ---------------------------------------------------------------------------
@_gpu
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()