17 KiB
脸部 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 5090(Blackwell sm_120)不可用——镜像 PyTorch 2.4 只支持到 sm_90,VAE 预缓存 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/89(PyTorch 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.8MB,32 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 存 checkpoint(12 个) |
| 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 创建 pod(RunPod REST API)
$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 等待就绪 + 拿连接信息
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 手动执行一次):
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 个文件 + 解压 + 启动
# 本地 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)
# 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
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,用户睡觉期间)
- 23:59 创建 pod
f0fhlwz7rrhma6(4090 Secure $0.74/h) - 00:02 用户 Web Terminal 修 key → SSH 连上 → 上传 → bootstrap
- 00:10 训练启动(2400 步,3.3s/步,预计 2.2h)
- 00:23 用户睡觉。我启动 monitor_v2.ps1(SSH 轮询)
- 00:26 训练正常:261/2400,loss 0.0601
- 07:54 用户醒来质问:"你在干什么?"
- SSH 连不上(banner exchange refused)
- pod 没了(0 pods)→ 余额耗尽,pod 被 RunPod 强制终止
- monitor 日志 steps=? 一直解析失败(bug)→ 没检测到余额耗尽,没抢救
- 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.py(SenseNova)用 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 分钟才定位)
- PowerShell 变量名不区分大小写!
$STATE(路径)会被$state(hashtable)覆盖 → 状态从未真正持久化 → 超时止损/崩溃检测/已下载清单全部静默失效。路径变量必须用独特名(如$STATEFILE)。 - 中文路径下 Add-Content 写新 .json 文件不可靠(Test-Path 返回 True 但文件实际不存在)→ 输出目录用纯 ASCII,状态用纯文本 .txt。
- train_env.json 读取失败必须退出(曾因 $PROJ 未定义导致配置全空 → TIMEOUT: 0h>budget 差点误删 pod)。已加安全阀:关键字段为空即 exit。
- 脚本必须存 GBK 编码(PowerShell 5.1 用 ANSI 读),且不能含非 GBK 字符(⚠️ \u26a0 等会崩)。
- Event 日志(train_events.log)记录关键动作(下载/完成/止损/删pod),未来 session 恢复上下文的依据。
- ⚠️ 完成判定必须用 final 文件(myface_lora.safetensors)作为信号,不能等 e120!训练在 e110 后直接存 final,没有 e120 checkpoint。本次 monitor 用
hasE120 && hasFinal判定 → 永远不触发 → pod 多烧 12 小时 ~$9(血泪教训,2026-08-09)。正确:if ($hasFinal)即完成。 - ⚠️ checkpoint 文件名是 6 位零填充
{0:D6}(myface_lora-000010.safetensors),不是000$e(00010)——否则探测全部 404 下载不到。 - ⚠️ 下载循环每轮只下 1 个 + 跳过本地已完整 + aria2c
--continue:否则串行下多个 1.18GB 超时导致 persist 来不及执行、每轮重复下载。
需要准备 train_env.json(创建 pod 后填入)
{
"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 创建 pod(4090 Secure)
- 3. 轮询拿到 IP/SSH 端口/8888 端口
- 4. 用户 Web Terminal 执行一次 §3.3 的 key 修复命令
- 5. 上传 4 文件 → 解压 → bootstrap(nohup 后台)
- 6. bootstrap 完成 → 启动 train.sh(nohup 后台)
- 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/ |
八、铁律(每次训练必须遵守)
- 只用 Secure Cloud,禁 Community(用户明确禁止)
- 创建 pod = 花钱,必须用户批准后才能执行
- 训练期间零手动干预,一切自动(用户睡觉)
- 任何新操作先查本文档,不记得就问,不猜
- 改代码必须升版本号(页面+终端可见)
- 禁止前台 sleep/轮询;长期任务后台启动
- scp 不用中文路径;下载用 HTTP 代理 + aria2c
- checkpoint 增量下载,任何时刻断线已下载的都是安全的
- 余额/时长超预算自动止损,不裸奔
- 训练完成后自动删 pod,停计费
- ⚠️ 换 GPU 前先查架构兼容性:镜像 PyTorch 2.4 支持 CUDA sm_50-90;5090(sm_120) 不可用,4090(sm_89)/A40(sm_86) 可用
- ⚠️ 预缓存验证看
_qie.safetensors(数量=素材数),不是 .npz;确认缓存齐全再启动训练,否则报 "No training items found" - 文档更新不用问用户,改完直接写;涉及关键决策(换 GPU/删 pod)才需要确认
- 每次换 pod 都要用户修 SSH key(RunPod 注册 key 是坏的 I2i//)——除非找到一次性注入正确 key 的方法
本次 v4 训练实况(2026-08-10,续炼)
| 项 | 值 |
|---|---|
| pod id | 74oru7saetzjo9(RTX 4090 Secure,0.74/h) |
| 类型 | 续炼(非从头) |
| 续炼起点 | myface_lora-000060.safetensors(v3 成果,1.1GB) |
| 数据集 | v7(32 图 + 全面重调 caption,3 张白色长手套标注) |
| 训练 | dim32/lr1e-4/50ep/32张=1600步,实测 ~3.2s/步,~1h15m |
| 预缓存 | 云端 bootstrap 内完成(Vae + TextEncoder) |
| 输出 | myface_v7(000010-000040 + final,各 1125MB) |
| 完成 | final 下载 → 自动删 pod(06:15:35),花费 ~2h ≈ .5 |
| 成果位置 | WebUI models\Lora\qwen\ivy\v7\(5 文件,全 1125MB 完整) |
v4 新增经验
- 续炼流程:本地打包 v7 dataset.zip → train.sh 加 --network_weights 指旧 checkpoint → 上传旧 checkpoint 到 /workspace/ → bootstrap 里 [3.5] 步骤自动移到 /workspace/ckpt/
- 运行新 pod 后查连接信息:RunPod API 的 untime 字段为空是正常的!连接信息在顶层字段(publicIp/portMappings/ports),别等 untime 就绪(会误判卡住)
- SSH key:本次 env.PUBLIC_KEY 显示正确 key(hmo@daily-workspace),但 SSH 仍 Permission denied → 仍需用户 Web Terminal 执行 §3.3 修复命令(指纹 7Eep5Qz)
- 训练完成无最后中间档:50ep 训练最终只有 000010-000040 + final(epoch 50 直接存 final,无 000050)——monitor 别等最后中间档,final 出现即完成(与 v3 的 e110 模式一致)
- monitor 状态残留坑:旧训练 monitor_state.txt 的 done=True 会让新监控直接跳过 → 每次新训练前必须重置状态文件
- monitor 变量名坑:不能用 $env(PS 内置 provider 前缀)当普通变量,会导致状态持久化静默失效
- 本地 run_cache.py 是死代码:训练走云端,本地预缓存脚本无意义(曾误花时间修它)
成果对比建议
- myface_v7.safetensors(final)为主力
- 与 myface_lora-000100(v3 用户最满意版)对比:重点看手套是否正常(本次补了 3 张白色长手套标注)