# ZeroGPU Space 实现讲解 本文档解释当前 Hugging Face Space 的实现方式、关键设计选择、运行流程、鲁棒性策略,以及后续维护方法。 ## 1. 目标 当前实现要完成的事情很明确: - 基于 `infer_single_image_transformers.py` 的成功推理路径构建 Hugging Face Space - 使用 `Gradio + ZeroGPU` - 支持两种输入方式: - 用户上传自己的图片 - 用户从仓库 `imgs/` 中选择示例图 - 结果需要同时展示: - 模型原始输出文本 - 检测状态 - bbox 坐标 - 画框后的结果图 第一版不追求复杂功能,重点是: - 简洁 - 容易维护 - 稳定 - 对用户输入严格校验 - 对模型边界情况不过度报错 ## 2. 文件说明 当前关键文件如下: - [app.py](/Users/chenzhihui/Desktop/Research/Code/Deepfake_detection/github/demo/app.py:1) - Space 主程序 - 包含 UI、模型加载、推理流程、bbox 后处理、示例图库逻辑 - [requirements.txt](/Users/chenzhihui/Desktop/Research/Code/Deepfake_detection/github/demo/requirements.txt:1) - Space 运行依赖 - [README.md](/Users/chenzhihui/Desktop/Research/Code/Deepfake_detection/github/demo/README.md:1) - Hugging Face Space 配置和部署说明 - [infer_single_image_transformers.py](/Users/chenzhihui/Desktop/Research/Code/Deepfake_detection/github/demo/infer_single_image_transformers.py:1) - 原始成功版 `transformers` 推理脚本 - [imgs]() - 示例图库目录 ## 3. 为什么不再用 `ms_swift` 当前 Space 运行时没有保留 `ms_swift`,原因很直接: - 你已经提供了成功的 `transformers` 版推理脚本 - ZeroGPU 官方路径本身就更适合 `Gradio + PyTorch + transformers` - `transformers` 依赖更常规,部署到 Hugging Face Space 更稳 - 当前需求只是推理,不需要 `ms_swift` 提供的训练或额外封装能力 所以现在的设计是: - 训练、导出、合并模型时可以继续用你自己的原有链路 - 部署到 Space 的运行时只保留 Hugging Face 原生推理链路 这样做的好处是依赖更少,部署更稳定,后续排查问题也更直接。 ## 4. 整体架构 当前 Space 是单文件主程序结构,足够简单,便于维护。 执行链路如下: 1. 页面加载 2. 扫描 `imgs/` 目录,生成示例图库 3. 页面构建完成 4. 用户选择示例图或上传自己的图片 5. 用户点击 `Run Detection` 6. 程序校验输入 7. 将图片缩放到 `1024x1024` 8. 进入 ZeroGPU 推理函数 9. 模型生成文本 10. 程序从文本里解析 `deepfake` bbox 11. 将 bbox 从 `1024x1024` 坐标反算回原图坐标 12. 在原图上画框 13. 返回结果图、状态、bbox 文本、原始模型输出 这里最关键的设计是把流程拆成了两段: - GPU 内: - 模型推理 - GPU 外: - 图像读取 - 输入校验 - resize - bbox 解析 - bbox 坐标映射 - 画框 - UI 输出 这能减少 ZeroGPU 占用时间,降低排队和超时风险。 ## 5. `app.py` 的关键实现 ### 5.1 常量定义 在 [app.py](/Users/chenzhihui/Desktop/Research/Code/Deepfake_detection/github/demo/app.py:28) 到 [app.py](/Users/chenzhihui/Desktop/Research/Code/Deepfake_detection/github/demo/app.py:48): - 定义了示例图目录 `IMAGE_DIR` - 定义了合法图片后缀 - 定义了环境变量名: - `MODEL_ID` - `HF_TOKEN` - 定义了默认提示词: - `DEFAULT_SYSTEM` - `DEFAULT_USER` - 定义了推理尺寸: - `INFER_SIZE = (1024, 1024)` - 定义了统一状态字符串 这样做的目的: - 避免魔法字符串散落到代码里 - 以后改配置不用到处找 - UI 和后端使用统一状态语义 ### 5.2 示例图库 示例图库逻辑在 [app.py](/Users/chenzhihui/Desktop/Research/Code/Deepfake_detection/github/demo/app.py:62) 到 [app.py](/Users/chenzhihui/Desktop/Research/Code/Deepfake_detection/github/demo/app.py:76)。 实现策略很简单: - 程序启动时扫描 `imgs/` - 只接收合法图片扩展名 - 自动忽略 `.DS_Store` 之类无效文件 - 文件名 stem 直接作为展示标题 这样有两个明显好处: - 你后续新增示例图时只要往 `imgs/` 放文件 - 不需要再维护额外的配置文件、JSON、metadata 表 也就是说,示例图库采用的是“目录即配置”的极简策略。 ### 5.3 图片输入统一化 在 [app.py](/Users/chenzhihui/Desktop/Research/Code/Deepfake_detection/github/demo/app.py:79) 到 [app.py](/Users/chenzhihui/Desktop/Research/Code/Deepfake_detection/github/demo/app.py:98): - `_load_image_from_path()` 负责从路径读图 - `_normalize_input_image()` 负责统一处理输入来源 为什么需要这个抽象: - Gradio 可能给你的是 `PIL.Image` - 也可能是路径 - 示例图库和上传图片的来源不完全一样 统一后,后续推理流程只认 `PIL.Image`,这样整个后端逻辑更简单。 ### 5.4 预处理 在 [app.py](/Users/chenzhihui/Desktop/Research/Code/Deepfake_detection/github/demo/app.py:101) 到 [app.py](/Users/chenzhihui/Desktop/Research/Code/Deepfake_detection/github/demo/app.py:106): - 统一用 LANCZOS 重采样缩放到 `1024x1024` 这一步严格对齐原始脚本,避免部署版和服务器版行为不一致。 ### 5.5 bbox 解析与映射 核心逻辑在: - [app.py](/Users/chenzhihui/Desktop/Research/Code/Deepfake_detection/github/demo/app.py:109) - [app.py](/Users/chenzhihui/Desktop/Research/Code/Deepfake_detection/github/demo/app.py:122) - [app.py](/Users/chenzhihui/Desktop/Research/Code/Deepfake_detection/github/demo/app.py:143) 这部分做了三件事: 1. 从模型输出里解析 `deepfake` bbox 2. 将 `1024x1024` 坐标映射回原图 3. 在原图上画红框并打标签 这里有一个重要设计: - `_extract_deepfake_bbox_1024()` 在没有匹配到 bbox 时不抛错,而是返回 `None` 原因是: - “没有 bbox” 不一定是系统失败 - 可能只是模型判断成 `real` - 也可能只是模型输出没有按预期给框 因此这一类情况被定义为“可恢复结果”,而不是系统异常。 ### 5.6 模型加载 模型加载逻辑在 [app.py](/Users/chenzhihui/Desktop/Research/Code/Deepfake_detection/github/demo/app.py:171) 到 [app.py](/Users/chenzhihui/Desktop/Research/Code/Deepfake_detection/github/demo/app.py:240)。 关键点如下: - 使用全局单例缓存: - `_MODEL` - `_PROCESSOR` - `_TOKENIZER` - 使用 `_MODEL_LOCK` 防止并发重复加载 - 使用惰性加载而不是导入时立即加载 惰性加载的好处: - 本地没有配置 `MODEL_ID` 时,程序也能先导入和做静态检查,因为代码内置了默认模型 id - 页面能先起来,再在启动状态里显示模型是否可用 - 降低初始化阶段直接崩溃的概率 加载策略对齐你成功版脚本: - 优先 `Qwen3VLForConditionalGeneration` - 否则回退 `AutoModelForImageTextToText` - `device_map="auto"` - `trust_remote_code=True` - 优先用 `dtype=torch.bfloat16` 这保证了 Space 和你验证成功的脚本尽量一致。 ### 5.7 ZeroGPU 推理函数 在 [app.py](/Users/chenzhihui/Desktop/Research/Code/Deepfake_detection/github/demo/app.py:243) 到 [app.py](/Users/chenzhihui/Desktop/Research/Code/Deepfake_detection/github/demo/app.py:287): - `_generate_text()` 是唯一被 `@spaces.GPU(duration=120)` 包住的函数 这表示: - 真正消耗 GPU 的步骤只集中在这里 - 前后处理都留在 GPU 外 同时这里保留了和原始脚本一致的模板构造方式: - `processor.apply_chat_template(...)` - `processor(...)` - `model.generate(...)` - `tokenizer.batch_decode(...)` 这样推理链路不会因为 UI 改造而偏离原有逻辑。 ### 5.8 结果与状态控制 主入口是 [app.py](/Users/chenzhihui/Desktop/Research/Code/Deepfake_detection/github/demo/app.py:311) 的 `run_inference()`。 它把异常分成三类: 1. 用户输入错误 2. bbox 数学或系统问题 3. 其他系统异常 当前策略是: - `ValueError` -> `Invalid input` - `BBoxError` -> `System error` - 其他异常 -> `System error` 同时如果模型没有返回 bbox: - 不直接失败 - 返回原图 - 根据文本内容给出状态: - `No deepfake region detected` - `Model output did not contain a valid bbox` 这是为了平衡两件事: - 对用户非法输入要严格 - 对模型输出边界情况不要频繁报错 ## 6. UI 设计说明 UI 在 [app.py](/Users/chenzhihui/Desktop/Research/Code/Deepfake_detection/github/demo/app.py:357) 到 [app.py](/Users/chenzhihui/Desktop/Research/Code/Deepfake_detection/github/demo/app.py:514)。 设计上做了几件事: - 页面上方是简洁的说明区 - 中间两栏布局: - 左边输入 - 右边结果 - 下方是示例图库 - 高级参数折叠到 `Accordion` 这样做的原因: - 默认界面尽量简洁 - 主要交互只有“选图/上传图 + 运行” - 提示词和 token 控制属于高级参数,不应打扰普通用户 视觉上保持轻量: - 使用柔和背景和医学感冷色调 - 避免过重装饰 - 强调结果图和输入图本身 ## 7. 为什么本地没有 `spaces` 也能跑检查 代码开头有一个兼容逻辑,在 [app.py](/Users/chenzhihui/Desktop/Research/Code/Deepfake_detection/github/demo/app.py:14) 到 [app.py](/Users/chenzhihui/Desktop/Research/Code/Deepfake_detection/github/demo/app.py:25)。 作用是: - 如果本地没有安装 `spaces` - 就提供一个空实现的兼容装饰器 这样可以保证: - 本地 `py_compile` - 本地导入 `app.py` - 不依赖 ZeroGPU 环境也能做逻辑测试 这不是为了掩盖错误,而是为了本地开发和 Space 部署环境解耦。 真正部署到 Hugging Face Space 时,`spaces` 仍然必须存在。 ## 8. README 的作用 [README.md](/Users/chenzhihui/Desktop/Research/Code/Deepfake_detection/github/demo/README.md:1) 现在只做部署相关的事情,不再承担大段方案讨论。 它现在包含: - Space YAML 配置 - `MODEL_ID` / `HF_TOKEN` 说明 - `preload_from_hub` 说明 - 示例图库目录约定 - 本地运行命令 这样更符合 Hugging Face Space 的实际使用方式。 ## 9. 环境变量说明 ### `MODEL_ID` 可选配置。 作用: - 指向你上传到 Hugging Face Hub 的模型仓库 - 程序通过 `from_pretrained(MODEL_ID)` 加载模型 - 如果没有显式配置,默认使用 `RichardChenZH/MedForge-Reasoner` ### `HF_TOKEN` 只有模型私有时才需要。 作用: - 用于访问私有仓库模型 如果模型是公开仓库,可以不配置。 ## 10. 示例图库如何维护 当前图库目录是 [imgs]()。 维护规则非常简单: - 新增示例:直接放一张图片进去 - 替换示例:直接覆盖原文件或删掉重加 - 删除示例:直接删除文件 不需要改 `app.py`,因为程序会在启动时重新扫描目录。 建议保持: - 文件名简洁 - 图片内容有代表性 - 数量不要太多,避免首屏画廊过重 ## 11. 当前鲁棒性策略 ### 明确报错的情况 这些情况会直接返回 `Invalid input` 或 `System error`: - 没选图 - 上传的不是合法图片 - 图片损坏 - `user_prompt` 里没有 `` - 去掉 `` 后文本为空 - `max_tokens <= 0` - 没配置 `MODEL_ID` 且默认仓库不可用 - 模型拉取失败 - bbox 反算后坐标非法 ### 不直接报错的情况 这些情况不会直接失败: - 模型输出没有 `deepfake` bbox - 模型文字判断倾向 `real/authentic/genuine` 这时会返回: - 原图 - 原始文本 - 清晰的状态说明 这是当前实现里最重要的稳定性设计之一。 ## 12. 已做的验证 本地已经做过的验证包括: - `python -m py_compile app.py infer_single_image_transformers.py` - 导入 `app.py` 成功 - 示例图库能识别出当前 3 张示例图 - 用 mock 方式验证以下路径: - 有效 bbox -> 返回带框结果图 - 无 bbox 且文本含 `real` -> 返回原图和 `No deepfake region detected` - 非法 bbox -> 返回 `System error` - 没有输入图 -> 返回 `Invalid input` 还没有做的只有真实模型联调,这一步需要你在实际 Space 环境中确认默认模型仓库可访问;如需切换仓库,再配置 `MODEL_ID`。 ## 13. 部署步骤 部署到 Hugging Face Space 时建议按这个顺序: 1. 创建 Gradio Space 2. 开启 ZeroGPU 3. 上传当前仓库文件 4. 如果要换模型仓库,再改 `README.md` 顶部的 `preload_from_hub` 5. 在 Space 设置里配置: - `MODEL_ID`,仅在要覆盖默认仓库时配置 - `HF_TOKEN`,如果模型私有 6. 启动 Space 7. 先点击示例图库测试 8. 再测试自定义上传图片 ## 14. 后续如果要扩展,建议怎么做 如果后面你要继续扩展,建议顺序如下: ### 优先可做 - 增加更明确的“模型加载中”状态提示 - 增加更好的错误文案 - 增加示例图说明文案 - 增加更细的状态标签,比如 `real` / `deepfake suspected` ### 可以做但不是第一优先 - 增加生成耗时展示 - 增加更多可调参数 - 增加结果下载按钮 - 增加原图和结果图的对比展示 ### 现在不建议做 - 多模型切换 - 聊天式多轮历史 - 批量推理 - 在线把用户上传图片加入示例图库 - 复杂前后端拆分 原因很简单:这些都会增加维护成本和出错面,不符合当前“极简生产”的要求。 ## 15. 总结 当前实现的核心思路是: - 推理逻辑尽量贴近已经验证成功的 `transformers` 脚本 - UI 保持简单直接 - 示例图库采用目录扫描,方便维护 - 对用户非法输入严格报错 - 对模型边界输出尽量平稳返回 - 尽量减少 ZeroGPU 占用时间 这套实现适合先稳定上线,再在真实 Space 环境里做小步迭代。