优化为4b 模型

This commit is contained in:
xsl
2026-07-18 15:30:31 +08:00
parent 659c037270
commit b58cd4c441
289 changed files with 9367 additions and 558 deletions
+153
View File
@@ -0,0 +1,153 @@
# 接口2 女性生发 — 耗时分析与优化方案
> 分析日期:2026-07-18
> 基准数据:19张图片 × 5种发际线 = 95次调用,平均 11.1s,最快 8.3s,最慢 14.0s
---
## 一、完整调用链与耗时拆解
`girl1.jpg` 单次调用(13.66s)为例,从 [hairline_grow.log](file:///home/ubuntu/hair/log/hairline_grow.log) 提取的精确时间:
```
接口2 generate_grow_results_swap()
├─ ① extract_context() ~1.8s
│ ├─ FaceLandmarker (MediaPipe) 检测 ~0.5s
│ ├─ FaceParser (SegFormer CPU) 头发分割 ~0.9s ← 瓶颈③
│ └─ 3D提升 + 平滑 ~0.4s
├─ ② generate_hairline_redraw() ~5.8s
│ ├─ detector.detect (MediaPipe) ~0.01s
│ ├─ SegFormer CPU 头发分割 ~0.9s ← 瓶颈③(重复!)
│ ├─ 遮罩计算 ~0.08s
│ ├─ _call_swap → swapHair HTTP (:8801) ~4.8s ← 瓶颈①(35%
│ │ └─ change_hair → webui SD1.5 img2img
│ └─ 接缝融合 + band_mask ~0.05s
└─ ③ _call_local_redraw → ComfyUI Flux-2 ~6.0s ← 瓶颈②(44%
├─ 上传图片 + 提交工作流 ~0.3s
├─ Flux-2 推理 (steps=4) ~4.5s
├─ 轮询 /history (sleep=1.0s) ~0.5s ← 可优化
└─ 下载结果图 ~0.7s
```
### 耗时占比
| 步骤 | 耗时 | 占比 | 可优化 |
|------|------|------|--------|
| swapHair HTTP (webui SD推理) | 4.8s | 35% | ✅ |
| ComfyUI Flux-2 推理 | 6.0s | 44% | ✅ |
| SegFormer CPU × 2次 | 1.8s | 13% | ✅ |
| 其他(MediaPipe+3D+融合) | 1.0s | 8% | — |
---
## 二、优化方案(按收益排序)
### 优化1ComfyUI 轮询间隔 1.0s → 0.2s(省 ~0.5-0.8s
**问题**[comfyui.py:148](file:///home/ubuntu/hair/hairline/comfyui.py#L148) `time.sleep(1.0)` — 每次轮询固定等1秒,Flux-2推理只需~4.5s,但轮询可能多等0.5-1s。
**改法**
```python
# comfyui.py:148
time.sleep(1.0) time.sleep(0.2)
```
**收益**:每次调用省 0.5-0.8s95次省 ~60s。
---
### 优化2:避免重复 SegFormer 推理(省 ~0.9s
**问题**`extract_context()` 在 [service.py:128](file:///home/ubuntu/hair/hairline/service.py#L128) 跑了一次 SegFormer`_grow_core()` 在 [hairline_grow.py:879](file:///home/ubuntu/hair/face_analysis/hairline_grow.py#L879) 又跑一次 SegFormer。同一张图做了两次相同的头发分割。
**改法**:让 `generate_hairline_redraw` 接受外部传入的 `parse_map`,跳过内部重复分割。
**收益**:每次调用省 ~0.9s95次省 ~85s。
---
### 优化3SegFormer 移到 GPU(省 ~0.9s,需换 torch cu128
**问题**[service.py:61](file:///home/ubuntu/hair/hairline/service.py#L61) 注释:
```
⚠️ 本 worker 是 RTX 5090(sm_120)torch 2.2.2(cu121) 只编到 sm_90
SegFormer 默认走 CPU~2.5s/张)
```
**改法**
1. 升级 torch 到 cu128`pip install torch torchvision --index-url https://download.pytorch.org/whl/cu128`
2. 设置环境变量:`SEG_DEVICE=cuda`
**收益**SegFormer CPU 0.9s → GPU 0.05s,省 ~0.85s。
---
### 优化4Flux-2 工作流 steps=4 → 2-3(省 ~1-2s
**问题**[0716add-hair-api.json](file:///home/ubuntu/hair/0716add-hair-api.json) 的 KSampler steps=4,每步约 1.1s。
**改法**:修改工作流 JSON 中 KSampler 节点的 `steps` 值。需质量验证。
**收益**steps 4→2 省 ~2.2ssteps 4→3 省 ~1.1s。
---
### 优化5swapHair webui 推理优化(省 ~1-2s
**问题**swapHair 调 change_hair:8801 → webui:57860 SD1.5 img2imgdenoising_strength=0.6。
**可能方向**
- 降低 denoising_strength0.6→0.45
- webui 启用 `--medvram``--xformers`(已启用 xformers
- 检查是否可换更快的 sampler(如 DPM++ 2M Karras
**收益**:需实测验证。
---
### 优化6base64 编解码优化(省 ~0.2s
**问题**`_call_swap` 把图片编成 base64 JSON 传输([hairline_grow.py:516-521](file:///home/ubuntu/hair/face_analysis/hairline_grow.py#L516-L521)),`_call_local_redraw` 又解一次。大图 base64 编解码耗时显著。
**改法**:用 multipart 上传代替 base64 JSON。
---
## 三、预期收益汇总
| 优化项 | 难度 | 单次省时 | 累计省时(95次) |
|--------|------|---------|---------------|
| 轮询 1s→0.2s | 极简 | 0.5s | 48s |
| 避免重复SegFormer | 中 | 0.9s | 86s |
| SegFormer→GPU | 中(需换torch) | 0.85s | 81s |
| Flux-2 steps 4→2 | 简单(需验证) | 2.2s | 209s |
| swapHair优化 | 复杂 | 1.5s | 143s |
| base64优化 | 中 | 0.2s | 19s |
### 乐观预期
| 阶段 | 平均耗时 | 95次总耗时 |
|------|---------|-----------|
| 当前 | 11.1s | ~17.6分钟 |
| 优化1-3(立即可做) | ~8.8s | ~14分钟 |
| 优化1-4(含Flux-2 | ~6.6s | ~10.5分钟 |
| 全部优化 | ~5.5s | ~8.7分钟 |
---
## 四、推荐优先级
1. **立即做**(无风险,无需换 torch):
- 优化1:轮询间隔 0.2s
- 优化2:避免重复 SegFormer
- 优化4Flux-2 steps 4→3(先验证质量)
2. **短期做**(需换 torch):
- 优化3SegFormer→GPU
3. **中期探索**
- 优化5swapHair webui 参数调优
- 优化6base64→multipart
+177
View File
@@ -0,0 +1,177 @@
# 接口3 B端生发 — 实现文档
> 文档日期:2026-07-18
---
## 一、接口概述
**接口3** 是 B端(医生/操作端)生发接口。医生在用户照片上手动用马克笔画出发际线后,只需上传这一张划线图,系统自动检测划线 → 生成遮罩 → 送 ComfyUI 生发,返回「植发3个月」效果图。
**与接口2 的核心区别**
| 特性 | 接口2(C端生发) | 接口3(B端生发) |
|------|----------------|----------------|
| 输入 | 原始照片 | 划线图(含手绘线) |
| 发际线来源 | 系统按发型模板自动生成 | 医生手绘标注 |
| 发型类型 | ellipse/flower/heart/straight/wave | custom(自定义) |
| 中间步骤 | extract_context + swapHair + ComfyUI重绘 | 划线检测 + 遮罩 + ComfyUI生发 |
| 是否调 change_hair | 是(女性流程) | 否 |
| ComfyUI 工作流 | 0716add-hair-api.json(重绘) | add_hair.json(生发) |
| 典型耗时 | ~11s | ~6-8s |
---
## 二、接口定义
### 路由
```
POST /api/v1/hair/grow-b
```
### 入参
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `marked_image_file` | UploadFile | 三选一 | 划线图片文件(JPG/PNG) |
| `marked_image_url` | str | 三选一 | 划线图片 URL |
| `marked_image_base64` | str | 三选一 | 划线图片 base64 |
| `use_mask` | bool | 否(默认True) | 是否自动检测划线并建遮罩。False时跳过检测,直接送划线图 |
| `prompt` | str | 否 | ComfyUI 提示词,默认"补充遮罩区域的头发,加一点美颜" |
### 返回
```json
{
"code": 0,
"message": "success",
"data": {
"hair_growth_image_base64": "iVBORw0KGgo...(生发图 JPG base64",
"hairline_type": "custom"
}
}
```
错误码:
- `1001`: 无法识别人像 / 未检测到发际线划线
- `1007`: 处理失败
- `1008`: 图片格式不支持
---
## 三、完整调用链
```
POST /api/v1/hair/grow-b
├─ app.py hair_grow_b() [app.py:929]
│ ├─ resolve_image_bytes() → marked_raw 解析图片(file/url/base64三选一)
│ ├─ cv2.imdecode → marked_bgr 解码为 BGR
│ └─ run_in_threadpool(generate_grow_b, ...)
├─ service.py generate_grow_b(marked_bgr, use_mask, prompt) [service.py:381]
│ │
│ ├─ 步骤1:人脸检测 + 头发分割(仅 use_mask=True 时)
│ │ ├─ get_landmarker().detect(rgb) MediaPipe 478点人脸检测
│ │ │ → landmarks(无人脸返回 no_face
│ │ ├─ get_parser().parse(rgb) SegFormer 面部分割(CPU ~0.9s
│ │ │ → parse_mapint label map
│ │ │
│ ├─ 步骤2:手绘发际线检测(仅 use_mask=True 时)
│ │ ├─ detect_marker_hairline(marked_bgr, landmarks, parse_map)
│ │ │ │ [marker_detect.py:41]
│ │ │ ├─ forehead_upper_region(landmarks) 额头上部 ROI
│ │ │ ├─ head_silhouette(parse_map) 头部轮廓 ROI
│ │ │ ├─ _blackhat(gray) 黑帽变换(响应比邻域暗的细结构)
│ │ │ ├─ _snap_anchor(bh, 左鬓角21) 左锚点吸附
│ │ │ ├─ _snap_anchor(bh, 右鬓角251) 右锚点吸附
│ │ │ ├─ route_through_array(cost, 左, 右) Dijkstra最小代价路径
│ │ │ └→ path (N,2) row,col(拒识返回 None → no_line
│ │ │
│ │ ├─ path_to_curve_mask(path) 路径→曲线maskuint8 0/255
│ │ └─ mask_from_curve(curve_mask, landmarks, parse_map)
│ │ │ [mask.py]
│ │ ├─ _above_curve_region(curve_mask) 曲线以上区域
│ │ ├─ cv2.morphologyEx(闭运算) 填洞
│ │ ├─ 最大连通域
│ │ └─ 高斯羽化 → mask (uint8 0-255)
│ │
│ ├─ 步骤3:合成 RGBA PNG
│ │ ├─ compose_comfy_rgba(marked_bgr, mask) RGB=原图,alpha=255×(1-mask)
│ │ └─ PNG 编码 → rgba_png_bytes
│ │
│ └─ 步骤4ComfyUI 生发
│ └─ comfyui.run(rgba_png_bytes, prompt) [comfyui.py:87]
│ ├─ 上传图片到 ComfyUI /upload/image
│ ├─ 加载工作流 add_hair.json
│ ├─ 替换节点26输入图 + 节点6随机seed + 节点60提示词
│ ├─ POST /prompt 提交工作流
│ ├─ 轮询 /history/{prompt_id}(间隔0.2s
│ └─ GET /view 取回输出 PNG → grown_png
└─ 返回 {"grown_png": bytes, "status": "ok"}
```
---
## 四、用到的模型和外部服务
| 模型/服务 | 用途 | 位置 | 设备 |
|----------|------|------|------|
| **FaceLandmarker** (MediaPipe) | 478点人脸检测 | hairline/face_landmarks.py | CPU |
| **FaceParser** (SegFormer) | 面部分割(hair/skin/... | hairline/face_parsing.py | CPU (5090不兼容cu121) |
| **ComfyUI** (Flux-2) | 生发图生成 | hairline/comfyui.py → :8188 | GPU |
**注意**:接口3 **不调用** change_hair 服务(:8801),不需要 swapHair。这是它与接口2女性流程的关键区别。
---
## 五、核心算法:手绘发际线检测
### 5.1 为什么不用简单阈值?
手绘马克笔线条的灰度值与皮肤阴影、抬头纹等重叠,全局阈值无法区分。采用**黑帽变换 + Dijkstra最小路径**方案。
### 5.2 黑帽变换(Black Hat
```python
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (k, k))
bh = cv2.morphologyEx(gray, cv2.MORPH_BLACKHAT, kernel)
```
黑帽 = 闭运算 − 原图,响应"比局部邻域暗的细结构"(即马克笔线条),对抬头纹/眉毛/发丝鲁棒。
### 5.3 Dijkstra 最小代价路径
1. **ROI 限定**:额头上部 ∩ 头部轮廓(排除背景)
2. **锚点**:左鬓角(21) / 右鬓角(251) MediaPipe 关键点
3. **代价图**`cost = (bh.max() - bh) + 1.0`ROI外设 1e6
4. **路径**`route_through_array(cost, 左锚, 右锚)` — skimage 的 Dijkstra 实现
### 5.4 拒识机制
路径平均黑帽响应 < 8.0 → 判定"未画线",返回 `no_line`
---
## 六、与接口1、接口2 的对比
| 维度 | 接口1 | 接口2 | 接口3 |
|------|-------|-------|-------|
| 功能 | 四庭七眼测量 | C端生发(5种发际线) | B端生发(手绘线) |
| 路由 | /api/v1/face/measure | /api/v1/hair/grow | /api/v1/hair/grow-b |
| 输入 | 正面照 | 正面照 | 划线图 |
| MediaPipe | ✅ | ✅ | ✅ |
| SegFormer | ✅ | ✅ | ✅ |
| change_hair | ❌ | ✅(女性) | ❌ |
| ComfyUI | ❌ | ✅(Flux-2重绘) | ✅(Flux-2生发) |
| 典型耗时 | ~2s | ~11s | ~6-8s |
| ComfyUI工作流 | — | 0716add-hair-api.json | add_hair.json |
---
## 七、测试
- **测试页面**[static/test_interface3.html](file:///home/ubuntu/hair/static/test_interface3.html)
- **测试图片**[image/girl_img/girl13.jpg](file:///home/ubuntu/hair/image/girl_img/girl13.jpg)(需手动在图上画发际线后作为划线图上传)
+325
View File
@@ -0,0 +1,325 @@
# 目标:所有接口 < 10 秒 — 三个优化方案
> 日期:2026-07-18
> 基准数据(优化1+2后轮换测试 5轮×4接口)
---
## 当前性能基线
| 接口 | 平均 | 最快 | 最慢 | 目标 | 差距 |
|------|------|------|------|------|------|
| 接口1-测量 | 0.1s | 0.1s | 0.1s | <10s | ✅ 已达标 |
| 接口2-女-椭圆 | 17.6s | 10.0s | 19.7s | <10s | -7.6s |
| 接口2-男-椭圆 | 8.1s | 7.9s | 8.4s | <10s | ✅ 已达标(余量小) |
| 接口3-B端生发 | 22.9s | 22.6s | 23.3s | <10s | -12.9s |
---
## 根因分析
### ComfyUI 模型反复换出(最大瓶颈)
ComfyUI 服务端日志(`journalctl -u comfyui`)揭示了根因:
```
12:17:14 model_type FLUX
12:17:16 loaded completely; 17421 MB usable, 15622 MB loaded ← 全量加载(15.6GB)
12:17:16 Requested to load Flux2
12:17:23 Unloaded partially: 11904 MB freed, 3718 MB remains ← 部分卸载
12:17:24 loaded completely; 11832 MB usable, 8996 MB loaded ← 重新加载(8.9GB)
12:17:27 VAE loaded
12:17:29 Requested to load Flux2 ← 男性复用(快)
12:17:31 loaded completely; 11671 MB usable, 8996 MB loaded ← 已加载,6s完成
12:17:43 Unloaded partially: 8996 MB freed, 0.04 MB remains ← 全部卸载!
12:17:45 loaded completely; 17567 MB usable, 15622 MB loaded ← 全量重载(15.6GB)
12:17:52 Unloaded partially: 11904 MB freed, 3718 MB remains ← 再次部分卸载
12:17:54 loaded completely; 11814 MB usable, 8996 MB loaded ← 再次重载(8.9GB)
12:17:58 接口3 完成(21.5s
```
**ComfyUI 在每次执行后都会卸载模型,下次执行时重新加载。** 模型加载耗时 ~8-15s,是接口3和接口2女超时的主因。
### 显存分布(32GB GPU
| 进程 | 显存 | 说明 |
|------|------|------|
| ComfyUI (Flux-2) | ~10-15 GB | 动态加载/卸载 |
| hair_service_sd | ~8 GB | GAN 模型常驻 |
| SD WebUI | ~3.2 GB | SD1.5 + ControlNet |
| Hair 服务 | ~0.7 GB | uvicorn + 模型 |
| **合计** | **~22-27 GB** | **仅剩 5-10 GB 可用** |
显存不足导致 ComfyUI 无法常驻完整的 Flux-2 模型(~15.6GB 全量或 ~9GB FP8)。
### 耗时拆解
**接口322.9s**
```
ComfyUI 模型加载: ~15s ← 根因
ComfyUI 推理(steps=6): ~6s
预处理(人脸+SegFormer+划线): ~1.6s
```
**接口2女(17.6s**
```
ComfyUI 模型加载: ~10s ← 根因(模型被接口3卸载后需重载)
ComfyUI 推理(steps=4): ~4s
swapHair HTTP: ~4.6s
预处理(extract_context): ~1.5s(优化2后 SegFormer 只跑一次)
```
**接口2男(8.1s**
```
ComfyUI 推理(steps=6): ~6s
预处理: ~2s
(模型常被前一个女性接口加载好,所以无重载开销)
```
---
## 方案 A:释放显存给 ComfyUI(最小改动)
### 核心思路
ComfyUI 需要 ~15.6GB 显存才能全量加载 Flux-2。当前被 hair_service_sd8GB)和 SD WebUI3.2GB)挤占。如果释放足够显存,ComfyUI 就能常驻模型,消除反复加载。
### 具体措施
#### A1. 接口3不依赖 hair_service_sd
接口3 **不调用** change_hair:8801 的 swapHair。但 hair_service_sd 的所有 GAN 模型(8GB)常驻显存只为接口2女服务。
**方案**:将 hair_service_sd 改为**按需启动/停止**
- 接口2女调用前启动 hair_service_sd
- 接口2女完成后停止 hair_service_sd(释放 8GB
- 接口3和接口2男不启动 hair_service_sd
**释放**8 GB → ComfyUI 可用显存从 ~5GB 增至 ~13GB
#### A2. 接口2女改为先启动 hair_service_sd → swapHair → 停止 → 再调 ComfyUI
```
接口2女流程:
1. 启动 hair_service_sd (8s) ← 可预热
2. swapHair (4.6s)
3. 停止 hair_service_sd → 释放 8GB
4. ComfyUI 重绘 (4-6s, 模型常驻)
总: ~17s → 仍有问题
```
**问题**hair_service_sd 启动需要 8s(加载全部 GAN),反而增加耗时。
#### A3. 停止 SD WebUI3.2GB
SD WebUI 只被 change_hair 的 swapHair 使用(接口2女)。如果接口2女改用其他方案(见方案B),可以完全停止 SD WebUI。
**释放**3.2 GB
#### A4. 总释放和预期效果
| 措施 | 释放 | ComfyUI 可用 |
|------|------|--------------|
| 当前 | — | ~5-10 GB |
| A1 (停 hair_service_sd) | +8 GB | ~13-18 GB |
| A3 (停 SD WebUI) | +3.2 GB | ~16-21 GB |
| **合计** | **+11.2 GB** | **~16-21 GB** |
Flux-2 全量 15.6GB,释放后 ComfyUI 可常驻模型。
### 预期耗时
| 接口 | 当前 | 优化后 | 达标 |
|------|------|--------|------|
| 接口1 | 0.1s | 0.1s | ✅ |
| 接口2-女 | 17.6s | ~11s (swapHair 4.6s + ComfyUI 4s + 预处理 1.5s + 启停 1s) | ❌ 差1s |
| 接口2-男 | 8.1s | ~8s | ✅ |
| 接口3 | 22.9s | ~8s (ComfyUI 6s + 预处理 1.6s) | ✅ |
### 优点
- 改动最小:主要改服务启停逻辑
- 不影响算法质量
- 不需要换 torch 版本
### 缺点
- 接口2女仍可能超 10sswapHair 4.6s 是硬瓶颈)
- hair_service_sd 启停增加系统复杂度
- 需要精确的显存管理
---
## 方案 B:统一工作流 + 降 steps(中等改动)
### 核心思路
两个工作流(0716add-hair-api.json 27节点 vs add_hair.json 29节点)加载相同模型但参数不同(steps=4 vs steps=6),切换时触发 ComfyUI 重新初始化。统一为一个工作流 + 降低 steps 可消除切换开销并加速推理。
### 具体措施
#### B1. 统一为单一工作流
`add_hair.json` 的 steps 从 6 降到 4,与 `0716add-hair-api.json` 一致。然后所有接口都用同一个工作流。
**改动**
- `add_hair.json`: KSampler steps 6 → 4
- 接口2男和接口3统一用修改后的 `add_hair.json`
- 接口2女也改用 `add_hair.json`(不再用 `0716add-hair-api.json`
**效果**:ComfyUI 不再在工作流间切换,模型可常驻。
#### B2. 进一步降低 steps 到 2
Flux-2 模型在 steps=2 时仍能生成合理结果(需质量验证)。
| steps | 单次推理 | 质量 |
|-------|---------|------|
| 6 | ~6s | 最好 |
| 4 | ~4s | 好 |
| 2 | ~2s | 可接受(需验证) |
#### B3. 接口2女去 swapHair(可选但关键)
swapHair 调 change_hair:8801 → webui:57860 SD1.5 img2img,耗时 4.6s。如果去掉 swapHair
- 女性直接用原图 + 遮罩 → ComfyUI Flux-2 重绘
- 省去 4.6s + 不需要启动 hair_service_sd + SD WebUI
- 释放 ~11.2GB 显存给 ComfyUI
**但**:swapHair 是换发型功能,去掉后女性发型会不同。需要确认产品是否接受。
### 预期耗时
| 接口 | 当前 | B1(steps=4) | B2(steps=2) | B3(去swapHair) |
|------|------|-------------|-------------|----------------|
| 接口2-女 | 17.6s | ~10s | ~8s | ~6s |
| 接口2-男 | 8.1s | ~6s | ~4s | ~4s |
| 接口3 | 22.9s | ~8s | ~6s | ~6s |
### 优点
- 消除工作流切换开销
- 降低推理时间
- 可与方案A叠加
### 缺点
- steps 降低可能影响质量(需验证)
- 去 swapHair 改变女性发型效果
- 需要修改工作流 JSON 和服务代码
---
## 方案 C:ComfyUI 预热 + 模型常驻(系统级优化)
### 核心思路
通过 ComfyUI API 和配置,让 Flux-2 模型在启动后常驻显存,不执行后自动卸载。结合预热请求,确保每次调用时模型已在显存。
### 具体措施
#### C1. 配置 ComfyUI 保持模型加载
ComfyUI 有 `--reserve-vram``--gpu-only` 参数控制 VRAM 管理:
```bash
# 启动 ComfyUI 时禁用模型卸载
python main.py --listen --gpu-only --reserve-vram 0
```
- `--gpu-only`:禁止将模型卸载到 CPU(保持 GPU 常驻)
- `--reserve-vram 0`:不预留 VRAM(最大化模型可用空间)
**前提**:需要释放足够显存(结合方案A的A1/A3)。
#### C2. ComfyUI 预热请求
在 hair 服务启动时,发送一个空请求给 ComfyUI,触发模型加载:
```python
# hair 服务启动后
comfyui.warmup() # 发送最小工作流,触发 Flux-2 加载到 VRAM
```
**效果**:第一次请求不再需要等待模型加载。
#### C3. 停止其他占用显存的服务
与方案A结合:
- 停止 hair_service_sd(释放 8GB
- 停止 SD WebUI(释放 3.2GB
- 仅保留 ComfyUI + Hair 服务
**总可用显存**32GB - 0.7GB(Hair) = ~31GB → 足以常驻 Flux-215.6GB
#### C4. ComfyUI 模型管理优化
设置环境变量:
```bash
export COMFYUI_MODEL_KEEP=true # 保持模型加载
```
或在 ComfyUI 的 `extra_model_paths.yaml``comfyui/config.yaml` 中配置模型缓存策略。
#### C5. 接口2女 swapHair 异步化
swapHair(4.6s)是接口2女的独立瓶颈。改为异步流水线:
```
1. 接收请求 → 异步调 swapHair
2. 同时开始预处理(extract_context + 遮罩)
3. swapHair 完成 + 预处理完成 → 合并
4. ComfyUI 重绘
```
**效果**swapHair 4.6s 与预处理 1.5s 并行,省 1.5s。
### 预期耗时
| 接口 | 当前 | C方案 |
|------|------|-------|
| 接口1 | 0.1s | 0.1s |
| 接口2-女 | 17.6s | ~9s (swapHair 4.6s ∥ 预处理 1.5s + ComfyUI 4s) |
| 接口2-男 | 8.1s | ~6s (ComfyUI 4s + 预处理 2s) |
| 接口3 | 22.9s | ~6s (ComfyUI 4s + 预处理 1.6s) |
### 优点
- 从根本上解决模型换出问题
- 效果最稳定(模型常驻后不再有抖动)
- 可与方案B叠加(steps 降到2则接口2女 ~6s
### 缺点
- 需要停掉 hair_service_sd 和 SD WebUI(影响其他功能)
- 接口2女仍依赖 swapHair4.6s
- ComfyUI 配置改动需验证不影响稳定性
- `--gpu-only` 在显存不足时可能导致 OOM 崩溃
---
## 三方案对比
| 维度 | 方案A(释放显存) | 方案B(统一工作流) | 方案C(模型常驻) |
|------|----------------|------------------|-----------------|
| 改动量 | 小 | 中 | 中-大 |
| 接口2女 | ~11s ❌ | ~8s ✅ | ~9s ✅ |
| 接口3 | ~8s ✅ | ~6s ✅ | ~6s ✅ |
| 接口2男 | ~8s ✅ | ~4s ✅ | ~6s ✅ |
| 质量 | 不变 | 需验证 | 不变 |
| 风险 | 低 | 中(质量) | 中(OOM) |
| 可叠加 | ✅ | ✅ | ✅ |
---
## 我的建议
**推荐:B + C 组合(统一工作流 + 模型常驻)**
理由:
1. **方案C** 从根本上解决模型换出(最大瓶颈),但需要停掉 hair_service_sd 和 SD WebUI
2. **方案B** 降低 steps 到 2-4,进一步减少推理时间
3. **方案B的B3(去swapHair** 是接口2女达标的关键:去掉 swapHair 后不再需要 hair_service_sd 和 SD WebUI,为方案C释放显存
4. 组合后所有接口都能稳定在 6-8s
**关键决策点**:接口2女是否可以去掉 swapHair(换发型)?如果可以,方案B+C组合是最佳选择。如果不行,需要方案A(hair_service_sd 按需启停)+ 方案C + swapHair 异步化。
需要你确认:
1. 接口2女是否可以去掉 swapHair(直接用原图+遮罩 → ComfyUI)?
2. 是否可以永久停止 SD WebUI 和 hair_service_sd
3. steps 从 6 降到 2 是否可接受(需要质量验证)?
+298
View File
@@ -0,0 +1,298 @@
# 优化方案 V2 — 修订版(约束:保留 swapHair + 保留所有服务)
> 日期:2026-07-18
> 约束:不能去掉 swapHair、不能停止 hair_service_sd 和 SD WebUI
---
## 根因总结
**ComfyUI 未启用 `--gpu-only`,默认在每次请求后卸载模型到 CPU。**
9B FP8 模型(8.8GB) + 文本编码器(7.7GB) + VAE(0.3GB) = **16.8GB 模型显存**,可用 20GB 中仅剩 3.2GB 给 KSampler 执行 → ComfyUI 自动部分卸载模型 → 下次请求重新加载 → **每次浪费 8-15 秒**
### 关键发现
| 项目 | 值 |
|------|-----|
| GPU | RTX 5090 (32GB) |
| PyTorch | 2.7.1+cu128 (**已支持 sm_120**) |
| ComfyUI 启动参数 | `--listen 127.0.0.1 --port 8188`(无 --gpu-only |
| 当前模型 | 9B FP8 (8.8GB) |
| **4B FP8 模型已在磁盘** | **3.8GB**`diffusion_models/flux-2-klein-4b-fp8.safetensors` |
| 4B 文本编码器已在磁盘 | 7.5GB(`text_encoders/qwen_3_4b.safetensors` |
| 可用显存(ComfyUI | ~20GB32GB - 8GB hair - 3.2GB SD WebUI - 0.7GB Hair |
### 两种工作流对比
| 维度 | 0716add-hair-api.json (接口2女) | add_hair.json (接口2男/接口3) |
|------|------|------|
| 节点数 | 27 | 29 |
| 模型 | flux-2-klein-9b-fp8 | flux-2-klein-9b-fp8 (相同) |
| 文本编码器 | qwen_3_8b_fp8mixed | qwen_3_8b_fp8mixed (相同) |
| VAE | flux2-vae | flux2-vae (相同) |
| KSampler steps | **4** | **6** |
| 输出文件名 | hair_inpaint_XXXXX | ComfyUI_XXXXX |
**两个工作流加载完全相同的模型,仅 steps 不同。模型换出的根因是 ComfyUI 的默认卸载策略,不是工作流切换。**
---
## 方案 A--gpu-only + 统一 steps=4(最小改动)
### 思路
给 ComfyUI 加 `--gpu-only` 标志,禁止模型在 GPU↔CPU 之间转移。同时统一两个工作流的 steps 为 4,减少推理时间。
### 具体改动
#### A1. ComfyUI 启动加 --gpu-only
```bash
# 修改 ComfyUI 启动命令
python main.py --listen 127.0.0.1 --port 8188 --gpu-only
```
`--gpu-only` 让 ComfyUI 启动后将所有模型权重保留在 GPU 显存中,不自动卸载到 CPU。
#### A2. 统一 add_hair.json 的 steps 为 4
```json
// add_hair.json 节点 9 (SamplerCustomAdvanced)
// 将 steps 从 6 改为 4
```
#### A3. 显存验证
| 组件 | 显存 |
|------|------|
| 9B FP8 模型 | 8.8 GB |
| 文本编码器 (8B FP8) | 7.7 GB |
| VAE | 0.3 GB |
| KSampler 执行 | ~3-4 GB |
| **ComfyUI 总需求** | **~20-21 GB** |
| **可用** | **~20 GB** |
| **余量** | **-1 ~ 0 GB** ⚠ |
**风险**9B 模型 + --gpu-only 可能刚好 OOM。需要实测。
### 预期耗时
| 接口 | 当前 | 方案A | 达标 |
|------|------|-------|------|
| 接口2-女 | 17.6s | ~11sswapHair 4.6s + ComfyUI 4s + 预处理 1.5s + 余量 1s | ❌ 差1s |
| 接口2-男 | 8.1s | ~6sComfyUI 4s + 预处理 2s | ✅ |
| 接口3 | 22.9s | ~6sComfyUI 4s + 预处理 1.6s | ✅ |
### 优点
- 改动极小(1行启动参数 + 1个JSON值)
- 不换模型,质量不变
- 可快速验证
### 缺点
- **9B 模型可能 OOM**(仅 0-1GB 余量)
- 接口2女仍 ~11sswapHair 是硬瓶颈)
- `--gpu-only` 在显存不足时可能导致 ComfyUI 崩溃
---
## 方案 B:换 4B FP8 模型 + --gpu-only(推荐)
### 思路
9B FP88.8GB)换成 4B FP83.8GB),省 5GB 显存。模型+编码器从 16.8GB 降到 11.6GBKSampler 有 8.4GB 余量,彻底消除模型换出。4B 模型同样用 4 步推理,速度相当或更快。
### 磁盘上已有模型
```
ComfyUI/models/diffusion_models/flux-2-klein-4b-fp8.safetensors 3.8 GB ✅已存在
ComfyUI/models/text_encoders/qwen_3_4b.safetensors 7.5 GB ✅已存在
ComfyUI/models/vae/flux2-vae.safetensors 0.3 GB ✅已存在
```
**无需下载任何新文件!**
### 具体改动
#### B1. 修改两个工作流的模型加载节点
```python
# 0716add-hair-api.json 和 add_hair.json
# 节点 16 (UNETLoader): unet_name
"flux2.0/flux-2-klein-9b-fp8.safetensors"
"flux-2-klein-4b-fp8.safetensors"
# 节点 61 (CLIPLoader): clip_name
"qwen_3_8b_fp8mixed.safetensors"
"qwen_3_4b.safetensors"
```
#### B2. ComfyUI 启动加 --gpu-only
```bash
python main.py --listen 127.0.0.1 --port 8188 --gpu-only
```
#### B3. 统一 steps 为 4
add_hair.json 的 steps 从 6 改为 4。
#### B4. 显存验证
| 组件 | 显存 |
|------|------|
| 4B FP8 模型 | 3.8 GB |
| 文本编码器 (4B BF16) | 7.5 GB |
| VAE | 0.3 GB |
| KSampler 执行 | ~3-4 GB |
| **ComfyUI 总需求** | **~15-16 GB** |
| **可用** | **~20 GB** |
| **余量** | **+4-5 GB** ✅ 安全 |
### 预期耗时
| 接口 | 当前 | 方案B | 达标 |
|------|------|-------|------|
| 接口2-女 | 17.6s | ~11sswapHair 4.6s + ComfyUI 3s + 预处理 1.5s + 余量 2s | ❌ 差1s |
| 接口2-男 | 8.1s | ~5sComfyUI 3s + 预处理 2s | ✅ |
| 接口3 | 22.9s | ~5sComfyUI 3s + 预处理 1.6s | ✅ |
### 4B vs 9B 质量差异
根据公开资料:
- 4B 模型设计目标是"在消费级 GPU 上实现实时生成"
- 4 步推理,与 9B 相同
- 4B 是 Apache 2.0 开源,9B 是非商用许可
- 质量差异:4B 在复杂场景和细节上略逊于 9B,但对头发补全这类局部重绘任务影响可能较小
- **需要实际对比测试验证**
### 优点
- 模型已在磁盘,无需下载
- 显存余量充足(+4-5GB),不会 OOM
- 4B 模型推理速度更快
- 可彻底消除模型换出
- 不影响其他服务
### 缺点
- 需要质量验证(4B vs 9B
- 需修改两个工作流 JSON
- 接口2女仍 ~11sswapHair 是硬瓶颈)
---
## 方案 C4B FP8 + --gpu-only + SegFormer→GPU + steps=2(激进优化)
### 思路
在方案B基础上进一步优化:
1. SegFormer 移到 GPUtorch 已是 cu128,支持 5090
2. steps 降到 2(需质量验证)
3. swapHair 与预处理并行
### 具体改动
#### C1-C3:同方案B
#### C4. SegFormer 移到 GPU
```python
# hairline/face_parsing.py 或环境变量
# 当前 SegFormer 跑 CPU~0.9s/张)
# torch 2.7.1+cu128 已支持 RTX 5090 (sm_120)
export SEG_DEVICE=cuda
# 预期:0.9s → 0.05s
```
#### C5. steps 降到 2
```json
// add_hair.json + 0716add-hair-api.json
// KSampler steps: 4 → 2
```
Flux-2 Klein 是 step-distilled 模型,设计为 4 步。降到 2 步会有质量损失,需验证。
#### C6. swapHair 与预处理并行
```
当前流程:
预处理(1.5s) → swapHair(4.6s) → ComfyUI(3s) = 9.1s
优化后(并行):
预处理(1.5s) ─┐
├→ ComfyUI(3s) = 6.1s
swapHair(4.6s) ┘
```
### 显存验证
| 组件 | 显存 |
|------|------|
| 4B FP8 模型 | 3.8 GB |
| 文本编码器 (4B) | 7.5 GB |
| VAE | 0.3 GB |
| SegFormer (GPU) | ~0.5 GB |
| KSampler 执行 | ~2-3 GBsteps=2 更少) |
| **ComfyUI 总需求** | **~14-15 GB** |
| **可用** | **~20 GB** |
| **余量** | **+5-6 GB** ✅ |
### 预期耗时
| 接口 | 当前 | 方案C | 达标 |
|------|------|-------|------|
| 接口2-女 | 17.6s | ~7sswapHair 4.6s ∥ 预处理 0.6s + ComfyUI 2s | ✅ |
| 接口2-男 | 8.1s | ~4sComfyUI 2s + 预处理 1s | ✅ |
| 接口3 | 22.9s | ~4sComfyUI 2s + 预处理 0.7s | ✅ |
### 优点
- 所有接口达标(<10s
- SegFormer GPU 化(torch cu128 已支持)
- swapHair 并行化进一步优化接口2女
### 缺点
- steps=2 质量需验证
- SegFormer GPU 需修改代码
- swapHair 并行需重构 service.py
- 改动量最大
---
## 三方案对比
| 维度 | A (--gpu-only+steps=4) | B (4B FP8+--gpu-only) | C (4B+GPU SegFormer+steps=2) |
|------|----------------------|----------------------|------------------------------|
| 改动量 | 极小 | 小 | 中 |
| 需下载 | 无 | 无 | 无 |
| 接口2-女 | ~11s ❌ | ~11s ❌ | ~7s ✅ |
| 接口2-男 | ~6s ✅ | ~5s ✅ | ~4s ✅ |
| 接口3 | ~6s ✅ | ~5s ✅ | ~4s ✅ |
| 显存安全 | ⚠ 紧张(0-1GB) | ✅ 安全(+4GB) | ✅ 宽裕(+5GB) |
| 质量风险 | 无 | 中(4B vs 9B | 高(4B+steps=2 |
| OOM 风险 | 高 | 低 | 极低 |
---
## 建议
### 第一阶段:先做方案B
1. 修改两个工作流 JSON:换 4B FP8 模型 + 4B 文本编码器
2. ComfyUI 加 `--gpu-only`
3. 统一 steps=4
4. 跑一轮质量对比(4B vs 9B 的生发效果)
5. 跑轮换测试验证耗时
**如果接口3和接口2男达标(<10s),接口2女接近(~11s),则进入第二阶段。**
### 第二阶段:方案C 追加优化(解决接口2女)
6. SegFormer 移到 GPU(环境变量 SEG_DEVICE=cuda
7. swapHair 与预处理并行化
8. 如果质量允许,steps 降到 2-3
### 需要你确认
1. **是否先用方案B4B FP8 + --gpu-only)做一轮测试?**
2. **4B 模型质量是否需要先对比验证,还是可以直接切换后看效果?**
3. **方案C 的 steps=2 是否可以接受(需要质量验证)?**