# 脸部 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) ```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://-8888.proxy.runpod.net/` **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.ps1(SSH 轮询) 5. **00:26** 训练正常:261/2400,loss 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 ... 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 分钟才定位) 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://-8888.proxy.runpod.net", "ssh_host": "", "ssh_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/` | --- ## 八、铁律(每次训练必须遵守) 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-90;5090(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 | 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 新增经验 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 显示正确 key(hmo@daily-workspace),但 SSH 仍 Permission denied → 仍需用户 Web Terminal 执行 §3.3 修复命令(指纹 7Eep5Qz) 4. **训练完成无最后中间档**:50ep 训练最终只有 000010-000040 + final(epoch 50 直接存 final,无 000050)——monitor 别等最后中间档,final 出现即完成(与 v3 的 e110 模式一致) 5. **monitor 状态残留坑**:旧训练 monitor_state.txt 的 done=True 会让新监控直接跳过 → 每次新训练前必须重置状态文件 6. **monitor 变量名坑**:不能用 $env(PS 内置 provider 前缀)当普通变量,会导致状态持久化静默失效 7. **本地 run_cache.py 是死代码**:训练走云端,本地预缓存脚本无意义(曾误花时间修它) ### 成果对比建议 - myface_v7.safetensors(final)为主力 - 与 myface_lora-000100(v3 用户最满意版)对比:重点看**手套**是否正常(本次补了 3 张白色长手套标注)