脸部LoRA训练项目:素材流水线 + Gradio控制台 + RunPod云端续炼方案(v7续炼完成)

This commit is contained in:
hmo
2026-08-10 10:10:34 +08:00
commit 77d0cbd0cb
29 changed files with 5898 additions and 0 deletions
+312
View File
@@ -0,0 +1,312 @@
# 脸部 LoRA 云端训练 · 完整操作文档(v2 复盘 + v3 方案)
> 建立:2026-08-09
> 性质:**训练全流程唯一权威参考**(含上次全部踩坑记录)
> 铁律:**训练期间不要手动干预;监控全自动;用户睡觉期间 AI 不执行任何未经文档确认的操作**
## 本次训练实况(v3 最终版,2026-08-09
| 项 | 值 |
|---|---|
| pod id | `k1nzrqh8lfj7jg`**RTX 4090** 24GB$0.74/h |
| IP/SSH | 103.196.86.68 / 50155 |
| 镜像 | runpod/pytorch:2.4.0-py3.11-cuda12.4.1-devel-ubuntu22.04 |
| blocks_to_swap | **8**4090 24GB,与 v2 一致) |
| 训练 | dim32/lr1e-4/120ep/32张=3840步,实测 **2.5s/步**ETA ~2.5h |
| 预缓存 | ✅ 已验证 128 文件(32 img + 32 txt + 32 VAE `_qie` + 32 TE `_qie_te` |
| 监控 | monitor_v3 + 计划任务 FaceLoRA-Monitor-v3(每5分钟) |
| 下载 | `https://k1nzrqh8lfj7jg-8888.proxy.runpod.net/`HTTP 200 已验证) |
| 预算 | max_hours 4$0.74/h × 4 = **$2.96 封顶** |
> ⚠️ **GPU 选择结论(踩坑记录,2026-08-09**
> - ❌ **RTX 5090Blackwell sm_120)不可用**——镜像 PyTorch 2.4 只支持到 sm_90VAE 预缓存 GPU 编码直接失败(`sm_120 not compatible`),浪费 55GB 下载 + 烧钱
> - ❌ **A40 可用但慢**——实测 5.33s/步(算力 ~37 TFLOPS,是 4090 一半)
> - ✅ **RTX 4090 最优**——实测 **2.5s/步**,且是 v2 完整验证过的(上次跑完 2400 步)
> - **教训**:换 GPU 前必须确认「镜像 PyTorch 的 CUDA capability ≤ GPU 架构」!5090=sm_120、4090/A40=sm_86/89PyTorch 2.4 支持 sm_50-90
> ⚠️ **预缓存验证方法(踩坑记录)**:Qwen 的缓存文件是 **`.safetensors``img_xxx_..._qie.safetensors` + `_qie_te.safetensors`),不是 `.npz`**!判断缓存成功要看 `_qie.safetensors` 数量 = 素材数,别查 .npz(会误判为 0 导致误删/重复折腾)。
---
## 一、项目目标与当前状态
**目标**:用老莫(用户)老婆的照片训练脸部 LoRA,叠加 `Qwen-Rapid-AIO-NSFW-v23` checkpoint 出图。
**训练底模**Qwen-Image-Edit-2511 bf16 官方底模(不能用 FP8 融合模型训练)。
**当前素材状态**2026-08-09):
- 已选 32 张(特写 21 / 半身 8 / 全身 3),无冗余(已删重复)
- 大笑 3 张(稀缺)、表情 6 种、光照多样
- 训练集已生成:`K:\AI\training\ldf\singled\5. autohandling_v6\train_dataset\`img_001..032 + txt
- dataset.zip 已打包:`cloud/dataset.zip`20.8MB32 img + 32 txt = 64 文件)
- 余额:**$10 已充值**(预计本次花费 $3-4,足够)
---
## 二、训练参数(最终定稿)
| 参数 | 值 | 说明 |
|---|---|---|
| network_dim | 32 | v1(dim16) 验证"不像"→ 调大;v2 未验证效果 |
| learning_rate | 1e-4 | 与 dim32 配套 |
| max_train_epochs | 120 | 32 张 × 120 = **3840 步** |
| blocks_to_swap | 8 | 4090 24GB + 62GB RAM 够用 |
| fp8_base/scaled/vl | on | 省显存 |
| seed | 42 | 可复现 |
| save_every_n_epochs | 10 | 每 10 epoch 存 checkpoint12 个) |
| sample_every_n_epochs | 10 | 每 10 epoch 出 4 张样本图 |
| 分辨率 | 1024 | |
**云端 GPU****RTX 4090 Secure Cloud $0.74/h**A40 无容量,最后用 4090 成功)
**预计时长**3840 步 × ~3.3s/步 ≈ **3.5-4 小时**,花费 **~$3**
---
## 三、云端 pod 全流程(v2 实测验证,脚本化)
### 3.1 创建 podRunPod REST API
```powershell
$proj = "D:\F\NewI\opencode\daily-workspace\projects\脸部LoRA训练-Qwen-Image"
$key = Get-Content "$proj\.runpod_api_key"
$body = @{
name = "face-lora-train-v3"
imageName = "runpod/pytorch:2.4.0-py3.11-cuda12.4.1-devel-ubuntu22.04"
gpuTypeIds = @("NVIDIA GeForce RTX 4090")
gpuCount = 1
cloudType = "SECURE" # 铁律:只用 Secure,禁 Community
containerDiskInGb = 100
volumeInGb = 0
} | ConvertTo-Json
$r = Invoke-RestMethod -Uri "https://rest.runpod.io/v1/pods" -Method Post `
-Headers @{Authorization = "Bearer $key"; "Content-Type" = "application/json"} -Body $body -TimeoutSec 60
"pod id: $($r.id) | cost/h: $($r.costPerHr)"
```
**关键点**
- **API key 位置**`$proj\.runpod_api_key``C:\Users\hmo\.runpod_api_key` 不存在,别用错)
- **A40 曾无容量**stuck provisioning)→ 4090 是最终验证可行的
- 创建后轮询 pod 状态直到 `runtime` 非空(拿到 IP + SSH 端口 + Jupyter 端口 + 密码)
### 3.2 等待就绪 + 拿连接信息
```powershell
Start-Sleep 60 # 后台等待(铁律:禁止前台 sleep 轮询,分两步)
$r = Invoke-RestMethod -Uri "https://rest.runpod.io/v1/pods/$PODID" -Headers @{Authorization = "Bearer $key"} -TimeoutSec 30
if ($r.runtime) {
"publicIp: $($r.runtime.publicIp)"
$r.runtime.ports | ForEach-Object { "private:$($_.privatePort) -> public:$($_.publicPort)" }
"jupyterPass: $($r.runtime.jupyterPassword)"
}
```
### 3.3 ⚠️ SSH key 修复(每次新 pod 必做,最大的坑)
**背景**RunPod Settings 里注册的 key 是错误的(指纹 `I2i//`,与本地私钥 `7Eep5Qz` 不匹配)。新 pod 的 `PUBLIC_KEY` 环境变量注入的是错误 key → SSH 永远 Permission denied。
**修复**(需用户在 RunPod Web Terminal 手动执行一次):
```bash
curl -sk -u xiaoxiao:fXmRReiHMnY3AVB "https://git.yoin.fun/api/v1/repos/xiaoxiao/face-lora-qwen-image/raw/cloud/correct_key.pub" -o /root/.ssh/authorized_keys && chmod 600 /root/.ssh/authorized_keys && service ssh restart && ssh-keygen -lf /root/.ssh/authorized_keys
```
成功标志:指纹显示 `7Eep5QzBfPDzvTgHfNMOC5TPOmW+zSbhHYLctKmkX5Y`(与本地 `id_rsa.pub` 一致)。
**正确 key 已存**`cloud/correct_key.pub`Gitea 仓库里也有,可 curl 拉取)。
### 3.4 上传 4 个文件 + 解压 + 启动
```bash
# 本地 scp 上传(注意:目标路径含中文会坑,用 ASCII 中转或分步)
scp -i $KEY -P $PORT cloud/dataset.zip cloud/bootstrap.sh cloud/train.sh cloud/sample_prompts.txt root@$IP:/workspace/
# SSH 进 pod 后:
cd /workspace
unzip dataset.zip -d train_dataset # pod 上可能没有 unzip → 用 python -m zipfile -e 代替
bash bootstrap.sh # 装 musubi + 下 55G 模型 + 预缓存,10-15 分钟
nohup bash train.sh > /workspace/train.log 2>&1 & # 后台训练
```
**⚠️ SSH 后台任务坑**`nohup ... &` 直接跑会因 SSH session 挂起。正确:`setsid nohup bash train.sh > /workspace/train.log 2>&1 < /dev/null &`(三流全重定向)。
### 3.5 checkpoint 下载(HTTP 代理,不用 scp
```bash
# pod 上启动 http.server(替代 Jupyter 占用的 8888
python3 -m http.server 8888 --directory /workspace/ckpt
# 本地 aria2c 多线程下载(验证过:aria2c 1.37.0 可用)
aria2c -x16 -s16 -k1M -d "local_dir" -o "myface_lora-000050.safetensors" "https://$PODID-8888.proxy.runpod.net/myface_lora-000050.safetensors"
```
**下载 URL 格式**`https://<podid>-8888.proxy.runpod.net/<filename>`
**checkpoint 大小**~563MB 每个(v2 实测)
**校验**:完整文件 = 590153736 字节(562.8MB),小于此值 = 不完整,删了重下。
### 3.6 收尾:删除 pod
```powershell
Invoke-RestMethod -Uri "https://rest.runpod.io/v1/pods/$PODID" -Method Delete -Headers @{Authorization = "Bearer $key"} -TimeoutSec 30
```
---
## 四、⚠️ 上次 v2 训练完整翻车复盘(必须吸取)
### 时间线(8/6 23:00 - 8/7 07:55,用户睡觉期间)
1. **23:59** 创建 pod `f0fhlwz7rrhma6`4090 Secure $0.74/h
2. **00:02** 用户 Web Terminal 修 key → SSH 连上 → 上传 → bootstrap
3. **00:10** 训练启动(2400 步,3.3s/步,预计 2.2h
4. **00:23** 用户睡觉。我启动 monitor_v2.ps1SSH 轮询)
5. **00:26** 训练正常:261/2400loss 0.0601
6. **07:54** 用户醒来质问:"你在干什么?"
- **SSH 连不上**banner exchange refused
- **pod 没了**0 pods)→ **余额耗尽,pod 被 RunPod 强制终止**
- monitor 日志 **steps=? 一直解析失败**(bug)→ 没检测到余额耗尽,没抢救
7. **08:00** 确认:**训练其实完整跑完了**samples 48 张 = e010-e120 全有)!但 **checkpoint 一个没下载**monitor 只等 e120 后下载,且下载逻辑没触发)
### 根因(三条,全部是 monitor 脚本 bug
| # | Bug | 后果 |
|---|---|---|
| 1 | `tail -2` 抓 tqdm 进度 → 正则失败,steps 永远 `?` | 进度不可见,异常检测失效 |
| 2 | 下载只在 e120 出现后一次性执行,且用 scp(中文路径坑) | 中途不下载,崩溃时抢救也失败 |
| 3 | **无余额监控** | 余额耗尽 pod 被删,checkpoint 全丢 |
### 其他踩坑(8/7 全天)
- **scp 目标路径含中文**`projects\脸部LoRA训练-Qwen-Image`)→ 文件写入失败。解决:用 HTTP 代理下载
- **ssh 后台任务挂起 session**`nohup &` 不够,要 `setsid nohup ... </dev/null >log 2>&1 &`
- **image_to_text.pySenseNova)用 musubi venv python 跑**(系统 python PATH 坏了)
- **OpenCV 读不了中文路径的 ONNX 模型** → 拷贝到 %TEMP% ASCII 路径
- **GUI 每次改代码要升版本号**(页面标题 + 终端都显示,验证重启生效)
- **前后台铁律**:禁止前台 sleep/轮询;长期任务必须后台
---
## 五、v3 改进方案(本次要用,monitor_v3.ps1 已写好)
### 核心变化:从"SSH 轮询"改为"HTTP 文件轮询 + 状态持久化 + 计划任务"
| 项 | v2(失败) | v3(本次) |
|---|---|---|
| 调度 | 手动 while 循环 | **Windows 计划任务每 5 分钟** + PID 锁 |
| 进度信号 | SSH 解析 tqdm(失败) | **云端 checkpoint 文件出现**HTTP HEAD |
| 下载 | 只等 e120 一次性 | **增量下载**:每个 epoch 出现立即下 |
| 余额 | 无 | **时长止损**:超预算自动删 pod |
| 崩溃 | 依赖进度解析(失效) | checkpoint mtime 超 45 分钟未更新 → 抢救 |
| 状态 | 无 | `monitor_state.txt` 持久化(已下载清单) |
| 通知 | 无 | 只写日志(用户明确不要微信/通知) |
### 监控脚本(正式版)
**`projects/脸部LoRA训练-Qwen-Image/tools/monitor_train_v3.ps1`**(计划任务指向此文件)
-`temp/train_env.json`pod_id/dl_url/ssh_host/ssh_port/max_hours/total_steps
- PID 锁防重(计划任务重复触发安全)
- 每次运行做一轮:列云端 checkpoint → 增量下载 → 完成判定 → 崩溃判定 → 超时止损 → 写状态
- **所有输出在纯 ASCII 目录** `C:\Users\hmo\AppData\Local\Temp\opencode\face_lora\`monitor_v3.log / train_events.log / monitor_state.txt / checkpoints_v3/
### ⚠️ PS 5.1 血泪教训(2026-08-09 调试 40 分钟才定位)
1. **PowerShell 变量名不区分大小写!** `$STATE`(路径)会被 `$state`hashtable)**覆盖** → 状态从未真正持久化 → 超时止损/崩溃检测/已下载清单全部静默失效。**路径变量必须用独特名**(如 `$STATEFILE`)。
2. **中文路径下 Add-Content 写新 .json 文件不可靠**Test-Path 返回 True 但文件实际不存在)→ **输出目录用纯 ASCII,状态用纯文本 .txt**
3. **train_env.json 读取失败必须退出**(曾因 $PROJ 未定义导致配置全空 → TIMEOUT: 0h>budget 差点误删 pod)。已加安全阀:关键字段为空即 exit。
4. **脚本必须存 GBK 编码**PowerShell 5.1 用 ANSI 读),且**不能含非 GBK 字符**(⚠️ \u26a0 等会崩)。
5. **Event 日志**train_events.log)记录关键动作(下载/完成/止损/删pod),未来 session 恢复上下文的依据。
6. **⚠️ 完成判定必须用 final 文件(myface_lora.safetensors)作为信号,不能等 e120**!训练在 e110 后直接存 final**没有 e120 checkpoint**。本次 monitor 用 `hasE120 && hasFinal` 判定 → 永远不触发 → **pod 多烧 12 小时 ~$9**(血泪教训,2026-08-09)。正确:`if ($hasFinal)` 即完成。
7. **⚠️ checkpoint 文件名是 6 位零填充 `{0:D6}`**myface_lora-000010.safetensors),不是 `000$e`(00010)——否则探测全部 404 下载不到。
8. **⚠️ 下载循环每轮只下 1 个 + 跳过本地已完整 + aria2c `--continue`**:否则串行下多个 1.18GB 超时导致 persist 来不及执行、每轮重复下载。
### 需要准备 train_env.json(创建 pod 后填入)
```json
{
"pod_id": "<创建后从API拿>",
"dl_url": "https://<podid>-8888.proxy.runpod.net",
"ssh_host": "<publicIp>",
"ssh_port": <ssh public port>,
"max_hours": 4.0,
"total_steps": 3840
}
```
---
## 六、执行清单(本次训练,按顺序)
- [ ] 1. 用户确认:**批准启动云端训练**(涉及花钱 $3-4)
- [ ] 2. 用 §3.1 API 创建 pod4090 Secure
- [ ] 3. 轮询拿到 IP/SSH 端口/8888 端口
- [ ] 4. **用户 Web Terminal 执行一次** §3.3 的 key 修复命令
- [ ] 5. 上传 4 文件 → 解压 → bootstrapnohup 后台)
- [ ] 6. bootstrap 完成 → 启动 train.shnohup 后台)
- [ ] 7. pod 上启动 http.server 8888 服务 ckpt 目录
- [ ] 8. 填好 train_env.json → 注册 Windows 计划任务(每 5 分钟跑 monitor_v3.ps1
- [ ] 9. 用户睡觉。监控全自动:增量下载 checkpoint → 完成/崩溃/超时自动处理
- [ ] 10. 早上验证:本地 checkpoints_v3 目录应有一批 563MB 完整 checkpoint
- [ ] 11. checkpoint 复制到 WebUI `models\Lora\qwen\` → Rapid-AIO 出图验证
---
## 七、关键文件位置
| 文件 | 路径 |
|---|---|
| 训练集 | `K:\AI\training\ldf\singled\5. autohandling_v6\train_dataset\` |
| 数据集 zip | `cloud/dataset.zip` |
| 云端 bootstrap | `cloud/bootstrap.sh` |
| 云端训练 | `cloud/train.sh`(已加崩溃自动重启 ×20 |
| 正确 SSH key | `cloud/correct_key.pub` |
| API key | `projects\脸部LoRA训练-Qwen-Image\.runpod_api_key` |
| 监控 v3 | `temp/monitor_v3.ps1` |
| 监控配置 | `temp/train_env.json`(待创建 pod 后填) |
| 监控日志 | `temp/monitor_v3.log` |
| 本地 checkpoint | `temp/checkpoints_v3/` |
---
## 八、铁律(每次训练必须遵守)
1. **只用 Secure Cloud**,禁 Community(用户明确禁止)
2. **创建 pod = 花钱**,必须用户批准后才能执行
3. **训练期间零手动干预**,一切自动(用户睡觉)
4. **任何新操作先查本文档**,不记得就问,不猜
5. **改代码必须升版本号**(页面+终端可见)
6. **禁止前台 sleep/轮询**;长期任务后台启动
7. **scp 不用中文路径**;下载用 HTTP 代理 + aria2c
8. **checkpoint 增量下载**,任何时刻断线已下载的都是安全的
9. **余额/时长超预算自动止损**,不裸奔
10. **训练完成后自动删 pod**,停计费
11. **⚠️ 换 GPU 前先查架构兼容性**:镜像 PyTorch 2.4 支持 CUDA sm_50-905090(sm_120) 不可用,4090(sm_89)/A40(sm_86) 可用
12. **⚠️ 预缓存验证看 `_qie.safetensors`**(数量=素材数),不是 .npz;确认缓存齐全再启动训练,否则报 "No training items found"
13. **文档更新不用问用户**,改完直接写;涉及关键决策(换 GPU/删 pod)才需要确认
14. **每次换 pod 都要用户修 SSH key**RunPod 注册 key 是坏的 I2i//)——除非找到一次性注入正确 key 的方法
---
## 本次 v4 训练实况(2026-08-10,续炼)
| 项 | 值 |
|---|---|
| pod id | 74oru7saetzjo9RTX 4090 Secure0.74/h |
| 类型 | **续炼**(非从头) |
| 续炼起点 | myface_lora-000060.safetensorsv3 成果,1.1GB |
| 数据集 | v732 图 + 全面重调 caption,3 张白色长手套标注) |
| 训练 | dim32/lr1e-4/**50ep**/32张=1600步,实测 ~3.2s/步,~1h15m |
| 预缓存 | 云端 bootstrap 内完成(Vae + TextEncoder |
| 输出 | myface_v7000010-000040 + final,各 1125MB |
| 完成 | final 下载 → 自动删 pod06:15:35),花费 ~2h ≈ .5 |
| 成果位置 | WebUI models\Lora\qwen\ivy\v7\5 文件,全 1125MB 完整) |
### v4 新增经验
1. **续炼流程**:本地打包 v7 dataset.zip → train.sh 加 --network_weights 指旧 checkpoint → 上传旧 checkpoint 到 /workspace/ → bootstrap 里 [3.5] 步骤自动移到 /workspace/ckpt/
2. **运行新 pod 后查连接信息**RunPod API 的
untime 字段为空是正常的!连接信息在**顶层**字段(publicIp/portMappings/ports),别等
untime 就绪(会误判卡住)
3. **SSH key**:本次 env.PUBLIC_KEY 显示正确 keyhmo@daily-workspace),但 SSH 仍 Permission denied → 仍需用户 Web Terminal 执行 §3.3 修复命令(指纹 7Eep5Qz
4. **训练完成无最后中间档**50ep 训练最终只有 000010-000040 + finalepoch 50 直接存 final,无 000050)——monitor 别等最后中间档,final 出现即完成(与 v3 的 e110 模式一致)
5. **monitor 状态残留坑**:旧训练 monitor_state.txt 的 done=True 会让新监控直接跳过 → 每次新训练前必须重置状态文件
6. **monitor 变量名坑**:不能用 $envPS 内置 provider 前缀)当普通变量,会导致状态持久化静默失效
7. **本地 run_cache.py 是死代码**:训练走云端,本地预缓存脚本无意义(曾误花时间修它)
### 成果对比建议