Files
face-lora-qwen-image/docs/云端训练完整操作文档.md
T

312 lines
17 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 脸部 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 是死代码**:训练走云端,本地预缓存脚本无意义(曾误花时间修它)
### 成果对比建议
- myface_v7.safetensorsfinal)为主力
- 与 myface_lora-000100v3 用户最满意版)对比:重点看**手套**是否正常(本次补了 3 张白色长手套标注)