Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,13 @@

- **修复 Git 更新收走模型和训练集(#356)**:停止自动 stash,自动补齐旧包缺失的程序文件;新包保留完整浅克隆工作树,并在打包前执行数据保留测试。已经受影响的用户请保留整个旧目录(含 `.git`),参见 [恢复说明](https://github.com/wochenlong/lora-scripts-next/issues/356)。

### Musubi-tuner 新增 Ideogram 4(#424)

- Musubi 引擎支持 **Ideogram 4 LoRA**:训练页新增模型入口(`/lora/ideogram4.html`),三阶段任务(缓存 latents → 缓存文本编码器输出 → 训练)与任务队列、日志、预览沿用现有能力
- 组件只发**量化权重(FP8)**、许可为 **非商用**,整合包不预置;「训练用模型」区提供 conditional / unconditional DiT、Qwen3-VL-8B 文本编码器、Flux2 VAE 与 tokenizer 的清单与下载入口
- 上游快照补丁:修正文本编码器 rotary embedding 在 `to_empty()` 后未初始化的问题(上游 v0.3.6 修复),并把 tokenizer 指向本地目录,离线可用
- 引擎能力、预检与配置导入识别 `ideogram4-lora`(跨页导入自动跳转到对应训练页),详见 [Ideogram 4 训练指南](docs/ideogram4-musubi.md)

### 数据集标签编辑(#423)

- **编辑页重做**:默认只展示图片墙,选中图片后才展开编辑面板
Expand Down
2 changes: 1 addition & 1 deletion README-zh.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ Next Trainer 把数据集管理、打标、标签编辑、训练配置与任务
| --- | --- | --- |
| Kohya | SD 1.5、SDXL、Flux、Anima | 支持范围随模型而异;已接入独立环境管理 |
| Anima Fast | Anima 2B / 2.9B LoRA | 独立运行环境;两种规格有明确的底模路径入口 |
| Musubi | Krea 2 LoRA | 可选安装;Linux 多卡见专项指南 |
| Musubi | Krea 2 / Ideogram 4 LoRA | 可选安装;Ideogram 4 为非商用许可的量化权重,需自行下载;Linux 多卡见专项指南 |
| DiffSynth | Qwen-Image-2.1 文生图 / 图像编辑 LoRA | 当前为单卡 BF16;Edit 使用 Batch 1 |
| AI Toolkit | SDXL、Flux.1 Dev、Klein 4B / 9B、Krea 2 RAW、Anima、Qwen-Image-2.1 LoRA | 可选安装、独立环境;Klein 支持 base / distilled,Klein 与 Qwen 提供图像编辑入口 |

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ Settings are organised as **UI, Training Engines, API, Plugins, Advanced** and *
| --- | --- | --- |
| Kohya | SD 1.5, SDXL, Flux, Anima | Capabilities vary by model; standalone environment management is integrated |
| Anima Fast | Anima 2B / 2.9B LoRA | Isolated runtime with explicit base-model path selection |
| Musubi | Krea 2 LoRA | Optional installation; see the Linux multi-GPU guide |
| Musubi | Krea 2 / Ideogram 4 LoRA | Optional installation; Ideogram 4 ships non-commercial quantized weights you fetch yourself; see the Linux multi-GPU guide |
| DiffSynth | Qwen-Image-2.1 text-to-image / image-editing LoRA | Single-GPU BF16; Edit currently uses batch size 1 |
| AI Toolkit | SDXL, Flux.1 Dev, Klein 4B / 9B, Krea 2 RAW, Anima, Qwen-Image-2.1 LoRA | Optional installation with an isolated runtime; Klein supports base / distilled variants, and Klein and Qwen offer image-editing entry points |

Expand Down
8 changes: 8 additions & 0 deletions docs/dev-progress.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

| 方向 | 已集成内容 | 边界 |
| --- | --- | --- |
| Musubi / Ideogram 4(#424) | Musubi 引擎新增 Ideogram 4 LoRA:训练页模型入口、三阶段缓存与训练、条件化/非条件化 DiT 与采样参数、配置导入识别;本仓补丁修正上游文本编码器 rotary 未初始化问题并支持本地 tokenizer | 官方仅发 FP8 量化权重且为**非商用**许可,整合包不预置,需自行下载;LoRA 不能合并回 FP8 基座;上游为 experimental,真机验收见 #424 |
| 标签编辑与中文释义(#423) | 编辑页重做(图片墙优先、标签/自由两种编辑模式、批量操作与历史记录);内置 Danbooru 中文词典(约 33 万条),翻译来源为词库 / 本地模型 / API;翻译设置与 API 配置进入全局设置 | 词库可完全离线;本地模型与 API 来源依赖用户自行安装或配置;全局 API 配置的存储统一仍在推进,打标页当前只选择来源 |
| AI Toolkit(#399 及验收修复) | 独立环境、多模型 LoRA 入口、本地模型组件、文生图与图像编辑、统一任务和预览 | Qwen-Image-2.1 两种模式各通过 3 步真机冒烟及 20 步预览采样;不等于全模型、浏览器全流程或长期稳定性验收 |
| 引擎列表(#398) | 搜索、安装状态筛选、置顶与排序 | 顺序按浏览器及访问地址保存,不同步到其他设备,也不改变默认训练引擎 |
Expand Down Expand Up @@ -55,6 +56,13 @@ AI Toolkit 本轮修复包括 GPU 显示名称转设备编号、Windows 中文

Anima 底模选择适用于 Kohya LoRA、Anima Fast LoRA 和 Anima 全量微调。Anima Fast 另有明确的训练时长模式及验证集拆分配置;引擎参数与限制见 [Anima Fast 指南](anima-fast.md)。

### Musubi / Ideogram 4

- Musubi 引擎的训练类型从 Krea 2 扩展为 Krea 2 / Ideogram 4 两个模型族:训练页模型入口、三阶段缓存与训练、模型族专属参数与约束(量化-only 基座、非对称 CFG 采样开关、blocks_to_swap 上限 33)。
- 组件只发 FP8 量化权重(非商用许可),整合包不预置;「训练用模型」区提供清单与下载入口,tokenizer 建议本地放置以支持离线。
- 本仓补丁修正上游快照中文本编码器 rotary embedding 未初始化的问题(上游 v0.3.6 修复);文档见 [Ideogram 4 训练指南](ideogram4-musubi.md)。
- 真机验收(3 步冒烟、缓存复用、显存实测)在 [#424](https://github.com/wochenlong/lora-scripts-next/issues/424) 跟踪,不代表已完成。

### DiffSynth / Qwen-Image-2.1

- 支持完整模型目录或受支持的 BF16 DiT、文本编码器与 VAE 组件,Processor 资源自动管理。
Expand Down
79 changes: 79 additions & 0 deletions docs/ideogram4-musubi.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
# Ideogram 4(Musubi-Tuner)训练指南

Ideogram 4 通过 **Musubi-Tuner** 引擎接入,训练入口:训练页选择 **模型 = Ideogram 4,引擎 = Musubi-Tuner,目标 = LoRA**,或直接访问 `/lora/ideogram4.html`。

当前支持 **LoRA 训练**;上游尚未支持全量微调,也 **不支持把 LoRA 合并回 FP8 底模**。

## 许可与权重(务必先读)

- 模型许可为 **Ideogram Non-Commercial**:仅限非商业用途,微调同样受此限制;禁止使用其输出训练与 Ideogram 竞争的模型。
- 官方仅发布 **量化权重(FP8 / NVFP4)**,没有 BF16/FP16 底模。DiT 始终以预量化 FP8 加载作为冻结基座,LoRA 以计算精度运行——这是唯一的工作模式,不是可选项。
- 本项目的整合包 **不预置这些权重**。请在「训练用模型」区下载或手动放置,组件取自 Comfy-Org 单文件布局:

| 组件 | 文件 |
| --- | --- |
| conditional DiT | `ideogram4_fp8_scaled.safetensors` |
| unconditional DiT(非对称 CFG,可选) | `ideogram4_unconditional_fp8_scaled.safetensors` |
| 文本编码器(Qwen3-VL-8B,FP8) | `qwen3vl_8b_fp8_scaled.safetensors` |
| VAE(Flux2) | `flux2-vae.safetensors` |
| tokenizer(Qwen3-VL-8B) | 目录,建议本地放置 |

默认路径:`sd-models/ideogram4/`,tokenizer 目录为 `sd-models/ideogram4/qwen3-vl-8b-tokenizer`。

### 需要多少磁盘空间

官方只发量化权重,**有 FP8 版本,而且 FP8 就是我们默认使用、也是 musubi 训练脚本唯一支持的加载格式**。Comfy-Org 镜像上主要的文件体积(2026-10 实测):

| 文件 | 体积 | 是否必需 |
| --- | --- | --- |
| `ideogram4_fp8_scaled.safetensors`(conditional DiT) | 8.64 GB | 必需 |
| `qwen3vl_8b_fp8_scaled.safetensors`(Qwen3-VL-8B 文本编码器) | 9.86 GB | 必需 |
| `flux2-vae.safetensors` | 0.31 GB | 必需 |
| tokenizer(Qwen3-VL-8B 目录) | 约 0.02 GB | 必需 |
| `ideogram4_unconditional_fp8_scaled.safetensors` | 8.64 GB | 可选(非对称 CFG 推理 / 显式开启的训练采样) |

镜像里还有 **NVFP4**(DiT 5.11 GB + 文本编码器 5.87 GB)与 **int8_convrot**(8.93 GB)变体,体积更小;我们的默认资源清单与上游文档都按 FP8 单文件路径配置,其它量化格式 **未做过验证**。

配套空间按需预留:

- **权重最低要求**:约 **18.8 GB**(必需四项);需要无条件 DiT 再 +8.6 GB(合计约 27.5 GB)。
- **文本缓存**:每 token 存 53248 通道——BF16 下 128 token ≈ 13.6 MB/图,1000 张约 **13.6 GB**;`text_cache_dtype=fp8_e4m3fn` 可减半。
- **latent 缓存**:每图 MB 级,可忽略。
- **引擎环境**:若本机还没装 musubi-tuner(含 torch 等),安装另需约 **8–12 GB**。

**建议预留 60–80 GB**(权重 + 一份缓存 + 引擎与产物余量);已有 musubi 环境且数据集只有几百张图时,40 GB 也够。下载期间会同时在目标目录写入一份文件,不要贴着剩余空间下限操作。


## 运行流程与显存

训练按三阶段任务组执行:**缓存 latents → 缓存文本编码器输出 → 训练**。缓存阶段固定使用第一张 GPU。

- **文本缓存很大**:每个 token 存 53248 通道,BF16 下 128 token ≈ 13.6 MB/图(1k 图 ≈ 13.6 GB),256 token 翻倍,512 token ≈ 54.5 GB。可把 `text_cache_dtype` 设为 `fp8_e4m3fn` 减半,但属于精度权衡。
- 基座为 9.3B FP8 DiT + Qwen3-VL-8B 文本编码器。显存不足时按顺序尝试:`blocks_to_swap`(上限 **33**)、`gradient_checkpointing`、关闭训练中采样预览。
- 预检会在显存低于 16 GB 时提示,并检查已安装的 musubi-tuner 快照是否包含 Ideogram 4 脚本;缺失时请在「设置 → 训练引擎」修复/重装。

## 参数要点

- **时间步采样**:默认 `ideogram4_shift`(官方对齐的分辨率感知采样器),通常无需修改。上游另有 `ideogram4_timestep_mu/std` 两个兼容参数,但明确忽略其取值,界面不提供。
- **损失**:纯 MSE flow matching,`weighting_scheme` 只能是 `none`(界面不提供该选项,导入其它值时会被忽略并提示)。
- **caption**:官方用结构化 JSON(`high_level_description` / `style_description` / `compositional_deconstruction`)。纯文本也能训练;开启 `validate_caption_structure` 会校验结构,配合 `warn_on_caption_issues` 可只警告不中断。
- **LoRA 目标**:仅训练 conditional transformer(`attention.qkv`、`attention.o`、`feed_forward.w1/w2/w3`)。
- **注意力**:`sdpa` / `flash_attn` / `xformers` 可用;**SageAttention 不可用**(head 维度 256 超出其 int8/fp8 核支持范围)。
- 精度:`mixed_precision` 仅 bf16;`dit_dtype` 默认 bfloat16(底模始终 FP8 加载)。

## 训练中采样

- 需要同时给出 `text_encoder` 与 `vae` 路径。
- 采样调度由 `sampler_preset` 决定:`V4_DEFAULT_20`(18+2 步,默认)、`V4_QUALITY_48`(45+3 步)、`V4_TURBO_12`(11+1 步);`initial_sigma` 默认 1.004。
- 默认只用 conditional DiT 采样(LoRA 只挂在 conditional DiT 上)。若要使用官方非对称 CFG,需要提供 unconditional DiT 并开启对应开关。
- 官方结构化 JSON prompt 也可用于采样,界面默认给出一个三键示例。

## 本仓的上游补丁

我们固定的上游快照(2026-08-13)中,`ideogram4_utils.py` 的文本编码器加载在 `to_empty()` 之后 **没有重建 rotary embedding**,非持久化的 `inv_freq` 缓冲会保持未初始化,导致文本缓存是垃圾值。上游在 v0.3.6 修复。本仓不升级 pin,而是在启动训练前对该文件打等价补丁(幂等、写入前做语法校验),同时把 tokenizer 指向本地目录以支持离线运行。

## 已知限制

- 上游标注该模型支持为 **experimental**;上游 issue 报告过文本编码器预缓存阶段 OOM 与 resume 变慢,长训练前请先小步验证。
- 只能 LoRA;LoRA 不能合并回 FP8 权重,生成时通过 forward hook 加载,结果可能与把 LoRA 合并进权重的工具(如 ComfyUI)不同。
- 真机验收(3 步冒烟 + 缓存复用)在 [#424](https://github.com/wochenlong/lora-scripts-next/issues/424) 跟踪。
2 changes: 1 addition & 1 deletion frontend/src/engines/catalog.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ export const ENGINE_CATALOG: readonly EngineDefinition[] = [
requiresGpu: true,
managesRuntime: true,
mark: "M",
tags: ["lora", "krea2", "nvidia"],
tags: ["lora", "krea2", "ideogram4", "nvidia"],
version: "plugin",
updatedAt: "2026-08",
},
Expand Down
5 changes: 4 additions & 1 deletion frontend/src/i18n/messages/en-US.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ export default {
flux: "Flux",
lumina: "Lumina 2",
krea2: "Krea 2",
ideogram4: "Ideogram 4",
klein: "Klein (FLUX.2)",
"qwen-image-21": "Qwen-Image-2.1",
},
Expand Down Expand Up @@ -208,6 +209,7 @@ export default {
"flux-lora": { title: "Flux LoRA", area: "Flux · Kohya-ss · LoRA" },
"lumina2-lora": { title: "Lumina 2 LoRA", area: "Lumina 2 · Kohya-ss · LoRA" },
"krea2-lora": { title: "Krea 2 LoRA", area: "Krea 2 · Musubi-Tuner · LoRA" },
"ideogram4-lora": { title: "Ideogram 4 LoRA", area: "Ideogram 4 · Musubi-Tuner · LoRA" },
"qwen-image-21-lora": { title: "Qwen-Image-2.1 LoRA", area: "Qwen-Image-2.1 · DiffSynth-Studio · LoRA" },
"ai-toolkit-sdxl-lora": { title: "SDXL LoRA", area: "SDXL · AI Toolkit · LoRA" },
"ai-toolkit-flux-lora": { title: "Flux LoRA", area: "Flux · AI Toolkit · LoRA" },
Expand Down Expand Up @@ -711,6 +713,7 @@ export default {
flux: "Flux",
anima: "Anima",
krea2: "Krea 2",
ideogram4: "Ideogram 4",
klein: "Klein",
"qwen-image-21": "Qwen-Image-2.1",
nvidia: "NVIDIA",
Expand Down Expand Up @@ -806,7 +809,7 @@ export default {
},
musubi: {
name: "Musubi-Tuner",
summary: "Optional engine for Krea 2 and other image-model LoRA.",
summary: "Optional engine for Krea 2, Ideogram 4 and other image-model LoRA.",
sizeHint: "Large download (usually several GB); extra disk space and download time required",
},
"ai-toolkit": {
Expand Down
5 changes: 4 additions & 1 deletion frontend/src/i18n/messages/zh-CN.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ export default {
flux: "Flux",
lumina: "Lumina 2",
krea2: "Krea 2",
ideogram4: "Ideogram 4",
klein: "Klein (FLUX.2)",
"qwen-image-21": "Qwen-Image-2.1",
},
Expand Down Expand Up @@ -208,6 +209,7 @@ export default {
"flux-lora": { title: "Flux LoRA", area: "Flux · Kohya-ss · LoRA" },
"lumina2-lora": { title: "Lumina 2 LoRA", area: "Lumina 2 · Kohya-ss · LoRA" },
"krea2-lora": { title: "Krea 2 LoRA", area: "Krea 2 · Musubi-Tuner · LoRA" },
"ideogram4-lora": { title: "Ideogram 4 LoRA", area: "Ideogram 4 · Musubi-Tuner · LoRA" },
"qwen-image-21-lora": { title: "Qwen-Image-2.1 LoRA", area: "Qwen-Image-2.1 · DiffSynth-Studio · LoRA" },
"ai-toolkit-sdxl-lora": { title: "SDXL LoRA", area: "SDXL · AI Toolkit · LoRA" },
"ai-toolkit-flux-lora": { title: "Flux LoRA", area: "Flux · AI Toolkit · LoRA" },
Expand Down Expand Up @@ -711,6 +713,7 @@ export default {
flux: "Flux",
anima: "Anima",
krea2: "Krea 2",
ideogram4: "Ideogram 4",
klein: "Klein",
"qwen-image-21": "Qwen-Image-2.1",
nvidia: "NVIDIA",
Expand Down Expand Up @@ -806,7 +809,7 @@ export default {
},
musubi: {
name: "Musubi-Tuner",
summary: "可选训练引擎,支持 Krea 2 等图像模型 LoRA。",
summary: "可选训练引擎,支持 Krea 2、Ideogram 4 等图像模型 LoRA。",
sizeHint: "体积较大(通常数 GB),需额外磁盘与下载时间",
},
"ai-toolkit": {
Expand Down
16 changes: 14 additions & 2 deletions frontend/src/pages/MusubiGatePage.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,17 @@ const installing: EngineStatus = { id: "musubi", state: "installing", featureEna
const notInstalled: EngineStatus = { id: "musubi", state: "not_installed", featureEnabled: true }

const trainingPageStub = {
template: '<div class="training-page-stub"><slot name="form-top" /></div>',
props: ["schemaName", "title", "area"],
template: '<div class="training-page-stub" :data-schema="schemaName"><slot name="form-top" /></div>',
}
const routerLinkStub = {
props: ["to"],
template: '<a class="ghost-button"><slot /></a>',
}

function mountPage() {
function mountPage(props: Record<string, unknown> = {}) {
return mount(MusubiGatePage, {
props,
global: { plugins: [i18n], stubs: { TrainingPage: trainingPageStub, RouterLink: routerLinkStub } },
})
}
Expand All @@ -49,10 +51,20 @@ describe("MusubiGatePage", () => {
expect(status).toHaveBeenCalledWith("musubi")
expect(setInterval).not.toHaveBeenCalled()
expect(wrapper.find(".training-page-stub").exists()).toBe(true)
expect(wrapper.get(".training-page-stub").attributes("data-schema")).toBe("krea2-lora")
expect(wrapper.get('[data-testid="engine-ready-chip"]').text()).toContain("训练环境准备就绪")
wrapper.unmount()
})

it("forwards the selected musubi schema to the training form", async () => {
status.mockResolvedValue(ready)
const wrapper = mountPage({ schemaName: "ideogram4-lora" })
await flushPromises()

expect(wrapper.get(".training-page-stub").attributes("data-schema")).toBe("ideogram4-lora")
wrapper.unmount()
})

it("shows the install view instead of the form when musubi is not installed", async () => {
status.mockResolvedValue(notInstalled)
const wrapper = mountPage()
Expand Down
13 changes: 8 additions & 5 deletions frontend/src/pages/MusubiGatePage.vue
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@ import EngineStatusBar from "../components/EngineStatusBar.vue"
import TrainingPage from "./TrainingPage.vue"
import { SCHEMA_META } from "../training/modules"

withDefaults(defineProps<{ bare?: boolean }>(), { bare: false })
const props = withDefaults(defineProps<{ bare?: boolean; schemaName?: string }>(), {
bare: false,
schemaName: "krea2-lora",
})

const status = ref<EngineStatus>({ id: "musubi", state: "unknown", featureEnabled: true })
const loading = ref(false)
Expand All @@ -20,9 +23,9 @@ let logSource: EventSource | undefined
let progressSource: EventSource | undefined

const { t } = useI18n()
const meta = SCHEMA_META["krea2-lora"]
const title = computed(() => t(meta.titleKey))
const area = computed(() => t(meta.areaKey))
const meta = computed(() => SCHEMA_META[props.schemaName] ?? SCHEMA_META["krea2-lora"])
const title = computed(() => t(meta.value.titleKey))
const area = computed(() => t(meta.value.areaKey))
const ready = computed(() => status.value.state === "ready")
const working = computed(() => ["installing", "auditing"].includes(status.value.state))
const label = computed(() => {
Expand Down Expand Up @@ -133,7 +136,7 @@ onBeforeUnmount(() => {
v-if="ready"
:title="title"
:area="area"
schema-name="krea2-lora"
:schema-name="schemaName"
:bare="bare"
>
<template #form-top>
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/pages/TrainingPage.vue
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { applyReadonlyDefaults, cloneFormModel, cloneFormValue, createDefaultMod
import { loadTrainingSchema } from "../schema/loader"
import { buildTrainingConfig, checkTrainingConfig, hydrateImportedConfig, pickCarryOverFields, sanitizePersistedDraft } from "../training/params"
import { QWEN_VALIDATION_FIELDS, validateQwenConfig } from "../training/qwenValidation"
import { isAiToolkitSchema, moduleForSchema, moduleForTrainType } from "../training/modules"
import { isAiToolkitSchema, isMusubiSchema, moduleForSchema, moduleForTrainType } from "../training/modules"
import { collectDatasetCheckTargets } from "../training/datasetCheck"
import { datasetsApi, type DatasetValidation, type ValidateFinding } from "../api/datasets"
import { copyText } from "../utils/clipboard"
Expand Down Expand Up @@ -331,7 +331,7 @@ async function submit() {
if (!preflight.ok) throw new Error(preflight.errors?.join("\n") || t("training.submitConfirm.preflightFail"))
preflight.warnings?.forEach((warning) => ElMessage.warning(warning))
}
if (props.schemaName === "krea2-lora") {
if (isMusubiSchema(props.schemaName)) {
const preflight = await trainingApi.musubiPreflight(output.value)
if (!preflight.ok) throw new Error(preflight.errors?.join("\n") || t("training.submitConfirm.preflightFail"))
preflight.warnings?.forEach((warning) => ElMessage.warning(warning))
Expand Down
Loading
Loading