14 KiB
Job Server
本页记录 Environment、模型缓存和 Job Runtime 的内部设计。安装或使用遇到问题时,请先查看 安装与首次配置和常见问题。
运行架构
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_depth→generate-depthgenerate_surface→generate-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,这里只负责注册 Job 与 Resource。完整工作流位于 jobs/;模型适配器和
ONNX 实现位于 models/;纯投影、网格与唯一 Surface Builder 位于 geometry/;文件序列化位于
outputs/;输入准备位于 media/。模型下载由 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。Blender 侧通过 sysctl 识别 Apple Silicon 代际,只有 M4 或更新芯片才提供 MPS 选项;其他 Mac 只提供 CPU。M4 或更新芯片的 CoreML Session 使用 MLProgram 模型格式、全部可用计算单元并允许动态输入 Shape。
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 后处理中恢复。
安装网络与镜像
安装器不做带宽测速、来源竞速或失败后的自动切换。Install Environment 固定使用 Astral GitHub Release、uv 默认 Python 下载源和 PyPI;Install Environment (Mirror) 固定使用 CNB uv Release、CNB python-build-standalone 与清华 PyPI。两个 uv 来源使用同一固定版本和 SHA-256;下载压缩包只作临时文件,安装完成后删除。
镜像模式只在当前安装子进程中设置 UV_PYTHON_INSTALL_MIRROR 与 UV_INDEX_URL,不修改用户环境变量。Windows 系统代理会注入子进程;同时支持 HTTP_PROXY、HTTPS_PROXY 与 ALL_PROXY。post-install 模型下载保持 Hugging Face 与 OmooLab R2 的独立来源逻辑,不读取 Environment 安装模式。
Environment Bootstrap 的单次 0–100% 只包含 uv、Python 与 packages,不包含后续模型下载。开始显示 Starting installation ...;uv 使用 1–20%,显示 1/3 Starting uv download ...、1/3 Downloading uv [速度] 或镜像形式、1/3 Installing uv ...;Python 使用 20–50%,显示 Checking、Downloading、Installing,镜像下载带 (mirror);packages 使用 50–98%,显示 Resolving、Downloading packages [已开始数]/[解析总数]、Installing,镜像模式带 (mirror);98–100% 显示 Verifying installation ...。三个下载区间分别占对应阶段的 84–87%,缓存命中时直接跳过。单阶段状态不显示 1/1。
首次安装依次显示 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 返回失败并显示原始模型错误。
生命周期
扩展启用后不启动 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 只支持单张图片。DA3 Debug 多帧输入仍联合推理;MoGe-2 与 BEN2 Debug 按单图 API 逐帧保存直接返回。
联合推理时,onnx_da3.infer 会在执行前按 views × patches 预估注意力张量,超过 4 GiB 直接报错并提示减少帧数或 Resolution Scale,避免 onnxruntime 报出难以定位的分配失败或 UnicodeDecodeError(DirectML 在中文 Windows 下报错信息为 GBK 编码,被 UTF-8 解码掩盖)。
待解决方向:
- 重新导出 DA3 ONNX,使用 ORT 的
Attention/MultiHeadAttention贡献算子,或注入内存高效的注意力算子; - 若未来需要统一坐标系的视频重建,可按小窗口分批推理,再对齐相机位姿与深度尺度。