feat(接口2): 支持动态切换Flux模型+分辨率 + 模型对比测试脚本

代码改动:
- comfyui.py: run() 新增 unet_name 参数,提交前自动改写模型节点
  (.gguf→UnetLoaderGGUF, .safetensors→UNETLoader),并按模型自动同步
  文本编码器(4b→qwen_3_4b, 9b→qwen_3_8b),避免切换时维度不匹配
- redraw.py: run_redraw() 透传 unet_name
- service.py: generate_grow_results_swap/generate_grow_results 支持
  redraw_max_side(分辨率参数化) 和 unet_name 透传
- app.py: 接口2 新增 flux_model/redraw_max_side 两个 Form 参数(男女路径都加)
- test_interface2.html: 新增 Flux模型/压图长边 下拉选择器
- add_hair.json/0716add-hair-api.json: 工作流默认模型改为 9b

测试脚本:
- benchmark_matrix.py: 4模型×3分辨率×3图×3次 矩阵测试
- benchmark_hairstyle.py: 3图×5发型×10组合 发型对比测试
- benchmark_report.py/benchmark_hairstyle_report.py: HTML报告生成

清理:
- .gitignore: 排除 benchmark_out/、报告HTML、gateway.log、*.bak.*
- 移除 gateway.log 的 git 跟踪
This commit is contained in:
xsl
2026-07-25 16:16:13 +08:00
parent f509fe99b4
commit 94cdfd6de5
13 changed files with 657 additions and 362 deletions
+34 -1
View File
@@ -30,6 +30,19 @@ _REPO = os.path.dirname(os.path.dirname(__file__))
_INPUT_NODE = "26" # LoadImage:外部输入图(含 alpha 遮罩)
_SEED_NODE = "6" # RandomNoise
_PROMPT_NODE = "60" # JjkText:提示词
_UNET_NODE = "16" # UNETLoader / UnetLoaderGGUFFlux 模型加载
_CLIP_NODE = "61" # CLIPLoaderqwen 文本编码器
# Flux 模型 → 配套文本编码器映射。切换 unet 时自动同步编码器,避免维度不匹配。
# 规则:4b 系列配 qwen_3_4b9b 系列(fp8/GGUF)配 qwen_3_8b_fp8mixed。
def _clip_for_unet(unet_name: str) -> str | None:
"""根据 unet 文件名推断配套的文本编码器文件名;无法推断返回 None。"""
low = unet_name.lower()
if "4b" in low and "9b" not in low:
return "qwen_3_4b.safetensors"
if "9b" in low:
return "qwen_3_8b_fp8mixed.safetensors"
return None
_wf_cache: dict[str, dict] = {} # path → workflow JSON
_wf_output_node: dict[str, str] = {} # path → SaveImage 节点 ID
@@ -86,13 +99,18 @@ def _get_output_node(workflow_path: str | None = None) -> str:
def run(rgba_png_bytes: bytes, timeout: float = COMFY_TIMEOUT, prompt: str = None,
workflow_path: str | None = None, front: bool = False) -> bytes:
workflow_path: str | None = None, front: bool = False,
unet_name: str | None = None) -> bytes:
"""提交一次生发任务,返回输出 PNG 字节。失败抛异常。
prompt:非 None 时替换工作流节点60(JjkText)的文本;None 时用工作流内置默认提示词。
workflow_path:工作流 JSON 路径,None 则用默认 add_hair.json。
frontTrue 时任务插到 ComfyUI 队列最前(server 端 "front" 字段,队列号取负)。
接口2 对时延敏感用 True,避免排在接口3/5 的批量任务后面;其余接口保持 False。
unet_name:非 None 时改写工作流里的模型加载节点(节点16),动态切换 Flux 模型。
.safetensors → 保持 UNETLoader 节点类型不变,只替换 unet_name;
.gguf → 自动把节点类型改成 UnetLoaderGGUF(需装 ComfyUI-GGUF 插件)。
None 时用工作流内置默认模型。
"""
path = workflow_path or _WORKFLOW_DEFAULT
output_node = _get_output_node(path)
@@ -120,6 +138,21 @@ def run(rgba_png_bytes: bytes, timeout: float = COMFY_TIMEOUT, prompt: str = Non
wf[_SEED_NODE]["inputs"]["noise_seed"] = random.randint(0, 2**63 - 1)
if prompt is not None:
wf[_PROMPT_NODE]["inputs"]["text"] = prompt
if unet_name is not None:
node = wf.get(_UNET_NODE)
if node is not None:
# .gguf 需切换到 ComfyUI-GGUF 插件的 UnetLoaderGGUF 节点;
# .safetensors/.ckpt 保持原 UNETLoader 节点类型不变
if unet_name.lower().endswith(".gguf"):
node["class_type"] = "UnetLoaderGGUF"
else:
node["class_type"] = "UNETLoader"
node["inputs"]["unet_name"] = unet_name
# 同步切换配套文本编码器(4b→qwen_3_4b, 9b→qwen_3_8b),避免维度不匹配
clip_node = wf.get(_CLIP_NODE)
clip_name = _clip_for_unet(unet_name)
if clip_node is not None and clip_name is not None:
clip_node["inputs"]["clip_name"] = clip_name
# 诊断:落盘实际提交的工作流 + 输入图,便于和手动 ComfyUI 跑的对比
try: