File size: 5,051 Bytes
cfd987c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
e2e4ca3
1c02f7a
cfd987c
 
 
1c02f7a
 
 
 
 
 
 
 
 
 
e2e4ca3
1c02f7a
e2e4ca3
1c02f7a
e2e4ca3
 
 
 
1c02f7a
e2e4ca3
cfd987c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
e2e4ca3
 
 
 
 
 
 
cfd987c
 
 
 
e2e4ca3
cfd987c
 
 
 
 
 
 
e2e4ca3
cfd987c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
e2e4ca3
cfd987c
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
"""
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()