12 KiB
Job Server
运行架构
SnapForm 在独立的 Python 3.12 Environment 中运行 HTTP Job Server,Blender 只通过标准库 HTTP Client 提交和轮询任务,不把计算依赖载入 Blender Python。Server 只监听 127.0.0.1,由外置 BlendJob wheel 提供的 ServerController 保存随机端口与 Instance ID;专属本地进程协议不使用额外 Token 鉴权。
每个 Blender 实例启动并持有一个 SnapForm 专属 Server,不发现或复用其它 Blender、其它插件启动的本地 Server。多个 Blender 可以对应多个进程,但模型、Environment 与任务产物仍可共享同一个 Storage Root。
Environment、模型和任务结果默认保存在 ~/.snapform:
~/.snapform/
├── .venv/
├── environment.json
├── models/
├── jobs/
├── manifest.json
├── install.log
├── install-status.json # 仅在安装期间存在
└── server.log
不同 Blender 版本可共享此目录。也可以在 Extension Preferences 的 Storage Root 中修改位置。连接端口与 Instance ID 只保存在启动它们的 Blender 进程内存中。
Blender Client 与独立 Server 的通用 HTTP Runtime 都复用 src/snapform/wheels/ 中随扩展分发的同一份 BlendJob package;Server Environment 不再从 PyPI 安装第二份。JobServer 包装 FastAPI 并提供 Job、FIFO Queue 与 Resource API,JobClient 是无第三方依赖的 Blender 侧访问器。Blender 侧只把 server/__init__.py:server 入口字符串交给 Runtime,不导入 server package;独立进程启动后才取得其中的 server 实例、注册全部公开 Job,并创建 model_manager Resource。
SnapForm 根 package 不再有 client.py、environment.py、storage.py 或 models.py。这些文件会制造并不存在的业务边界:
- Storage Root、
.venv、Manifest、安装状态与日志路径由JobRuntime唯一管理; InstallEnvironment由runtime.operator_classes()提供,Python 与 Job 自身的额外 packages 直接声明在传给 Runtime 的environment字典中;BlendJob 不需要重复声明;- Blender Client 使用根目录
model_catalog.py构造 UI 并检查 Storage Root 下的模型必需文件; - 独立 Server 使用
server/model_catalog.py下载和验证模型,不依赖 Extension 根 package。两份声明的共享存储字段由测试保持一致,已加载 Session 状态仍读取 Server Snapshot。
BlendJob 的 JobClient 只是无依赖 HTTP transport,不是 SnapForm 业务抽象,也不向 Operator 暴露 JobRequest。
SnapForm 的业务 Operator 直接提交参数字典,Job Type 一一对应:
download_model→download-modeldownload_required_models→download-required-modelsremove_background→remove-backgroundextract_frames→extract-framesgenerate_da3_depth→generate-da3-depthgenerate_moge2_depth→generate-moge2-depthgenerate_da3_surface→generate-da3-surfacegenerate_moge2_surface→generate-moge2-surfacedebug_da3→debug-da3debug_moge2→debug-moge2debug_ben2→debug-ben2
DA3 与 MoGe-2 的生产任务不再合并成带 Backend 参数的通用请求。每个 Operator 的 request(context) 直接构造自己的有效参数,Debug 任务也与生产任务完全分开。
Server 端没有 execute_*、*_result 或 CLI worker 中转。全部 @server.job(...) 都集中在 server/__init__.py:简单任务直接实现,复杂任务调用按领域拆分的 media.py、background.py、moge2.py 或 da3.py;共享的纯几何计算位于 geometry.py。模型下载由 ModelManager 单向调用 model_download.py,不存在循环 import。
ONNX-only Environment
扩展不内置 uv 可执行文件或 AI 运行库。Install Environment 首先下载固定版本的 uv,校验 SHA-256 后解压到共享 Storage Root,再用它创建环境并安装:
- ONNX Runtime 1.24.4;Windows 使用
onnxruntime-directml - NumPy 2.4.2、Pillow 12.1.1、PyAV 17.0.0、trimesh 4.11.2
- FastAPI 0.139.2、Uvicorn 0.51.0、huggingface-hub 1.4.1
运行环境不安装 PyTorch、TorchVision、xFormers,也不安装 DA3、MoGe、BEN2 或 utils3d 的 Python 源码包。模型推理和 SnapForm 所需的相机、点云、网格后处理均由 ONNX Runtime、NumPy 与内部实现完成。
Windows 当前发行环境提供 DirectML 与 CPU;Auto 优先使用 DmlExecutionProvider。显式指定不可用设备时会安全回退到 CPU。其他平台安装标准 ONNX Runtime,并按可用 provider 选择 CoreML、OpenVINO 或 CPU。
environment.json 保存规范化后的声明,manifest.json 保存该声明与基础 Server packages 的 Hash。Python、packages、平台 packages、基础 Server packages 或 Environment Schema 改变后需要重新执行 Install Environment,不再维护手工 Manifest Revision。BlendJob 直接随 Extension 更新,不参与 Environment Hash。
模型
所有正式功能都使用 ONNX:
- MoGe-2 ViT-S、ViT-B、ViT-L Normal
- DA3 Small、DA3 Base
- BEN2 Base
首次安装在 post_install(runtime) 中自动下载 MoGe-2 ViT-S Normal 与 FP16 BEN2 Base。它们和 Models panel 的后续下载一样,都是 download-model Job,与推理共用队列、HTTP 进度和取消协议。模型按声明的目录名保存在 ~/.snapform/models/,并保留仓库内部结构。声明了文件清单与校验值的模型先走 Hugging Face resolve 直链流式下载,失败后尝试 https://models.omoolab.xyz/<repository-name>/<relative-path>;两者都失败时当前 Job 直接失败,不记录或猜测后续下载源。模型直链的 TCP、代理 CONNECT 与 TLS 建连最长等待 10 秒;连接后连续 60 秒没有收到响应数据则视为当前来源失败并切换下一来源。该空闲超时不限制模型总下载时长。R2 文件会校验大小与 SHA-256。未声明清单的仓库仍使用 huggingface_hub 快照下载。可用 HF_ENDPOINT 指定 Hugging Face 镜像。
Image Empty 的右键菜单会读取 model_manager 状态;缺少 MoGe-2 ViT-S 或 BEN2 时显示
Download Required Models。对应的 download-required-models Job 在 Server 端再次检查就绪状态,
只下载缺失项,并把多个下载的进度合并为单次 0–100%。必需模型补齐前,右键菜单中的
Convert to Depth Plane 保持禁用。
model_manager Resource 在 Server 内持有磁盘就绪状态和内存 Session。Blender UI 使用 Client catalog 声明的目录名与 ready_patterns 直接检查 <storage-root>/models/,因此展开模型面板、打开转换对话框或 Image Empty 右键菜单都不会仅为状态检测启动 Server。真正下载或推理时,Server 仍按自身 catalog 通过 ModelManager.ready() 重新验证文件。ModelSessionCache 保留 BEN2,并在 DA3 与 MoGe-2 之间只保留最近使用的一种 Depth Session。GET /resources/model_manager 只返回当前加载状态;POST /resources/model_manager/clear 只允许在没有 Active 或 Queued Job 时执行。
DA3 ONNX 使用模型的 camera decoder 恢复外参与内参,不提供 ray-pose 参数。MoGe-2 的 metric depth、normal、mask、normalized intrinsics,以及 BEN2 的 Alpha,均在 NumPy 后处理中恢复。
安装网络与镜像
安装器直接开始工作,不做阻塞式网络预检。packages 由 uv 安装,可用 UV_INDEX_URL 显式指定镜像。Windows 系统代理会注入子进程;同时支持 HTTP_PROXY、HTTPS_PROXY 与 ALL_PROXY。
uv 默认从 Astral 的 GitHub Release 下载,失败后回退到 https://mirror.omoolab.xyz/uv/<version>/。两个来源使用相同的连续字节进度并共用固定 SHA-256 校验。下载压缩包只作临时文件,提取完成后删除。模型下载不经过 GitHub。
首次安装依次显示 Environment、MoGe-2 下载与 BEN2 下载,每项分别使用 0–100%。Environment 由 Job Runtime 依次完成 uv 下载、Python Environment 创建与 packages 安装,uv 不再占用独立一轮进度。两个模型下载由 SnapForm 的 post_install(runtime) 通过 runtime.request() 顺序发起;切换模型时进度归零,但不显示 2/2 等阶段编号。
任一 post-install 模型失败时,Environment 文件仍然保留,但安装 Operator 返回失败并显示聚合后的模型错误,
不再把带 post_install_error 的终态报告为成功。
生命周期
扩展启用后不启动 Server;第一次执行 Job 或点击 Start 时才为当前 Blender 启动专属实例。正常关闭或禁用扩展时直接停止自己启动的进程;Server 也监视 Blender 父进程 PID,父进程异常退出后会自行停止。
AI、帧提取和 Models panel 下载的 Job 进度保存在 Server 内存中。Handler 只使用 context.progress(value, message) 发布进度,终态由 JobServer 内部管理;SnapForm 不再维护独立的 server/progress.py 或 Job 状态 JSON。Blender 每 0.25 秒轮询 GET /jobs/{job_id},任务目录不写 status.json。Operator 直接提交参数字典;Job 被接受时由 JobServer 自动生成 ID 并创建 jobs/<opaque-job-id>/,提交响应同时返回 job_id 与 directory。路径通过 JobContext.directory 传给 Handler,SnapForm Server 强制将输出写入该目录。
job_runtime.py 创建唯一的 runtime = JobRuntime(...),以自身目录为 entrypoint_root,传入 server:server 相对入口字符串与显式 snapform namespace,并导出 runtime.JobOperatorBase。Runtime 独占 Storage Root、Environment 路径、安装进程、Server 控制和 Status Bar。Operator 未覆盖 request(context) 时会提交全部 Property;覆盖后返回值纯替换默认参数。可选的 response(context, result) 在 Blender 主线程直接完成结果导入。SnapForm 不再提供 Client 请求层、Job Mixin、Status Bar 或 Build 中转 Operator。
Environment 安装是 Server 启动前唯一的 Bootstrap 流程:environment.json 记录期望安装的环境声明,install-status.json 在安装期间回传进度并在任务收尾后删除,install.log 持久记录安装输出。Bootstrap 不创建 Job 目录。Environment 成功后执行普通的 post_install(runtime);默认模型下载此时已经是正式 Server Job,会创建自己的 Job 目录,并使用与其它任务相同的进度和取消协议。
Server 使用标准库单 Worker FIFO Queue,全局同时只执行一个 Job,包括模型下载。Server 忙碌时新 Job 进入 queued,不再返回 409;排队任务可以在执行前取消。取消运行中任务不会重启 Server;流式模型下载会在分块检查点结束,ONNX Runtime 当前调用则在返回后的最近检查点结束。JobServer 最多保留 100 个已完成 Job 的内存状态,后续提交时清理更早记录。
Server 由 Environment 通过 python -m blendjob.runner 启动,避免 BlendJob 内部模块名遮蔽 Python 标准库。启动与崩溃信息写入 ~/.snapform/server.log。
已知限制
DA3 ONNX 的注意力是朴素实现(MatMul → Softmax → MatMul),峰值显存与 (帧数 × 每帧 patch 数)² 成正比。迁移到 ONNX 前的 PyTorch 版使用 scaled_dot_product_attention(XFORMERS_DISABLED=1),按块计算注意力分数、不落地完整注意力矩阵,显存近似线性,因此视频能处理几十上百帧;ONNX 导出把 SDPA 回退成朴素注意力,多帧在中高分辨率下会显存爆炸(约 12 帧 @ 1008 即需 ~90 GB)。
Depth Plane 的视频、序列帧转换会复用同一个 ONNX Session 逐帧独立推理,再把结果合并到现有序列输出,因此峰值显存只取决于单帧分辨率。逐帧结果不保留 DA3 的跨视图尺度与相机位姿一致性,适用于当前逐帧动画结构。Surface 只支持单张图片;Debug 多帧输出仍使用联合推理,以保留多视图关系。
联合推理时,onnx_da3.infer 会在执行前按 views × patches 预估注意力张量,超过 4 GiB 直接报错并提示减少帧数或 Resolution Scale,避免 onnxruntime 报出难以定位的分配失败或 UnicodeDecodeError(DirectML 在中文 Windows 下报错信息为 GBK 编码,被 UTF-8 解码掩盖)。
待解决方向:
- 重新导出 DA3 ONNX,使用 ORT 的
Attention/MultiHeadAttention贡献算子,或注入内存高效的注意力算子; - 若未来需要统一坐标系的视频重建,可按小窗口分批推理,再对齐相机位姿与深度尺度。