diff --git a/.github/workflows/build-launcher.yml b/.github/workflows/build-launcher.yml index 99d1711..1cb84bc 100644 --- a/.github/workflows/build-launcher.yml +++ b/.github/workflows/build-launcher.yml @@ -1,14 +1,16 @@ name: build-launcher -# 一键启动器(tools/launcher.py → AnimaLoraStudio.exe)的构建。 +# Build for the one-click launcher (tools/launcher.py -> AnimaLoraStudio.exe). # -# PyInstaller 产出的是**宿主平台**的可执行文件,没法交叉编译 —— Windows 的 exe -# 只能在 windows runner 上出。Linux 的那份顺带一起出,方便本机 Linux 用户。 +# PyInstaller produces an executable for the **host platform** — it can't cross-compile, +# so the Windows exe can only be built on a windows runner. The Linux one is built +# alongside it for the convenience of local Linux users. # -# 触发: -# - 改到启动器源码的 PR / push → 只构建,作为「exe 还能编出来」的守门 -# - 打 tag(v*)→ 构建并把 exe 传成 release 附件 -# - 手动 workflow_dispatch +# Triggers: +# - PR / push touching the launcher source -> build only, as a gate that "the exe +# still builds" +# - tag push (v*) -> build and attach the exe as a release asset +# - manual workflow_dispatch on: pull_request: paths: @@ -42,8 +44,9 @@ jobs: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 with: - # 启动器只用标准库,但**它自己**得能在用户机器上跑。用 3.10 构建 - # 让产物的 glibc / API 需求落在我们声明支持的最低版本上。 + # The launcher itself only uses the standard library, but **it** still has to + # run on the user's machine. Building with 3.10 keeps the produced binary's + # glibc/API requirements at the minimum version we claim to support. python-version: "3.10" - name: Install PyInstaller @@ -52,8 +55,9 @@ jobs: - name: Build run: python tools/build_launcher.py - # 冒烟:产物必须能跑起来并认出自己在仓库里(--check 不装任何东西)。 - # 这一步能挡住"打包成功但一运行就缺模块"这类只有跑一次才暴露的问题。 + # Smoke test: the built binary must actually run and recognize itself inside the + # repo (--check installs nothing). This step catches "packaged fine but missing a + # module at runtime" issues that only surface when you actually run it once. - name: Smoke test run: ${{ matrix.binary }} --check diff --git a/.github/workflows/publish-launcher.yml b/.github/workflows/publish-launcher.yml index 9c8e992..90d2ce0 100644 --- a/.github/workflows/publish-launcher.yml +++ b/.github/workflows/publish-launcher.yml @@ -1,22 +1,26 @@ name: publish-launcher -# 构建 Windows 版 `AnimaLoraStudio.exe` 并**提交回仓库**。 +# Builds the Windows `AnimaLoraStudio.exe` and **commits it back to the repo**. # -# 为什么 exe 进 git(一般来说二进制不该进):本 fork 的目标用户是「clone 完 -# 想双击就用」的本机用户。让他们先去 Releases 翻附件、或先装 PyInstaller 自己 -# 编,等于把要省的那一步又加了回来。exe 只有 7MB 且极少变(只在 -# tools/launcher.py 改动时需要重出),代价可以接受。 +# Why the exe goes into git (binaries usually shouldn't): this fork's target users are +# people who "clone it and want to double-click it right away." Making them first dig +# through Releases for the attachment, or install PyInstaller and build it themselves, +# just adds back the step we're trying to save them. The exe is only ~7MB and rarely +# changes (only needs rebuilding when tools/launcher.py changes), so the cost is +# acceptable. # -# 为什么必须走 CI 而不是本地编:PyInstaller 产出的是**宿主平台**的可执行文件, -# 不能交叉编译 —— Windows 的 exe 只能在 Windows 上出。维护者用 Linux/macOS 时 -# 这个 workflow 是唯一的出路。 +# Why this has to go through CI instead of a local build: PyInstaller produces an +# executable for the **host platform** — it can't cross-compile, so the Windows exe can +# only be built on Windows. This workflow is the only path for maintainers on +# Linux/macOS. # -# 手动触发(Actions → publish-launcher → Run workflow)。刻意**不**挂到 push: -# PyInstaller 的输出不是逐字节可复现的,每次 push 都重出会让仓库无谓地长胖, -# 而且会和用户的 commit 抢同一分支。 +# Manually triggered (Actions -> publish-launcher -> Run workflow). Deliberately **not** +# hooked to push: PyInstaller's output isn't byte-for-byte reproducible, so rebuilding on +# every push would bloat the repo for no reason and would race with users' own commits on +# the same branch. # -# 常规的「exe 还编得出来吗」守门在 build-launcher.yml(每次改启动器源码时跑, -# 不提交任何东西)。 +# The routine "does the exe still build" gate lives in build-launcher.yml (runs on every +# change to the launcher source, commits nothing). on: workflow_dispatch: inputs: @@ -38,7 +42,8 @@ jobs: - uses: actions/setup-python@v5 with: - # 用支持的最低版本构建,产物的运行时要求才落在我们声明的下限上。 + # Build with the lowest supported version so the binary's runtime requirements + # stay at the floor we claim to support. python-version: "3.10" - name: Install PyInstaller @@ -47,9 +52,10 @@ jobs: - name: Build run: python tools/build_launcher.py - # 冒烟:产物必须真的能跑起来并认出自己在仓库里。--check 不装任何东西。 - # 提交一个编得出来但一运行就报缺模块的 exe,比不提交更糟 —— 用户拿到的 - # 是「双击没反应」,而仓库看起来一切正常。 + # Smoke test: the built binary must actually run and recognize itself inside the + # repo. --check installs nothing. Committing an exe that builds fine but errors out + # on a missing module at runtime is worse than not committing at all — the user + # gets a "double-click does nothing" experience while the repo looks perfectly fine. - name: Smoke test run: | Copy-Item dist\AnimaLoraStudio.exe .\AnimaLoraStudio.exe -Force @@ -61,9 +67,10 @@ jobs: git config user.name "github-actions[bot]" git config user.email "41898282+github-actions[bot]@users.noreply.github.com" git add -f AnimaLoraStudio.exe - # PyInstaller 的输出每次都有字节差异(时间戳等),所以这里不能靠 - # 「有 diff 就提交」判断有没有意义 —— 用 --quiet 只是为了跳过 - # 「一个文件都没变」这种(理论上不会出现的)空提交。 + # PyInstaller's output differs byte-for-byte every time (timestamps etc.), so + # "there's a diff" can't be used to decide whether committing is meaningful — + # --quiet here is only to skip the (theoretically impossible) case where + # literally nothing changed, to avoid an empty commit. if git diff --cached --quiet; then echo "nothing to commit" exit 0 diff --git a/.github/workflows/version-check.yml b/.github/workflows/version-check.yml index e68cf47..4b7174b 100644 --- a/.github/workflows/version-check.yml +++ b/.github/workflows/version-check.yml @@ -1,12 +1,13 @@ name: version-check -# 跨文件 version drift gate。v0.8.1 release 漏 bump package-lock.json,结果 -# npm install 启动期把 lockfile 改脏 → ZIP 用户 self-update preflight 卡 dirty -# tree。这个 workflow 防同类回归。 +# Cross-file version drift gate. The v0.8.1 release forgot to bump package-lock.json, +# which meant npm install dirtied the lockfile at startup -> ZIP users' self-update +# preflight got stuck on a dirty tree. This workflow guards against the same regression. # -# 跑两件事: -# 1. release_notes.yaml schema 校验(validate) -# 2. __init__.py / package.json / package-lock.json 四处 version 一致(verify-versions) +# Runs two checks: +# 1. release_notes.yaml schema validation (validate) +# 2. version consistency across __init__.py / package.json / package-lock.json +# (verify-versions) on: pull_request: push: diff --git a/.gitignore b/.gitignore index 9947a75..1aae15b 100644 --- a/.gitignore +++ b/.gitignore @@ -32,11 +32,12 @@ studio/web/node_modules/ studio/web/dist/ studio/web/.vite/ -# 顶层意外创建的 node_modules(vitest / 某些工具可能在 repo root 拉一份) +# Stray top-level node_modules (vitest / some tools may pull one at the repo root) /node_modules/ -# Studio 走 preset 池(studio_data/presets/,已被 studio_data/ 总规则覆盖), -# 旧的 config/ 目录已删,不再需要 user yaml 排除规则。 +# Studio now uses the preset pool (studio_data/presets/, already covered by the +# studio_data/ blanket rule); the old config/ directory is gone, so the user +# yaml exclusion rule is no longer needed. # OS / editor .DS_Store @@ -72,37 +73,43 @@ dataset/ nul # Root-level AI agent files — gitignored so contributors can put personal # config / @docs/AGENTS.md import here without conflicting with the repo. -# Project 公约 lives at docs/AGENTS.md (NOT ignored — note the leading /). +# The project conventions live at docs/AGENTS.md (NOT ignored — note the leading /). /AGENTS.md /CLAUDE.md -# 本地解压的设计稿原始目录(已整理进 mockup/) +# Local extracted design-mockup source directory (already organized into mockup/) 1/ -# 工具生成的临时目录 +# Temporary directory generated by tooling .spec-workflow/ -# Self-update 进程间通信用的瞬时标志(server 写 → cli.py / wrapper 读后立即删) +# Ephemeral flag used for self-update inter-process communication +# (server writes it -> cli.py / wrapper reads it and deletes it immediately) tmp/ -# 文档本地草稿(个人 TODO / 未定稿设计 / 临时笔记) +# Local documentation drafts (personal TODOs / unfinished designs / scratch notes) docs/_local/ # Local UI snapshots before redesign (not for commit) _ui_backup/ -# 一键启动器的产物(tools/build_launcher.py 编出来的)。 +# Build output of the one-click launcher (produced by tools/build_launcher.py). # -# `AnimaLoraStudio.exe`(Windows)**故意不在此列** —— 它随仓库一起提交, -# 这样用户 `git clone` 下来就能直接双击,不用先去 Releases 找附件。 -# 由 .github/workflows/publish-launcher.yml 在 windows runner 上构建并回提。 +# `AnimaLoraStudio.exe` (Windows) is **deliberately not** in this list — it's +# committed alongside the repo so that after `git clone` a user can just +# double-click it, without first hunting for the attachment on Releases. +# It's built and committed back by .github/workflows/publish-launcher.yml on +# a windows runner. # -# 非 Windows 的产物仍然忽略:Linux/macOS 用户直接 `./studio.sh` 就行, -# 不需要引导程序,提交进去只是给每个人多下几 MB。 +# The non-Windows build output is still ignored: Linux/macOS users just run +# `./studio.sh` directly and don't need a bootstrapper, so committing it would +# only add a few extra MB for everyone to download. /AnimaLoraStudio /dist/ -# 第三方库缓存(pip / HuggingFace / torch / npm / ...)—— 见 -# studio/infrastructure/local_cache.py:默认收进项目文件夹,免得几个 GB -# 悄悄堆在系统盘上。整个目录随时可删,删了只是下次重下。 +# Third-party library cache (pip / HuggingFace / torch / npm / ...) — see +# studio/infrastructure/local_cache.py: it's collected into the project +# folder by default so a few GB don't quietly pile up on the system drive. +# The whole directory can be deleted at any time; deleting it just means +# it gets re-downloaded next time. /.cache/ diff --git a/CHANGELOG.md b/CHANGELOG.md index c46fa09..4fb145f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,974 +1,973 @@ # Changelog -> **本文件由 [`tools/bump_version.py render-changelog`](tools/bump_version.py) 从 [`docs/announcements/`](docs/announcements/) 的 `tag: release` post 自动派生** -> —— 请改那些 markdown,不要改本文件。编写指南见 [`docs/announcements/README.md`](docs/announcements/README.md)。 +> **Generated by [`tools/bump_version.py render-changelog`](tools/bump_version.py) from the `tag: release` posts in [`docs/announcements/`](docs/announcements/).** +> Edit those markdown files, not this one. Authoring guide: [`docs/announcements/README.md`](docs/announcements/README.md). --- ## v0.20.2 — 2026-07-19 -修复 Krea 2 FP8 基底模型下的 LoKr 训练崩溃,以及正则集页面长 tag 溢出 +Fixes LoKr training with Krea 2 FP8 base models and long-tag overflow on the regularization page -### 修复 +### Fixed -- **Krea 2 FP8 基底模型下 LoKr 训练可用了(上游 #454)** - 使用官方 FP8 基底模型的 LoKr 训练此前会在第一步因不支持的 FP8 运算而中断。升级后已有训练配置无需改动,数据集也不用重建。 +- **LoKr training now works with Krea 2 FP8 base models (upstream #454)** + LoKr runs using an official FP8 base model previously stopped on the first step because an FP8 operation was unsupported. Existing training configurations work as-is after upgrading; datasets do not need to be rebuilt. -- **正则集列表里的长自然语言 tag 不再溢出(上游 #454)** - 「排除 train 高频 tag」中的长句此前会折行并盖住后面的内容。现在固定为单行高度加省略号,完整文本仍可悬停查看。 +- **Long natural-language tags no longer overflow the regularization list (upstream #454)** + Long sentences in “Exclude train top tags” previously wrapped across multiple lines and covered following content. They now stay at a single-line height with an ellipsis, while the full text remains available on hover. --- ## v0.20.1 — 2026-07-19 -修复 v0.20.0 中 NaViT 打包训练无法启动的问题 +Fixes NaViT packed training failing to start in v0.20.0 -### 修复 +### Fixed -- **NaViT 打包训练无法启动(#450)** - v0.20.0 中开启 NaViT 打包(`navit_packing`)的训练会在第一步报 `NameError: name 't5_attn' is not defined` 并退出。现已修复:训练配置无需任何改动,已构建的 latent 缓存可直接复用。 +- **NaViT packed training failed to start (#450)** + In v0.20.0, training runs with NaViT packing (`navit_packing`) enabled crashed on the first step with `NameError: name 't5_attn' is not defined`. This is now fixed: no config changes are needed, and previously built latent caches are reused as-is. --- ## v0.20.0 — 2026-07-18 -Krea 2 模型族全流程支持 · fp8 底模训练与推理 · 显存策略与编排 · 模型来源统一管理 · 预设自动保存 - -### 新增 - -- **Krea 2 模型族:训练到出图全流程(#408–#425)** - 训练配置新增「模型族」选择:除 Anima 外现在可以训 Krea 2(12.9B 单流 MMDiT,Qwen3-VL 文本编码)——Raw 底模 LoRA 训练、训练中采样、正则集 AI 生成、训练后评估、测试出图(含 Turbo 8 步蒸馏出图)全链路可用;输出 LoRA 与 ComfyUI 直接互通(采样组合 Euler + Simple 逐字对应)。入门与训练 tips 见公告「Krea 2 支持上线」。 - - - 下载中心按模型族分区,Krea 2 的 Raw / Turbo 各有 bf16 与官方 fp8 版,文本编码器 bf16 / fp8 单选;VAE 与 Anima 共享不重复下载 - - 切换模型族是确认式动作:弹窗逐项列出将重算的权重路径与族默认值,确认才生效;参数下拉按族过滤选项,Anima 专属功能(NaViT、tag 打乱等)对 Krea 2 自动隐藏 - - 训练开始默认把全部 caption 预编码进文本缓存、随即释放文本编码器(省约 9 GB 显存),大显存云端可关闭改为常驻在线编码 -- **fp8 底模:训练与推理全线(#426, #434, #435, #436, #437, #444)** - 官方 fp8 权重现在既能当训练底模也能当推理底模:选中 fp8 文件即生效,无需额外开关。训练侧(fp8_base)权重显存 25.6 → 13.1 GB,Krea 2 训练从 32 GB 下探到 24 GB 级显卡;推理侧 fp8 常驻显存约减半,LoRA 以 merge 回写方式挂载、与 ComfyUI 出图逐位一致。 - - - XY 矩阵在 fp8 底模上支持 LoRA 强度轴(逐格重 merge);LoHa 与 rs-LoRA 均可挂 fp8 底模,DoRA 暂不支持 - - 文本编码器官方 fp8 版(5.2 GB)训练与出图全线可用,只下这一份即可;切换精度自动重建文本缓存 -- **显存与内存编排(#427, #437, #438, #439)** - 测试出图新增「显存策略」三档(默认·用后释放 / 省显存 / 性能优先):任务开始把全部 prompt 预编码进缓存后释放文本编码器,采样期文本编码器零显存占用——16 GB 卡在「省显存」档可跑 Krea 2 fp8 出图;评估出图与正则 AI 生成同样接入。训练与出图加载大权重前有内存护栏:内存不够时给出可操作报错,而不是整机换页卡死。「清理显存」现在能真正清干净(补清 cuBLAS 工作区残留)。 -- **civitai 社区 LoRA 直接加载(#426, #428)** - PEFT / comfy 键格式的 Krea 2 社区 LoRA(civitai 生态常见形态)现在可以直接在测试页挂载,bf16 与 fp8 底模都支持,无需转换。 -- **模型来源统一管理(#446)** - WD14 / CLTagger / 评估指标 / 放大器 / 训练主模型五处的「换模型」统一为同一种候选列表:内置候选 + 你添加的候选,底部「添加下载」「添加本地文件」双入口;本地文件候选只会从列表移除、永不删你的文件。旧配置自动迁移,选中项不变。 -- **LLM 打标预设统一编辑器(#445)** - LLM 打标器的预设编辑收口到一个全局编辑器(设置页与打标页打开同一个),字段不再两处不一致;设置页 LLM 区改为预设管理列表(单选默认 + 新建 / 编辑),打标页只留本次运行的预设选择;在设置里改完参数,打标页立即生效。 -- **生成页:底模 / 文本编码器选择持久化 + token 计数(#441)** - 测试页选过的底模与文本编码器现在跨页面、跨会话记住(不选则跟随全局设置);prompt 框右下角显示 token 计数;在线出图不再把超过 512 token 的 prompt 静默截断。 -- **生成任务超时兜底(#443)** - 设置里可配置生成任务超时(默认不开启):整机卡死等极端情况下到时强制终止推理进程并标记失败,下个任务自动重新加载,不再需要重启电脑。 - -### 变更 - -- **新配置的默认 LoRA 类型改为经典 LoRA(#442)** - 新项目与新预设的 `lora_type` 默认值从 `lokr` 改为 `lora`;已有配置显式落盘的值不受影响。 -- **下载中心已下载资产的操作改为「删除」(#437, #442)** - 「重新下载」的隐式覆盖退役:先删除、再下载;打标 / 评估 / 放大器模型区同步接入删除。 -- **配置互斥规则双端强制 + 联动确认(#431, #432)** - 界面上声明过的参数互斥现在后端同样强制:手写 yaml / 命令行的非法组合从静默放行改为启动即报错并给出原因;在界面里触发联动改值时(如开 NaViT 联动 attention backend)先弹确认框列出将改动的字段,确认才应用。配置 YAML 预览与实际落盘现在物理同源,所见即所存。 - -### 改进 - -- **预设页自动保存 + 三页统一保存指示(#433)** - 全局预设页与训练 / 设置页一致改为自动保存(600ms 防抖),编辑不再需要点保存;训练、预设、设置三页统一「✓ 已保存」指示;禁用状态的输入框现在有明确的灰显。 - -### 修复 - -- **全局换选模型后,旧版本的训练配置不跟随(#439)** - 设置页换选主模型 / 文本编码器后,已有版本开启「自动同步路径」时训练配置此前仍显示并使用创建时的旧路径;现在读取与训练入队都跟随全局选择。 -- **训练任务日志看不到 step 进度(#439)** - Studio 里跑的训练任务日志此前可能整个 epoch 一行 step 进度都不出(输出缓冲 + 进度条在非终端环境刷屏);现在按行实时输出。 -- **Windows 上取消任务可能残留训练进程(#410)** - 进程终止命令因权限等原因失败时,此前任务显示已取消但进程仍在占用 GPU;现在自动回退到逐进程递归终止。 +Full Krea 2 model-family support · fp8 base-model training and inference · VRAM policies and orchestration · unified model-source management · autosaving presets + +### Added + +- **Krea 2 model family: the full pipeline from training to generation (#408–#425)** + The training config gains a model-family selector: besides Anima you can now train Krea 2 (a 12.9B single-stream MMDiT with Qwen3-VL text encoding) — LoRA training on the Raw base, samples during training, AI-generated regularization sets, post-training evaluation, and test generation (including 8-step distilled Turbo) all work end to end; output LoRAs interoperate directly with ComfyUI (the Euler + Simple sampling combo matches word for word). See the "Krea 2 support is here" announcement for getting started and training tips. + + - The download center is grouped by family: Krea 2 Raw / Turbo each come in bf16 and official fp8, the text encoder toggles bf16 / fp8; the VAE is shared with Anima, no duplicate download + - Switching families is a confirmed action: a dialog itemizes every weight path and family default about to be recomputed before anything changes; option dropdowns filter per family, and Anima-only features (NaViT, tag shuffle, etc.) hide automatically on Krea 2 + - Training pre-encodes all captions into a text cache and releases the text encoder by default (saving about 9 GB of VRAM); high-VRAM cloud setups can disable it to keep the encoder resident +- **fp8 base models: training and inference across the board (#426, #434, #435, #436, #437, #444)** + Official fp8 weights now work as both training and inference base models — pick the fp8 file and it just works, no extra switch. For training (fp8_base), weight VRAM drops 25.6 → 13.1 GB, bringing Krea 2 training down to 24 GB-class GPUs; for inference, resident VRAM is roughly halved, with LoRAs applied via merge-writeback that matches ComfyUI output bit for bit. + + - XY grids support the LoRA-strength axis on fp8 bases (re-merged per cell); LoHa and rs-LoRA load on fp8 bases, DoRA is not supported yet + - The official fp8 text encoder (5.2 GB) works for both training and generation — one download covers everything; switching precision rebuilds the text cache automatically +- **VRAM and RAM orchestration (#427, #437, #438, #439)** + Test generation gains a three-level VRAM policy (default·release-after-use / save VRAM / performance): each task pre-encodes all prompts into a cache and releases the text encoder, so it occupies zero VRAM during sampling — a 16 GB GPU can generate with Krea 2 fp8 on the "save VRAM" setting; evaluation and regularization generation use the same orchestration. Loading large weights is now guarded by a RAM check that fails with an actionable error instead of freezing the whole machine in page thrash. "Free VRAM" now actually frees everything (clears leftover cuBLAS workspaces). +- **Community LoRAs from civitai load directly (#426, #428)** + Krea 2 community LoRAs in PEFT / comfy key format (the common civitai shape) now load directly on the Test page, on both bf16 and fp8 bases, no conversion needed. +- **Unified model-source management (#446)** + Swapping models for WD14 / CLTagger / eval metrics / upscalers / training base models now uses one shared pattern: a candidate list (built-ins + your additions) with "add download" and "add local file" entries at the bottom; local-file candidates are only ever removed from the list — your files are never deleted. Old configs migrate automatically with selections preserved. +- **One editor for LLM tagger presets (#445)** + LLM tagger preset editing is consolidated into a single global editor (Settings and the Tagging page open the same one), ending field drift between the two; the Settings LLM section becomes a preset management list (default radio + create / edit), the Tagging page keeps just the per-run preset choice; edits made in Settings take effect on the Tagging page immediately. +- **Generate page: persistent base/TE choice + token count (#441)** + Your base model and text encoder picks on the Test page are now remembered across pages and sessions (unset = follow global Settings); the prompt box shows a token count; online generation no longer silently truncates prompts beyond 512 tokens. +- **Generation task timeout (#443)** + An optional task timeout (off by default) force-kills the inference process when the machine is wedged, marks the task failed, and reloads on the next task — no more reboots. + +### Changed + +- **Default LoRA type for new configs: lokr → lora (#442)** + New projects and presets default `lora_type` to classic LoRA; existing configs with an explicit value are unaffected. +- **Downloaded assets in the download center now offer "delete" (#437, #442)** + The implicit overwrite of "re-download" is retired: delete first, then download; tagger / eval / upscaler sections get delete too. +- **Config exclusion rules enforced on both ends + linked-change confirmation (#431, #432)** + Parameter exclusions declared in the UI are now enforced by the backend as well: invalid combinations in hand-written yaml / CLI fail at startup with a reason instead of passing silently; when a UI change triggers linked field rewrites (e.g. NaViT forcing the attention backend), a dialog lists the changes for confirmation first. The YAML preview is now physically the same serialization as what lands on disk. + +### Improved + +- **Presets page autosaves + unified save indicator (#433)** + The global presets page now autosaves like the Train and Settings pages (600ms debounce) — no more save button for existing presets; Train / Presets / Settings share the same "✓ saved" indicator; disabled inputs are now visibly grayed out. + +### Fixed + +- **Training configs didn't follow a global model re-selection (#439)** + After picking a different base model / text encoder in Settings, versions with "auto-sync paths" enabled kept showing and using the paths from creation time; reads and enqueued training now follow the global selection. +- **Training task logs showed no step progress (#439)** + Task logs for Studio-launched training could go a whole epoch without a single step line (output buffering + the progress bar misbehaving outside a terminal); output is now line-by-line in real time. +- **Canceling a task on Windows could leave the training process running (#410)** + When the process-kill command failed (e.g. access denied), the task showed as canceled while the process kept holding the GPU; termination now falls back to recursively killing the process tree. --- ## v0.19.0 — 2026-07-14 -预处理涂抹 / Mask 笔刷与 masked loss · 单图查看缩放平移 · bundle 可打包 latent 缓存 · 版本切换一致性修复 · Ctrl+C 干净退出 +Retouch & mask brushes with masked loss · zoom and pan everywhere · latent cache in bundles · version-switch consistency fixes · clean Ctrl+C shutdown -### 新增 +### Added -- **预处理「涂抹」工具:取色笔刷覆盖文字 / 水印(#393)** - 预处理页新增第五个工具「涂抹」:用取色笔刷把图上的文字、水印、杂物实心涂掉,避免 LoRA 学入。支持 Alt 点击吸管取色、软边笔刷、逐图撤销重做(Ctrl+Z);改动先留在内存,可逐图或一键保存全部。 -- **Mask 笔刷 + masked loss:训练跳过你标记的区域(#394, #395, #400)** - 涂抹页新增 Mask 模式:用笔刷把易崩坏的手、无关背景等区域标记出来(配橡皮擦与覆盖率提示,超过 50% 会警示),再在训练配置的 loss 组开启 `masked_loss`,训练时这些区域就不参与学习,出图时按底模的先验表现、不带训练集里的崩坏特征;灰度 mask 支持部分权重。mask 以 `{图名}.mask` 文件与图片同目录保存,自动跟随裁剪、放大等预处理操作(还原原图时删除);bundle 导出可选一并打包。 +- **Retouch tool: paint over text and watermarks (#393)** + Preprocessing gains a fifth tool, Retouch: paint over text, watermarks, and clutter with a solid color-picked brush so your LoRA never learns them. Alt-click to sample a color, soft-edged brush, per-image undo/redo (Ctrl+Z); edits stay in memory until you save one image or all at once. +- **Mask brush + masked loss: training skips the regions you mark (#394, #395, #400)** + The Retouch page gains a Mask mode: brush over regions you don't want trained on (hands prone to artifacts, irrelevant backgrounds), with an eraser and a coverage readout (warns above 50%). Then enable `masked_loss` in the training config's loss group — masked regions are excluded from learning, so generated images fall back to the base model there instead of picking up artifacts from your dataset; grayscale masks give partial weighting. Masks are stored next to each image as `{name}.mask`, follow crop and upscale automatically (restoring the original deletes them), and can optionally be included in bundle exports. - - `masked_loss` 默认关闭:训练集画了 mask 但没开启时训练页会提示;开启后 mask 变更自动重建 latent 缓存。与 NaViT 打包模式暂不兼容(该模式下自动忽略 mask 并在日志提示)。 -- **导出 bundle 可打包 VAE latent 缓存(#391)** - 导出 bundle.zip 时,训练集 / 正则集可分别勾选把 VAE latent 缓存(`.npz`)一并打包;导入到另一台机器后直接复用、跳过重新 encode(encode 慢的机器收益最大)。包体积会明显变大(1024² 每张约 1 MB,开翻转增广再翻倍)。 -- **打标页显示验证集打标状态(#392)** - 版本有验证集图片时,打标页右栏新增「验证集打标状态」:打标进度与「此轮需要打标」按数据集分开统计;打标范围选「验证集」时训练集侧不再显示「—」。 + - `masked_loss` is off by default: the training page points it out when your set has masks but the switch is off, and mask changes automatically invalidate the latent cache. Not yet compatible with NaViT packing (masks are ignored there with a log notice). +- **Bundle export can include the VAE latent cache (#391)** + When exporting a bundle.zip, you can now include the VAE latent cache (`.npz`) for the training set and regularization set separately; importing on another machine reuses it directly and skips re-encoding (biggest win on machines where encoding is slow). Expect a noticeably larger bundle (about 1 MB per 1024² image, doubled with flip augmentation). +- **Tagging page shows validation set progress (#392)** + When a version has validation images, the tagging page's right panel gains a "validation set" section: tagging progress and the "to tag this round" estimate are tracked per dataset, and selecting the validation scope no longer shows "—" for the training side. -### 改进 +### Improved -- **单图查看全面获得缩放平移(#396)** - 全站单图查看(生成页全屏查看、各页画廊预览放大、打标页内嵌大图)统一支持滚轮缩放(以指针为中心)、拖拽平移、双击在适配 / 100% 间切换;打标页大图升级为原图分辨率,放大看局部不再糊。 +- **Zoom and pan in every single-image view (#396)** + All single-image views (fullscreen viewer on the Generate page, gallery preview modals across the app, the inline image on the tagging page) now support pointer-centered wheel zoom, drag to pan, and double-click to toggle fit / 100%; the tagging page image is upgraded to full resolution, so zooming in stays sharp. - - 查看器内点击图片不再关闭弹层(点击与拖拽平移无法两全);关闭用 × 按钮、Esc 或点击边缘遮罩。 -- **tag 自动补全:候选只在输入时弹出 + 全局开关(#401)** - 点击输入框挪光标不再弹出候选挡住正在读的内容,只有输入变化后才弹;候选已弹出时点击输入框会立即收起。设置 → Tag 翻译词典新增「输入时自动补全 tag」开关,可全站关闭。 -- **评估失败可从日志条直接重试(#397)** - 训练后评估失败时,评估日志条上现在有「重试」按钮,一键重跑本轮全部评估;此前只能手动重新选 checkpoint 跑一遍。任务详情头部的「重试」对训练任务改名「重新训练」,明确它是用相同配置从头训练、而不是重跑评估。 -- **T5 tokenizer 缺失引发的训练失败给出可读错误(#387)** - T5 tokenizer 本地目录缺失时训练会自动联网下载,此前网络不稳会报一段看不出原因的超时堆栈;现在下载开始前有日志提示,下载失败的错误信息写明原因与修复指引(检查网络 / 去设置页下载 / 核对路径配置)。 -- **筛选页数据集切换对齐队列页样式(#392)** - 训练集 / 验证集切换从双 tab 改为与队列页一致的单按钮(显示要切去的数据集),移到页头最右。 + - Clicking the image inside a viewer no longer closes it (click and drag-to-pan can't coexist); close with the × button, Esc, or by clicking the edge overlay. +- **Tag autocomplete: suggestions only pop up as you type + global toggle (#401)** + Clicking into an input to move the cursor no longer pops suggestions over what you're reading — they appear only after the text changes, and clicking the input dismisses an open list. Settings → Tag dictionary gains an "autocomplete while typing" switch to turn it off app-wide. +- **Failed evaluations can be retried from the log entry (#397)** + When post-training evaluation fails, the evaluation log entry now has a Retry button that re-runs the whole round in one click; previously you had to re-select checkpoints manually. The "Retry" button in the task detail header is renamed "Retrain" for training tasks, making clear it starts training over with the same config rather than re-running evaluation. +- **Readable errors when the T5 tokenizer is missing (#387)** + When the local T5 tokenizer directory is missing, training downloads it automatically; on flaky networks this used to fail with an inscrutable timeout stack trace. The download is now announced in the log, and failures explain the cause with fix guidance (check your network / download it from Settings / verify the path setting). +- **Curation dataset switch matches the queue page style (#392)** + The training / validation set switch changes from two tabs to the same single-button toggle as the queue page (labeled with the set you'd switch to), placed at the far right of the header. -### 修复 +### Fixed -- **切换版本后的旧版本残留(#389, #390)** - 多版本项目切换版本后:立即点「开始训练」此前可能把旧版本入队;步骤页正文此前会残留旧版本数据——尤其图片筛选页会一直显示旧版本的图,看着旧图操作实际写入新版本。现在切换即时生效、步骤页整体随版本刷新;打标页有未保存编辑时切换会先弹确认。 -- **终端 Ctrl+C 关不掉 server(#398)** - 浏览器 tab 开着时在终端按 Ctrl+C 停 server,此前会卡在「Waiting for connections to close」永远退不出(再按 Ctrl+C 也无效,只能杀进程);现在一次 Ctrl+C 约 3 秒内干净退出,且不再刷出退出期的假报错。 -- **Windows 训练日志不再出现 triton 报错误导(#388)** - Windows 上启用 xformers 时,日志会打出无害的 `No module named 'triton'` 报错堆栈;任务因其他原因失败时它还会被误当失败原因展示,让人以为要装 triton。现在跳过该探测(triton 对本项目用到的 xformers 加速零参与),日志与失败摘要恢复干净。 +- **Stale data after switching versions (#389, #390)** + In multi-version projects, after switching versions: clicking "Start training" right away could enqueue the previous version, and step pages could keep showing the previous version's data — the curation page in particular kept showing old images while actions applied to the new version. Switching now takes effect immediately and step pages fully reload with the version; switching away from unsaved tag edits asks for confirmation first. +- **Ctrl+C in the terminal couldn't stop the server (#398)** + With a browser tab open, pressing Ctrl+C used to hang the server at "Waiting for connections to close" forever (a second Ctrl+C didn't help; only killing the process did). One Ctrl+C now exits cleanly within about 3 seconds, without the spurious errors that used to appear during shutdown. +- **Windows training logs no longer show a misleading triton error (#388)** + With xformers enabled on Windows, logs used to print a harmless `No module named 'triton'` traceback — and when a task failed for an unrelated reason, it could be shown as the failure cause, suggesting you needed to install triton. The probe is now skipped (triton plays no part in the xformers kernels this app uses), keeping logs and failure summaries clean. --- ## v0.18.0 — 2026-07-09 -NaViT 打包训练 · WandB 全局预设化 · 训练配置只落盘生效参数 · 去重审核精简 · CAME 优化器 - -### 新增 - -- **NaViT / Patch-n-Pack 打包训练(默认关闭)(#371, #372)** - 训练新增 `navit_packing` 模式:把多张不同尺寸的图打包进同一条序列训练,每张图只关注自己的内容,显存利用率由 token 预算决定,不再受「一批一个桶尺寸」限制。 - - - 可选 `navit_native_resolution` 原生分辨率:不再把图缩放 / 裁剪到分桶网格,按原生尺寸参训(16px 对齐);超出 token 预算时默认等比缩小,也可选直接报错。 - - 两者默认关闭;关闭时训练行为与之前完全一致。 -- **timestep shift 分辨率修正(默认关闭)(#383)** - 训练配置新增 `timestep_shift_resolution_aware` 开关:开启后 timestep shift 按每张图的实际 token 数相对基准分辨率自动缩放(分辨率越高偏移越强),多分辨率训练与 NaViT 原生分辨率下每张图各自落在合适的噪声区段;与基准分辨率相同的图不受影响。 -- **CAME 优化器(#358)** - 优化器新增 CAME:用行 / 列分解的二阶矩降低优化器状态的显存占用,并以置信度加权抑制分解带来的近似噪声;高级参数 6 项(beta / eps / clip),默认按论文取值。 -- **LLM 打标可注入本地 tagger 预打标结果(#230)** - LLM 预设新增「辅助 tagger」选项(关闭 / WD14 / CLTagger):打标前先用本地 tagger 对每张图预打标,结果替换提示词中的 `{{tags}}` 占位符,作为先验辅助 LLM 写 caption。 - - - 出厂自带两个含 `{{tags}}` 的内置辅助提示词预设(结构化 caption / 单行自然语言),也可在任意预设的提示词里手动加 `{{tags}}`;仅当提示词包含 `{{tags}}` 时生效。 -- **训练配置页 / 预设页新增「YAML 预览」(#366)** - 两页可随时查看实际落盘、参训的生效配置;预设页原「TOML 预览」名不副实(字段并非 sd-scripts 格式),已由 YAML 预览取代。 - -### 变更 - -- **WandB 配置移入全局设置,训练配置中的 WandB 字段移除(#367)** - WandB 从训练配置里的 per-config 覆盖块改为全局设置中的预设管理(设置 → WandB):API Key 不再随 config.yaml / 任务快照 / 导出预设 / bundle 明文落盘。全局旧配置自动迁移;曾在训练配置里填过 WandB 的用户请看置顶迁移公告(含 API Key 轮换建议)。 - - - 预设支持多套配置切换、另存为、导入导出(导出文件含真实 key,注意保管)。 - - artifact 上传设置从「采样图上传」的嵌套里独立出来,并合并为三档:关闭 / 仅保留最新 / 保留全部。 - - 命令行直跑训练的 `--wandb_*` 旗标随之移除,改用 `WANDB_*` 环境变量。 -- **训练配置只落盘生效参数(#366, #369)** - config.yaml / 预设 / 任务快照不再写入未启用功能的子参数与内部默认值,文件明显变短、查看参数没有噪音;已有文件读取不受影响,缺省键按默认值补齐。 - - - 顺带修复:未启用功能的隐藏字段若残留非默认值(如切换优化器后留下的旧子参数),此前会静默参与训练;现在落盘即裁剪,所见即所训。 -- **去重审核精简为两项扫描参数,裁剪并入分组(#381)** - 去重扫描参数从 18 项精简为「匹配范围 + 灵敏度(宽松 / 标准 / 严格)」两项,其余阈值固化为内置常量;裁剪 / 缩放关系不再是单独的「质量候选」tab,作为更严格的重复并进普通分组审核,且仅在「重复 + 同分镜差分」范围下检测。 -- **打标页内直接管理模型(#378, #380)** - 打标页右栏改为「当前打标状态」面板(已打标数 / 打标方式 / 模型 / 触发词 / 此轮需要打标数);模型下载、切换与下载源选择移入各打标器的「高级参数」折叠卡,不用再跳全局设置。 - -### 改进 - -- **侧边栏版本选择器重构为版本行(#384)** - 侧边栏「训练版本」块改为紧凑版本行:切换版本改为弹出浮层(不再撑开侧栏),hover 显示新建 / 导出 / 删除;总览页版本条同步补上删除入口,版本四操作两处对齐。 -- **正则集页布局重构(#379)** - 「生成」tab 改为左右分栏:左侧生成参数按「正则集设置 / 出图设置」重新分组,右侧新增正则集状态面板,底部「此轮需要生成」按来源 / 模式 / 目标数实时推算,与后端口径一致。 -- **预处理各页操作统一(#381)** - 工具切换改为贴 header 的下划线 tab,各页主操作统一移到页头右上角;裁剪页删掉左侧参数区给工作区让空间,「智能聚类」更名「自动聚类」并移入页头按钮弹层。 -- **模型根目录迁移支持合并到已有数据(#368)** - 设置页迁移模型根目录时,目标已有数据不再直接拒绝:可选「跳过已有文件」(只补缺失文件,适合指向与其他工具共用的模型目录)或「覆盖已有文件」;复制全程逐文件原子写入,中途失败不会损坏目标目录的既有文件。 -- **队列行操作按钮归位(#370)** - 「继续训练」与暂停行的「恢复 / 取消」从时间列移入行尾操作列并 icon 化;继续训练补上确认弹窗,防止误点直接拉起训练。 -- **提示词选择器的数据集预览可点击放大(#375)** - 测试页「从训练集选 prompt」底部的图片预览支持点击放大全屏查看,方便与生成结果比对。 - -### 修复 - -- **出图中间步预览颜色反相(#377)** - 生成过程的中间步预览此前用错了解码器(按 Flux 而非 Wan2.1 的 latent 空间解码),颜色反相 / 错乱且画面很小;现已用正确的投影解码,预览颜色正常并铺满结果区。 -- **回看历史时点「开始生成」结果区不再停在老图(#376)** - 在回看历史图(尤其 XY 图)时点「开始生成」,结果区此前停留在回看的老图上;现在会回到实时视图,直接看到新任务的生成进度。 -- **只填一个代理字段时 HTTPS 流量不再绕过代理(#374)** - HTTP / HTTPS 代理任一留空时回退用另一个(设置页一直如此描述,但此前未实现):只填 HTTP 代理时 booru 查询 / 下载不再直连超时。两个字段都填时各自生效、互不覆盖。 -- **方图分桶不再落到偏小的桶(#313)** - 多个候选桶宽高比距离相同时(如基准 1536 下的多个 1:1 桶),现在选面积最接近基准分辨率的桶,此前会落到第一个生成的偏小桶(1472×1472 而非 1536×1536);前端桶预览与训练端同步该规则。受影响的数据集升级后分桶结果会略有变化。 - -### 删除 - -- **去重审核的「模糊候选」移除(#381)** - 模糊检测属于画质过滤、与「去重」无关,该 tab 及对应扫描逻辑已移除。 +NaViT packed training · Global WandB presets · Configs persist active fields only · Simplified dedup review · CAME optimizer + +### Added + +- **NaViT / Patch-n-Pack packed training (off by default) (#371, #372)** + Training gains a `navit_packing` mode: images of different sizes are packed into a single sequence, with each image attending only to its own content. VRAM utilization is governed by a token budget instead of the "one bucket size per batch" constraint. + + - Optional `navit_native_resolution`: images train at their native size (16px-aligned) instead of being resized / cropped to the bucket grid; images over the token budget are scaled down proportionally by default (or can be made to fail instead). + - Both are off by default; with them off, training behaves exactly as before. +- **Resolution-aware timestep shift (off by default) (#383)** + New `timestep_shift_resolution_aware` switch in the training config: when enabled, the timestep shift scales automatically with each image's token count relative to the base resolution (higher resolution → stronger shift), so multi-resolution and NaViT native-resolution runs place every image in an appropriate noise range. Images at the base resolution are unaffected. +- **CAME optimizer (#358)** + New CAME optimizer option: factorized row / column second moments reduce optimizer-state VRAM, with confidence-guided weighting to suppress the approximation noise the factorization introduces. Six advanced parameters (betas / eps / clip), defaulting to the paper's values. +- **LLM tagging can inject local tagger output (#230)** + LLM presets gain an "assist tagger" option (off / WD14 / CLTagger): each image is pre-tagged by the local tagger first, and the result replaces the `{{tags}}` placeholder in your prompt as a prior to guide the LLM's caption. + + - Two built-in assist prompt presets ship with `{{tags}}` included (structured caption / single-line natural language); you can also add `{{tags}}` to any preset's prompt yourself. It only takes effect when the prompt contains `{{tags}}`. +- **YAML preview on the training config and presets pages (#366)** + Both pages can now show the effective config exactly as it will be written to disk and used in training. The presets page's old "TOML preview" was misleading (the fields were never sd-scripts format) and has been replaced by the YAML preview. + +### Changed + +- **WandB settings moved to global Settings; WandB fields removed from training configs (#367)** + WandB moves from a per-config override block in the training config to preset-based management in global Settings (Settings → WandB): your API key is no longer written in plain text into config.yaml, task snapshots, exported presets or bundles. Existing global settings migrate automatically; if you ever filled in WandB inside a training config, see the pinned migration post (including the API-key rotation advice). + + - Presets support switching between multiple setups, save-as, and import / export (exports contain the real key — handle with care). + - The artifact upload settings moved out of the sample-image upload subsection (they were unrelated) and merged into a three-way choice: off / keep latest only / keep all. + - The `--wandb_*` CLI flags are gone along with the fields; use `WANDB_*` environment variables instead. +- **Training configs persist active fields only (#366, #369)** + config.yaml, presets and task snapshots no longer include sub-parameters of disabled features or internal defaults — files are much shorter and easier to read. Existing files load unchanged; missing keys fall back to their defaults. + + - This also fixes a real issue: leftover non-default values in hidden fields (e.g. sub-parameters of a previously selected optimizer) could silently affect training. They are now pruned at write time — what you see is what trains. +- **Dedup review simplified to two scan parameters; crops merged into groups (#381)** + The dedup scan goes from 18 parameters down to two: match scope and sensitivity (loose / standard / strict), with the remaining thresholds fixed as built-in constants. Crop / resize relations are no longer a separate "quality candidates" tab — they join the regular duplicate groups as stricter duplicates, and are only detected under the "duplicates + near-duplicates" scope. +- **Manage tagger models right on the Tagging page (#378, #380)** + The Tagging page's right panel becomes a "current tagging status" card (tagged count / method / model / trigger word / images this run will process); model download, selection and download-source switching move into each tagger's "advanced parameters" section — no more round-trips to global Settings. + +### Improved + +- **Sidebar version picker rebuilt as a version row (#384)** + The sidebar "training version" block becomes a compact version row: switching opens a popover (no longer stretches the sidebar), and hovering reveals create / export / delete. The Overview page's version rail gains the missing delete action, so all four version actions match in both places. +- **Regularization page layout rework (#379)** + The "Generate" tab is now split into two columns: generation parameters on the left, regrouped into "reg-set settings / image settings", and a new status panel on the right whose "images this run will generate" figure updates live from source / mode / target count, matching the backend's math. +- **Unified actions across preprocessing pages (#381)** + Tool switching becomes an underline tab row attached to the header, and each page's primary actions move to the top-right of the header. The crop page drops its parameter sidebar to give the workspace more room; "smart clustering" is renamed "auto clustering" and moves into a header popover. +- **Model root migration can merge into existing data (#368)** + Migrating the model root in Settings no longer refuses when the target already has data: choose "skip existing files" (copies only what's missing — ideal for pointing at a directory shared with other tools) or "overwrite existing files". Copies are atomic per file, so a failure mid-way never corrupts what's already in the target. +- **Queue row actions in their proper place (#370)** + "Resume training" and the paused-row "resume / cancel" buttons move from the time column into the trailing action column as icons; resume now asks for confirmation, so a stray click can't kick off training. +- **Dataset preview in the prompt picker zooms on click (#375)** + The dataset image preview at the bottom of the Generate page's "pick prompt from training set" panel now opens full-screen on click, for side-by-side comparison with generated results. + +### Fixed + +- **Mid-step generation previews no longer show inverted colors (#377)** + In-progress previews decoded latents with the wrong decoder (Flux instead of the Wan2.1 latent space), producing inverted / garbled colors at a tiny size. They now use the correct projection, with normal colors, filling the result area. +- **Clicking "Generate" while viewing history returns to the live view (#376)** + Clicking "Generate" while looking at an older image (especially XY grids) used to leave the result area stuck on that old image; it now returns to the live view so you see the new task's progress immediately. +- **HTTPS traffic no longer bypasses the proxy when only one field is set (#374)** + If either the HTTP or HTTPS proxy field is empty, the other is now used for both (as the settings page always claimed but never implemented): with only an HTTP proxy configured, booru queries and downloads no longer bypass it and time out. With both fields set, each is respected independently. +- **Square images no longer land in an undersized bucket (#313)** + When several candidate buckets tie on aspect-ratio distance (e.g. multiple 1:1 buckets at base 1536), the bucket closest in area to the base resolution now wins; previously the first generated smaller bucket won (1472×1472 instead of 1536×1536). The frontend bucket preview mirrors the same rule. Affected datasets will bucket slightly differently after upgrading. + +### Removed + +- **"Blur candidates" removed from dedup review (#381)** + Blur detection is quality filtering, not deduplication; the tab and its scan logic are gone. --- ## v0.17.0 — 2026-07-03 -队列全面改版 · 出图多任务排队 · 训练定时开始 · 显存调度统一准入 · 打标落盘格式收敛 +Queue page overhaul · Generation task queueing · Scheduled training · Unified VRAM admission · Tagging output format cleanup -### 新增 +### Added -- **队列页全面改版:分区、全类型可见、搜索 / 过滤 / 分页(#343, #348, #359, #361, #362, #363)** - 队列不再只显示训练任务:训练 / 正则集构建 / 测试出图 / 评估出图统一进一个队列,按「计划任务 / 进行中 / 等待入队 / 历史」分区显示,历史支持分页。 +- **Queue page overhaul: sections, all task types, search / filter / pagination (#343, #348, #359, #361, #362, #363)** + The queue no longer shows training tasks only: training, regularization-set builds, test generation and evaluation sampling now share one queue, split into "Scheduled / Running / Waiting / History" sections, with paginated history. - - 顶部可搜索(任务名 / 配置名 / 项目名)、按类型和状态过滤;筛选条件会记住,切走再回来不丢。默认只看训练,可切「全部」。 - - 详情页按任务类型显示对应内容,并提供「查看结果」跳转(出图任务直达测试页对应结果,正则任务直达正则集页)。 - - 新增「数据作业」标签页:下载 / 预处理 / 打标 / 评估指标等作业集中可见,行内展开看参数、错误与实时日志,可取消、可跳回原步骤页。 - - 所有任务与数据作业现在共用一套编号,跳转与引用不再有两套 ID。 -- **出图多任务排队:连点连出、统一时间线、全阶段进度(#355, #348)** - 测试页点「开始生成」不再互相打断:任务依次排队执行,右栏变成统一时间线(排队中 / 生成中 / 已完成),可单条取消或一键清空队列。 + - Search by task, config or project name, and filter by type and status; filters are remembered across visits. Defaults to training tasks only — switch to "All" anytime. + - Task detail pages now adapt to the task type, with a "View result" shortcut (generation tasks jump to their results on the Generate page, regularization tasks to the reg-set page). + - New "Data jobs" tab: downloads, preprocessing, tagging and evaluation metric jobs are all visible in one place — expand a row for parameters, errors and live logs, cancel it, or jump back to its original page. + - Tasks and data jobs now share a single ID space; no more two numbering schemes. +- **Generation task queueing: submit back-to-back, unified timeline, full-phase progress (#355, #348)** + Clicking "Generate" on the test page no longer interrupts a running job: tasks queue up and run in order, and the right panel becomes a unified timeline (queued / running / done) with per-task cancel and a clear-queue button. - - 「开始生成」旁新增批量数:一次入队 N 个任务,seed 自动递增。 - - 进度条覆盖模型加载 / 文本编码 / 采样 / VAE 解码全阶段,不再长时间卡在 0% 或 100%。 - - 出图任务的日志现在也能在队列详情页实时查看,并可从详情页直达对应出图结果回看。 -- **训练可定时开始(#356)** - 训练入队时可设定具体时间(如凌晨 3:00)或延迟 N 小时,到点自动进入排队;队列页新增「计划任务」段,显示计划时间与倒计时,可随时「立即开始」或取消。 -### 变更 + - New batch count next to "Generate": enqueue N tasks at once, with auto-incrementing seeds. + - The progress bar now covers every phase — model load, text encoding, sampling and VAE decode — instead of sitting at 0% or 100% for long stretches. + - Generation task logs stream live in the queue detail page, which also links straight back to the resulting images. +- **Scheduled training (#356)** + When enqueueing a training run you can now pick an exact start time (e.g. 3:00 AM) or a delay in hours; the task waits and joins the queue automatically. The queue page gets a "Scheduled" section with the planned time, a countdown, and "Start now" / cancel actions. +### Changed -- **显存调度统一为资源档位准入(#360, #362, #363)** - 什么任务能同时用显卡,现在由后端按三档统一把关:独占(训练 / 正则 AI 先验 / 测试出图 / 评估出图)全系统同时只跑一个;轻量(打标 / 超分 / 评估指标)可与独占任务并行;下载不受限。修复了训练时仍可能同时出图等三个显存准入漏洞。 +- **VRAM scheduling unified into resource-tier admission (#360, #362, #363)** + Which tasks may use the GPU at the same time is now enforced by the backend in three tiers: exclusive (training / reg AI prior / test generation / evaluation sampling) runs one at a time system-wide; light (tagging / upscaling / evaluation metrics) may run alongside; downloads are unrestricted. This closes three admission gaps, including one where generation could still run during training. - - **训练时打标 / 超分 / 评估指标现在默认照常并行**(此前默认推迟)。可在设置里关闭;旧开关 `allow_gpu_during_train` 废弃,新开关为 `light_tasks_during_train`,旧值不迁移。 - - 独占类任务严格按入队顺序执行,不再有隐式插队——所见队列顺序即执行顺序。 - - 训练时测试页「开始生成」不再置灰:现在可以直接提交,排队等训练完成后自动开始。 -- **本地打标落盘格式跟着产物走,不再手选(#353)** - 打标页的「输出格式」选择器移除:本地打标(wd14 / CLTagger)与 LLM 文本模式一律写 `.txt`(触发词在首位),LLM 分类 JSON 模式写 `.json`;图片已有 `.json` 的保持 `.json` 更新。 + - **Tagging, upscaling and evaluation metrics now run alongside training by default** (previously deferred). You can turn this off in Settings; the old `allow_gpu_during_train` switch is deprecated and replaced by `light_tasks_during_train`, without migrating the old value. + - Exclusive tasks run strictly in enqueue order — no more implicit priority; the queue order you see is the execution order. + - "Generate" is no longer greyed out while training: submissions simply queue up and start once training finishes. +- **Local tagging output format now follows the output, no manual choice (#353)** + The output-format selector on the Tagging page is gone: local taggers (wd14 / CLTagger) and LLM text mode always write `.txt` (trigger word first); LLM categorized-JSON mode writes `.json`; images that already have `.json` keep getting `.json` updates. - - `.txt` 标注现在支持 `tag_dropout`(此前只对 JSON 生效);`keep_tokens` 前缀照常免 shuffle 免 dropout,与 kohya 语义一致。 - - 已有的扁平 JSON 数据集不受影响,训练端永久保留读取兼容。 + - `.txt` captions now honor `tag_dropout` (previously JSON-only); the `keep_tokens` prefix stays exempt from shuffle and dropout, matching kohya semantics. + - Existing flat-JSON datasets are unaffected — the training side keeps reading them indefinitely. -### 修复 +### Fixed -- **本地打标 + JSON 输出的数据集不再以空标注参训(#352)** - 本地打标器写出的扁平 JSON 此前会被训练端误判格式、解析失败后静默以空 caption(连触发词都没有)参与训练。现在读取正确,且开训前会预检所有 JSON 标注,有解析失败的直接列出文件、拒绝开训。 -- **`lora_reg_dims` 分层 rank 此前对 `algo=lora` 静默失效(#350)** - 自 v0.10.0 起,`algo=lora` + `lora_reg_dims` 的训练分层 rank 一直被静默忽略(实际按全局 rank 训练,无任何报错;LoKr 不受影响)。修复后同配置重训会真正按分层 rank 构建网络。 +- **Datasets tagged locally with JSON output no longer train on empty captions (#352)** + Flat JSON written by the local taggers was misdetected by the training side; the parse failure was silently swallowed and those images trained with empty captions (not even the trigger word). Reading is fixed, and a pre-training check now lists any unparsable JSON captions and refuses to start instead of failing silently. +- **`lora_reg_dims` per-layer ranks were silently ignored for `algo=lora` (#350)** + Since v0.10.0, training with `algo=lora` + `lora_reg_dims` silently ignored the per-layer ranks (the network trained at the global rank with no warning; LoKr was unaffected). After the fix, retraining with the same config actually applies the per-layer ranks. - - 注意:旧的此类 checkpoint(统一 rank)在新版本 resume / 继续训练会因形状不匹配报错——去掉 `lora_reg_dims` 配置或重新训练即可。自检:受影响 run 的 `run.log` 里只有 `[lora_reg_dims] skipped N modules` 而没有 `applied rank overrides`。 -- **训练配置页「采样 prompts」现在能正常换行(#364)** - 多 prompt 轮换字段(每行一条)此前敲回车会被立即吃掉,实际没法输入多行。已修复;同页三个采样 prompt 输入框改为随内容自动撑高。 -- **公告栏打开瞬间点别的公告不再被跳回第一篇(#354)** - 打开公告栏后立刻点另一篇时,选中可能被「默认选中第一篇」覆盖回去。已修复。 -- **项目页顶栏滚动时保持固定(#347)** - 项目列表往下滚时页面顶栏不再跟着滚走,与队列页行为一致。 + - Note: resuming or continuing an old checkpoint of this kind (uniform rank) on the new version fails with a shape mismatch — drop the `lora_reg_dims` setting or retrain. To check whether a run was affected: its `run.log` shows only `[lora_reg_dims] skipped N modules` and no `applied rank overrides`. +- **"Sample prompts" on the training config page accepts newlines again (#364)** + The multi-prompt rotation field (one prompt per line) swallowed the Enter key, making multi-line input effectively impossible. Fixed; the three sampling prompt boxes on that page also auto-grow with content now. +- **Announcement center no longer jumps back to the first post (#354)** + Clicking another post right as the announcement center opened could get overridden by the "select first post" default. Fixed. +- **Projects page header stays pinned while scrolling (#347)** + The header no longer scrolls away with the project list, matching the queue page. -### 删除 +### Removed -- **队列导入 / 导出功能移除(#363)** - 队列页的「导出 / 导入」按钮及对应接口移除。任务与数据作业已统一进单一台账,跨机迁移场景请直接迁移 `studio_data`(见设置页的数据目录说明)。 +- **Queue import / export removed (#363)** + The "Export / Import" buttons on the queue page and their endpoints are gone. Tasks and data jobs now live in a single ledger; to move between machines, migrate the `studio_data` directory instead (see the data-directory section in Settings). --- ## v0.16.0 — 2026-06-29 -应用内公告栏 · 访问地址迁回根路径 · 设置页即时生效 · 测试页加载提速 · LoRA 评估(实验性)· 训练与界面多项修复 +In-app announcement center · app moved back to the root URL · instant-apply settings · faster Generate page · LoRA evaluation (experimental) · several training and UI fixes -### 新增 +### Added -- **应用内公告栏:右上角铃铛集中看更新与迁移提示(#336, #337, #339)** - 此前版本更新了什么、撞到行为变化都没有主动提示;现在每次更新会自动弹一次公告,铃铛也可随时打开。 +- **In-app announcement center — a bell in the top-right for updates and migration notes (#336, #337, #339).** + Until now nothing told you what changed in a release or warned you about behavior changes. An announcement now pops up once after each update, and the bell is always there to open. - - 左侧版本列表 + 右侧正文,正文支持完整 markdown(标题 / 列表 / 链接 / 代码)。 - - 更新日志从原来的「去设置里翻」统一进公告栏,并补齐了历史版本的英文(英文用户翻旧版本不再回退中文)。 - - 公告分「更新日志 / 公告 / 迁移」三类;需要你注意或操作的行为变化(如本版的访问地址迁移)会单独置顶。 -- **LoRA 训练评估(实验性):训练后自动给 checkpoint 算质量指标(#138, #319, #322, #323, #324)** - 作为后续评估相关功能的基础先落地。当前指标对动漫域的判别力还有限、仅供参考,默认按需开启,不是本版重点。 + - Version list on the left, post body on the right, with full markdown (headings / lists / links / code). + - The changelog moved from "dig through Settings" into the announcement center. + - Posts are grouped into changelog / notice / migration; behavior changes that need your attention (like this release's URL move) are pinned separately. +- **LoRA training evaluation (experimental) — automatic quality metrics on checkpoints after training (#138, #319, #322, #323, #324).** + Landed as the foundation for upcoming evaluation work. The current metrics have limited discriminative power on anime-domain outputs and are for reference only; they are opt-in and not the focus of this release. - - 训练后自动出评估样图并算 CLIP-T / CLIP-I / DINO-I,另加动漫域的 tag 召回率与角色一致性(CCIP),并与纯底模 baseline 对照看净增益。 - - 可在项目里手动圈定验证集;评估进度 / 日志接入统一的任务日志抽屉。 -- **创建项目可自定义 slug + 跨页保留已选中项目(#333)** - 全中文项目名以前落盘会塌成 `project`、项目一多不好找;选好项目切到测试 / 队列又会丢失选中。 + - After training, eval samples are generated and scored with CLIP-T / CLIP-I / DINO-I, plus anime-domain tag recall and character consistency (CCIP), compared against a base-model baseline to isolate the LoRA's net gain. + - You can hand-pick a validation set per project; eval progress and logs feed the shared task log drawer. +- **Editable project slug on creation, and selected project kept across pages (#333).** + Project names without Latin characters used to collapse to `project` on disk and got hard to tell apart; picking a project and switching to Generate / Queue also dropped the selection. - - 新建项目弹窗加「项目 slug」输入框,实时校验并预览最终落盘名(留空仍按标题自动派生)。 - - 选中的项目切到队列 / 测试等全局页时保留在侧边栏,不用回列表重选。 -- **大图预览底部显示「第几张 / 共几张」(#326)** - 下载 / 预处理 / curation / 正则集 / 训练采样 / XY 网格等处看大图、用 ←/→ 翻页时,底部新增当前位置计数。 + - The new-project dialog adds a "project slug" field that validates live and previews the final on-disk name (leave it blank to derive from the title as before). + - The selected project stays in the sidebar when you move to global pages like Queue or Generate — no need to reselect from the list. +- **Image counter ("N / M") at the bottom of the large-image viewer (#326).** + When browsing large images with ←/→ across Download / Preprocess / curation / regularization / training samples / XY grid, the viewer now shows your position in the sequence. -### 变更 +### Changed -- **访问地址迁回根路径,`/studio/` 前缀移除(#334)** - 打开地址从 `localhost:8765/studio/` 变为 `localhost:8765/`,顺带修好「把容器端口反代到根」这类部署(如 ModelScope / HuggingFace 创空间)打不开的问题。 +- **App moved back to the root URL; the `/studio/` prefix is gone (#334).** + The address changes from `localhost:8765/studio/` to `localhost:8765/`, which also fixes deployments that reverse-proxy the container port to root (e.g. ModelScope / HuggingFace Spaces) failing to load. - - 旧 `/studio/...` 链接本版仍会自动跳转到新地址,下个版本移除,建议更新书签。 - - 详见置顶的「访问地址改为根路径」迁移公告。 + - Old `/studio/...` links still redirect automatically this release and will be removed in the next one — update your bookmarks. + - See the pinned "App moved to the root URL" migration post for details. -### 改进 +### Improved -- **全局设置页改为即时生效,去掉底部保存条(#340)** - 开关 / 下拉改了即时保存,文本框 / 数字框失焦或回车时保存,右下角有保存状态指示,不再需要点「保存」。 +- **Global Settings is now instant-apply; the bottom save bar is gone (#340).** + Toggles and dropdowns save on change; text and number fields save on blur or Enter. A save indicator sits in the bottom corner — no more "Save" button. - - 删 preset、恢复词典默认、移除自定义本地模型等破坏性操作会先弹确认。 -- **测试页打开明显提速(#327, #328)** - 历史图多、项目 / 版本多时打开测试页卡顿的两处根因已修。 + - Destructive actions (delete a preset, restore dictionary defaults, remove a custom local model) ask for confirmation first. +- **The Generate page opens noticeably faster (#327, #328).** + Two causes of slow loads with many history images and many projects / versions are fixed. - - 历史栏改为虚拟滚动,元数据直接读 PNG 文本块(本机实测历史扫描 15.4s → 0.12s)。 - - LoRA 列表改为按需懒加载,不点开 LoRA 选择器就不再发那一批项目 / 版本请求。 - - XY 出图开始后结果定格,中途改坐标轴只影响下次生成,不再串改已生成的结果。 + - The history rail is now virtualized and reads PNG metadata straight from the text chunk (local test: history scan 15.4s → 0.12s). + - The LoRA list loads lazily — opening the page no longer fires that batch of project / version requests unless you open the LoRA picker. + - XY results freeze once generation starts; changing an axis mid-run only affects the next run instead of corrupting results already produced. -### 修复 +### Fixed -- **修复 V100 等 fp16 显卡的训练精度映射 + GradScaler(#335)** - 修正 fp16 路径的 dtype 映射与 GradScaler 持久化,让仅支持 fp16 的显卡(如 V100)能正常训练。 -- **训练缓存阶段的 VAE 分块日志不再刷屏 / 误导(#332)** - 做 latent 缓存时每张图都打一条分块日志(200 张图刷 200 条)、且写着「已用 > 总显存」像是显存不足。分块本身是对的(在规避显存换页崖),本版把这类日志去重并降级,不再刷屏和误导。 -- **补正则 AI 先验取消按钮 + 设置抽屉点侧栏收起 + 版本面板加载防跳(#329)** +- **Fixed fp16 dtype mapping and GradScaler for fp16-only GPUs like the V100 (#335).** + Corrects the fp16 dtype mapping and GradScaler persistence so cards that only support fp16 (e.g. the V100) train correctly. +- **VAE tiling logs during the cache stage no longer spam or mislead (#332).** + During latent caching, every image logged a tiling decision (200 images, 200 lines) reading "used > total VRAM" as if memory were exhausted. The tiling itself is correct (it avoids the VRAM paging cliff); this release de-duplicates and demotes those logs. +- **Added a cancel button for AI-prior generation, made the settings drawer dismiss on sidebar click, and stabilized the version panel on load (#329).** - - 正则集「AI 先验生成」运行中补上取消按钮(此前只有 booru 抓取有)。 - - 设置抽屉点左侧侧边栏也能收起(此前蒙版只盖主内容区)。 - - 版本「更新内容」面板加载 / 切换版本时高度不再跳动。 + - The regularization "AI prior" run now has a cancel button (previously only booru scraping did). + - The settings drawer now dismisses when you click the sidebar, not just the main area. + - The version "what's new" panel no longer jumps in height while loading or switching versions. --- ## v0.15.0 — 2026-06-23 -多分辨率训练 + CLTagger V2 + 自定义底模 + 模型根目录可迁移 + 设置页重做 + 错误提示本地化 + 自更新/ONNX 多项修复 - -### 新增 - -- **多分辨率训练:一次混训多个基准分辨率 + 桶长宽比可配 + 桶分布预览(#296)** - 官方示范 LoRA 常用多个分辨率(如 512/768/1024)混训改善高分推理;此前炉子只支持单一基准分辨率,大长宽比的作品还会被写死的 2:1 桶强裁。本版在保留原有按长宽比分桶的前提下加入多分辨率训练。 - - - 训练分辨率可填多个值(如 `512, 768, 1024`),无前缀文件夹的每张图在各分辨率各训一遍。 - - 文件夹名加 `1024px_` 前缀(如 `1024px_2_data`)可固定该文件夹分辨率、覆盖全局列表。 - - 新增 `aspect_ratio_limit`(默认 2.0),调大让长图按原比例进桶、不再被强裁。 - - 训练页右栏显示实际桶分布(每桶尺寸 + 有效图数),样本凑不够一个 batch 的桶会标红提醒(整桶会被丢弃)。 - - 顺手修:基准分辨率设为 512 时原来只剩一个方桶(分桶失效、所有图被压成正方),现已按基准分辨率正确生成桶范围。 -- **新增 CLTagger V2 打标器(gated 仓库 + 外置权重,V1 行为不变)(#297)** - 打标器 CLTagger 新增 V2 版本(`cella110n/cl_tagger_v2`)支持。V2 的权重和词表结构与 V1 不同(2GB 权重是外置文件、独立词表、RGB 预处理)。 - - - V2 是 gated 仓库,下载时自动带上你在密钥页填的 HuggingFace token;缺 token 或未授权时给出可操作的提示。 - - 外置权重文件纳入完整性校验,避免只下到一半就被误判「可用」、到加载时才黑盒报错。 - - V1(`cl_tagger_1_02`)行为完全不变,可继续使用;在 Settings 打标区切换版本。 -- **可注册本地微调权重为底模 + 正则先验/测试出图临时换底模(#302, #309)** - 此前底模只能从官方提供的几个版本里选,想在自己的微调权重上测试出图、或拿本地下载的 `.safetensors` 当底模没有第一方入口。本版补上。 - - - Settings → 训练 → Anima 主模型可「添加本地模型」,把盘上已有的 `.safetensors` 注册成底模(只记路径,不复制、也不删原文件)。 - - 正则集 AI 先验出图、测试页出图各加一个「底模」下拉,可临时换别的已下载版本(含本地微调权重)来生成,不动全局设置;下拉只列磁盘上确实存在的模型。 - - 顺手修:测试/出图页此前其实没跟随 Settings 里选的底模版本(一直写死用一个版本),现在真正跟随,在微调权重上测试才生效。 -- **模型根目录可改到其它盘 + 一键迁移已下载权重(无需重启)(#304)** - 下载的模型权重默认落在仓库下的 `models/`,越下越大。和 studio_data 数据目录一样,现在可以把模型根目录改到其它盘,并一键把已下载的权重复制过去。 - - - Settings → 系统 → 存储位置新增「模型根目录」卡;更改位置 → 扫描确认(文件数 / 总大小)→ 进度条复制。 - - 复制后即刻生效、无需重启(这点与 studio_data 迁移不同);旧目录原样保留,旧训练配置里写死的旧绝对路径仍能解析。 - - 有训练 / 打标等任务在跑时迁移会被拒绝,避免搬动过程中文件被截到写一半。 - -### 变更 - -- **Settings 加密钥 tab 集中凭证 + 下载源改为按模型类型独立选(#301)** - 设置页的凭证和下载源做了重组。 - - - 新增「密钥」tab 集中 HuggingFace / ModelScope / Gelbooru / Danbooru 凭证(此前散在训练 / 数据集 / 监控等多个 tab,比如 CLTagger 要用的 HuggingFace token 得去训练页找)。 - - 下载源从「一个全局 HF/ModelScope 开关」改成各模型卡按类型独立选(训练模型 / WD14 / 放大器各一个下拉);老的全局设置会作为初始值自动迁移,不静默回退。 - - 下载失败时的错误提示改得更可操作(如 gated 仓库缺 token 直接提示去填 token),不再只在终端可见。 - - 原本挂在 Gelbooru 下、实际被所有下载 / 上传共用的几个字段(保留 tag、转 PNG、去 alpha 通道)搬到全局下载设置下,旧值自动迁移不丢。 - -### 改进 - -- **错误提示按 app 语言本地化(中/英),ADR 0009 错误信封迁移完成(#294)** - 此前 app 虽是中英双语,但出错时的 toast 文案写死中文,英文用户看到的是中文错误信息。本版起错误提示按 app 语言本地化。 - - - 覆盖约 120 个错误码,中英双语;下载失败等场景的错误信息也更具体、可操作(如 gated 仓库缺 token 会直接提示去填 token)。 - - 后端每个错误响应统一带语义错误码 + trace ID,前端按错误码查本地化文案;暂时还没覆盖到的错误码会回退显示原始英文信息,不会变空白。 - - 至此 ADR 0009 错误信封的三阶段迁移(统一信封 → 错误码本地化 → 清理旧字段)全部完成。 -- **Settings 页字段标题/布局/开关样式统一整理 + 移除冗余选项(#306)** - 设置页做了一轮 UI 整理,统一长期积累的不一致。 - - - 字段标题改成人话(不再直接用配置字段名,如 `exclude_tags` 显示「排除 Tags」、`api_key` 显示「API Key」)。 - - 布尔开关统一改成启用/关闭下拉(修上下对齐),表单控件宽度和样式统一,内容区最大宽度放宽到适配宽屏。 - - 系统页「重新运行首次引导」并入「服务」区;存储 / 模型根目录地址从代码块改成可选中复制的只读输入框。 - - 打标器手动指定本地模型目录的选项移除(模型根目录已能统一指定落点,该选项冗余);旧配置里的残留值加载时自动丢弃。 -- **训练监控采样图角标显示 epoch+step + 移除失效的独立监控按钮(#307)** - - 训练监控页的采样缩略图 / 大图角标现在显示 epoch + step(此前 epoch 只藏在文件名里,要点开放大看文件名才知道是第几轮)。 - - 缩略图角标从叠在 64px 小图上(盖住画面)改到图下方一行紧凑显示,图本身保持干净。 - - 移除一直失效的「独立监控」弹出按钮(漏了 app 路径前缀、点击会落到错误页);侧边栏「监控」入口本就带任务选择,这个按钮是冗余的。 - -### 修复 - -- **修复 Linux 装 onnxruntime-gpu 误装 CUDA 13 build 致打标崩溃(#311)** - Linux 用户装 ONNX CUDA 版后,打标直接报 `libcudart.so.13` 找不到并误报「未安装 onnxruntime」——其实包装上了,只是加载失败。 - - - 根因:新版 onnxruntime-gpu(1.27 起)的 PyPI 默认 wheel 切到了 CUDA 13,而本项目整套按 CUDA 12 设计(torch 是 cu128、预加载的库都是 `.so.12`),混用导致加载期就崩。 - - 本版按 torch 的 CUDA 大版本分流安装对应的 onnxruntime-gpu build,CUDA 12 钉到 1.20–1.26 这条线;将来 torch 升到 CUDA 13 时会自动切到对应 build。 - - Settings → ONNX Runtime 信息盒加一行显示当前 torch 的 CUDA 大版本,错位时带提示。 - - 已经装坏的用户可手动 `pip install "onnxruntime-gpu==1.20.1"` 后重启立即恢复;DirectML / CPU / Windows 路径不受影响。 -- **自更新加固:npm 改写 lockfile 不再阻断升级 + 老用户升级不重下模型数据(#308, #310)** - WebUI 自更新(`git fetch` + `git reset --hard`)的两类可靠性问题修复。 - - - 新版 npm 在启动期会自动给 `package-lock.json` 追加 `peer` 等元数据,让这个受版本控制的文件凭空变「脏」,把「工作树必须干净」的更新前置检查卡死、更新按钮点不动。本版起这类自动改写不再算脏、不阻断更新。 - - 若工作树真有手动改动,更新前会弹「强制覆盖并更新」确认(回滚也共用),避免 reset 静默覆盖用户的真实改动。 - - 早期版本曾把几个下载得到的模型数据文件(tokenizer / 配置,十几 MB)误入 git 库、后来转成 gitignore;从那个时期的老版本升级时 `reset --hard` 会把它们删掉、逼用户重下。本版在升级前备份、升级后还原这几个文件,老用户零感知(大权重文件从未入过库,不受影响)。 -- **修复小数据集 grad_accum 尾组梯度被丢(每轮少训一步)(#305)** - 数据集每轮的 batch 数不能被梯度累积步数(`grad_accum`)整除时,最后不满一组的那几个 batch 的梯度会被丢弃、不计入训练。 - - - 极端情况:3 张图 / `grad_accum=4` 时整轮一个 update step 都没有、完全不训练。 - - 多轮训练时尾组梯度还会泄漏进下一轮、并被错误归一。 - - 本版起每轮末尾不满一组也照常 step,并按实际 batch 数归一(末步梯度不被削弱)。已能整除、或 `grad_accum=1` 的配置行为不变。 -- **修复关缓存 + batch>1 + 非方图组合训练 torch.stack 崩溃(#299)** - 关闭 latent 缓存(`cache_latents=False`)+ batch 大于 1 + 训练集长宽比不全相同时,训练会直接报 `stack expects each tensor to be equal size` 崩溃。 - - - 该路径此前漏了按长宽比分桶,一个 batch 里混入不同尺寸的图,拼接时崩。 - - 缓存路径(默认开启)早已规避;本版让非缓存路径也按桶分批。多分辨率训练(本版新增)关缓存时尤其容易触发,已一并修好。 +Multi-resolution training + CLTagger V2 + custom base models + a movable model root directory + Settings redesign + localized error messages + various self-update / ONNX fixes + +### Added + +- **Multi-resolution training: mix several base resolutions in one run + configurable bucket aspect ratios + a bucket-distribution preview (#296)** + Official reference LoRAs often mix several resolutions (e.g. 512/768/1024) to improve high-resolution inference; until now the trainer only supported a single base resolution, and works with extreme aspect ratios were hard-cropped by a fixed 2:1 bucket. This version adds multi-resolution training while keeping the existing aspect-ratio bucketing. + + - The training resolution can take multiple values (e.g. `512, 768, 1024`); each image in a folder with no prefix is trained once at each resolution. + - Adding a `1024px_` prefix to a folder name (e.g. `1024px_2_data`) pins that folder's resolution, overriding the global list. + - New `aspect_ratio_limit` (default 2.0): raise it to let tall images bucket at their original ratio instead of being hard-cropped. + - The right column of the training page shows the actual bucket distribution (size and effective image count per bucket); buckets that can't fill a batch are flagged in red (the whole bucket is dropped). + - Also fixed: setting the base resolution to 512 previously left only a single square bucket (bucketing broke and every image was squashed to square); buckets are now generated correctly from the base resolution. +- **New CLTagger V2 tagger (gated repo + external weights; V1 behavior unchanged) (#297)** + The CLTagger tagger gains support for a V2 version (`cella110n/cl_tagger_v2`). V2's weights and tag-vocabulary structure differ from V1 (a 2GB weights file kept externally, a separate vocabulary, RGB preprocessing). + + - V2 is a gated repo; downloads automatically include the HuggingFace token you entered on the Secrets tab, with an actionable message when the token is missing or unauthorized. + - The external weights file is included in the integrity check, so a half-finished download won't be misjudged "ready" and then fail opaquely at load time. + - V1 (`cl_tagger_1_02`) behaves exactly as before and can keep being used; switch versions in the tagging section of Settings. +- **Register local fine-tuned weights as a base model + temporarily swap the base model for AI-prior and test image generation (#302, #309)** + Until now the base model could only be chosen from the few official versions; there was no first-party entry point for generating test images on your own fine-tuned weights or using a locally downloaded `.safetensors` as the base model. This version adds it. + + - Settings → Training → the Anima base model now offers "add local model," registering an existing `.safetensors` on disk as a base model (it only records the path — nothing is copied or deleted). + - The regularization-set AI-prior generation and the Generate page each gain a "base model" dropdown for temporarily switching to another downloaded version (including local fine-tuned weights) without touching the global setting; the dropdown lists only models that actually exist on disk. + - Also fixed: the test / Generate page wasn't actually following the base model version chosen in Settings (it was hard-wired to one version); it now follows it for real, so testing on fine-tuned weights takes effect. +- **The model root directory can move to another drive + a one-click migration of already-downloaded weights (no restart needed) (#304)** + Downloaded model weights default to `models/` under the repo and keep growing. Just like the studio_data directory, you can now move the model root directory to another drive and copy the already-downloaded weights over in one click. + + - Settings → System → Storage location adds a "model root directory" card; change the location → confirm the scan (file count / total size) → copy with a progress bar. + - It takes effect immediately, with no restart (unlike the studio_data migration); the old directory is left intact, and old absolute paths hard-wired into old training configs still resolve. + - Migration is refused while training, tagging, or other tasks are running, to avoid files being truncated mid-write during the move. + +### Changed + +- **A Secrets tab in Settings centralizes credentials, and the download source is now chosen per model type (#301)** + Credentials and download sources in Settings have been reorganized. + + - A new "Secrets" tab centralizes HuggingFace / ModelScope / Gelbooru / Danbooru credentials (previously scattered across the training / dataset / monitor tabs — for example, the HuggingFace token CLTagger needs had to be found on the training page). + - The download source changes from a single global HF/ModelScope toggle to a per-type choice on each model card (a separate dropdown for the training model / WD14 / upscaler); the old global setting migrates automatically as the initial value rather than silently reverting. + - Error messages on download failure are now more actionable (e.g. a gated repo missing a token prompts you to enter one directly), instead of only being visible in the terminal. + - A few fields that were filed under Gelbooru but actually shared by all downloads / uploads (keep tags, convert to PNG, strip the alpha channel) move under the global download settings, with old values migrated automatically and nothing lost. + +### Improved + +- **Error messages are localized to the app language; the ADR 0009 error-envelope migration is complete (#294)** + Error toasts used to be hard-coded in a single language. As of this version, error messages are localized to the app language. + + - About 120 error codes are covered; error messages for cases like download failures are also more specific and actionable (e.g. a gated repo missing a token prompts you to enter one directly). + - Every backend error response now carries a semantic error code + a trace ID, and the front end looks up the localized message by error code; codes not yet covered fall back to showing the original English message rather than going blank. + - With this, all three phases of the ADR 0009 error-envelope migration (unified envelope → error-code localization → cleanup of old fields) are complete. +- **Settings field titles / layout / toggle styles unified + redundant options removed (#306)** + Settings got a round of UI cleanup to unify inconsistencies accumulated over time. + + - Field titles are now plain language (no longer the raw config field name — e.g. `exclude_tags` shows "Excluded Tags," `api_key` shows "API Key"). + - Boolean toggles are unified into an enable/disable dropdown (fixing vertical alignment); form-control widths and styles are unified, and the content area's max width is widened to suit wide screens. + - The System page's "rerun first-run setup" is folded into the "Services" section; the storage / model-root paths change from code blocks to selectable, copyable read-only inputs. + - The tagger's "manually specify a local model directory" option is removed (the model root directory can already set the landing location uniformly, making it redundant); leftover values in old configs are dropped automatically on load. +- **Training-monitor sample thumbnails show epoch + step in their badge + the broken standalone monitor button is removed (#307)** + - The sample thumbnails / full images on the training monitor page now show epoch + step in their badge (epoch was previously hidden only in the file name, so you had to open the full image and read the file name to know which round it was). + - The thumbnail badge moves from overlaying the 64px thumbnail (covering the image) to a compact line below it, keeping the image itself clean. + - Removes the long-broken "standalone monitor" popout button (it was missing the app path prefix, so clicking it landed on an error page); the sidebar "Monitor" entry already includes task selection, making this button redundant. + +### Fixed + +- **Fixed onnxruntime-gpu installing the CUDA 13 build by mistake on Linux, crashing tagging (#311)** + After installing the ONNX CUDA build, Linux users saw tagging fail with `libcudart.so.13` not found and a misleading "onnxruntime not installed" — the package was actually installed, it just failed to load. + + - Root cause: the default PyPI wheel for newer onnxruntime-gpu (from 1.27 on) switched to CUDA 13, while this project is built end-to-end for CUDA 12 (torch is cu128, the preloaded libraries are all `.so.12`), and mixing them crashes at load time. + - This version installs the matching onnxruntime-gpu build based on torch's CUDA major version, pinning CUDA 12 to the 1.20–1.26 line; when torch later moves to CUDA 13, it will switch to the corresponding build automatically. + - The Settings → ONNX Runtime info box adds a line showing torch's current CUDA major version, with a note when there's a mismatch. + - Users who already have a broken install can run `pip install "onnxruntime-gpu==1.20.1"` and restart to recover immediately; the DirectML / CPU / Windows paths are unaffected. +- **Self-update hardening: npm rewriting the lockfile no longer blocks upgrades + upgrading from old versions no longer re-downloads model data (#308, #310)** + Two kinds of reliability issues in the WebUI self-update (`git fetch` + `git reset --hard`) are fixed. + + - Newer npm automatically appends metadata like `peer` to `package-lock.json` at startup, making this version-controlled file "dirty" out of nowhere and stalling the "working tree must be clean" pre-update check, so the update button wouldn't respond. As of this version, this kind of automatic rewrite no longer counts as dirty or blocks updates. + - If the working tree really does have manual changes, a "force overwrite and update" confirmation appears before updating (shared with rollback), so a reset won't silently overwrite your real changes. + - Early versions had mistakenly committed a few downloaded model data files (tokenizer / config, a dozen-odd MB) to git and later moved them to gitignore; upgrading from a version of that era, `reset --hard` would delete them and force a re-download. This version backs up and restores those files around the upgrade, so old users won't notice (the large weights files were never committed and are unaffected). +- **Fixed grad_accum dropping the last partial group's gradients on small datasets (one fewer step per round) (#305)** + When a dataset's per-round batch count isn't divisible by the gradient-accumulation step count (`grad_accum`), the gradients of the last under-filled group of batches were discarded and not counted toward training. + + - Extreme case: with 3 images / `grad_accum=4`, an entire round had no update step at all and didn't train. + - Across multiple rounds, the last group's gradients also leaked into the next round and were normalized incorrectly. + - As of this version, an under-filled group at the end of each round still steps normally and is normalized by the actual batch count (so the last step's gradient isn't weakened). Configs that already divide evenly, or use `grad_accum=1`, behave the same as before. +- **Fixed a torch.stack crash when training with caching off + batch>1 + non-square images (#299)** + With latent caching off (`cache_latents=False`) + a batch larger than 1 + a training set whose aspect ratios aren't all the same, training crashed outright with `stack expects each tensor to be equal size`. + + - This path was missing aspect-ratio bucketing, so a batch mixed images of different sizes and crashed at concatenation. + - The cached path (on by default) already avoided this; this version makes the non-cached path batch by bucket too. Multi-resolution training (new in this version) is especially prone to triggering it with caching off, and it's fixed along with the rest. --- ## v0.14.0 — 2026-06-19 -新增 SOAP 优化器、LeapAlign 自蒸馏、SRA v2 对齐训练 + Generate 页改版 + 大显存 VAE 卡死修复 - -### 新增 - -- **新增 SOAP / Schedule-Free SOAP 优化器,矩阵型 adapter 收敛更快(#271)** - - 两个新 optimizer_type:`soap`(Adam in the Shampoo eigenbasis)对 LoRA / LoKr 等低秩矩阵因子的拟合速度通常快于 AdamW / Prodigy,代价是更多 state 显存。 - - `soap_sf` 在 SOAP 上叠加 Schedule-Free 轨迹,免 LR 调度、缓解风格突变;与 PPSF 同款约束,lr_scheduler 必须设为 none。 -- **新增 LeapAlign 两步跳跃自蒸馏训练,含 FlowBP 四种轨迹变体(#277, #278)** - - 通过 `leap_enabled` 启用(位于训练高级选项)。在 rectified flow 上构造两步跳跃轨迹,让跳跃预测出的 latent 逼近训练数据的真实 latent,无需奖励模型或完整采样轨迹,适配 LoRA 微调。 - - `leap_ratio` 控制与标准训练的混合比例;`leap_variant` 提供 original / sparse / bridge / lagrange 四种轨迹积分方式(沿 FlowBP 思路),默认 original 行为不变。 - - 与 InfoNoise、loss_weighting、huber loss 互斥(前端联动灰显),启用时会跳过这些机制。 -- **新增 SRA v2 自表征对齐辅助 loss,对齐中间表征到 VAE latent(#279)** - - 通过 `sra_enabled` 启用(默认关闭,位于训练高级选项)。把某个 transformer block 的中间 hidden state 投影回 VAE latent 空间并与 clean latent 对齐,作为辅助正则帮助收敛,不增加额外的 diffusion 前向。 - - `sra_weight`(默认 0.2)控制权重,支持 none / linear / cosine / jump 衰减调度;投影 MLP 只在训练期存在,不写入导出的 LoRA。 - - 默认开启 `sra_normalize`:对齐前对预测与目标各自做 per-sample 标准化,避免视频 VAE latent 尺度过大导致对齐 loss 失衡崩坏。 - - 仅接在标准 rectified flow 路径,不与 LeapAlign 同用。 -- **项目卡片可编辑标题与备注,操作图标改为悬停显示(#286)** - - 项目卡片新增铅笔按钮,可修改标题和备注(备注可用于搜索 / 分类);slug 与 LoRA 输出名固定不变,改标题不影响任何产物文件名或路径。 - - 卡片底部操作图标(编辑 / 归档 / 恢复 / 删除)改为鼠标悬停或键盘聚焦时淡入,平时更清爽。 -- **正则集新增全局默认排除 tag,换项目/版本不再重输(#285)** - - Settings → 数据集 → 正则集新增「默认排除 tag」,正则集生成页在该 build 无本地记录时用它作为初始排除种子(watermark / signature / username 等常排 meta tag 不必每次重敲)。 - - 仅作种子:进页面后仍可逐 tag 增删,不是硬过滤。 - -### 变更 - -- **Generate 页左侧配置改分页 tab + 提示词选择器加缩略图预览(#280)** - - 左侧 LoRA/XY · 提示词 · 配置 三块从竖向堆叠改为一张统一卡片内的分页 tab,一次只看一页,当前页记忆到本地。 - - 提示词选择器每行加缩略图、底部加大图预览,方便与右侧生成结果比对。 - - XY 轴卡片整理:轴名单独成行、移除按钮移到右端不再与下拉同行;权重支持逗号一次输入(如 `0.2, 0.3, 0.4`)。 - -### 修复 - -- **修复大显存卡训练采样/出图时 VAE 偶发卡死上百秒(#281)** - - 大显存卡(如 32G)整图 VAE encode/decode 工作集逼近显存约一半时会触发系统显存换页,单次操作从不到 1 秒退化到上百秒、系统提交内存暴涨 20-30G,且不抛干净的 CUDA OOM,旧的「报错才分块」兜底救不到。 - - 新增 `vae_tiling` 配置(auto / on / off,默认 auto):按实时可用显存预判,越过崖时主动分块 encode/decode,没越崖仍走整图快路径、无性能惩罚。 -- **修复 LLM 打标器提示词模板输入一个字就失焦(#291)** - - LLM 打标器预设的提示词模板(消息编辑器)里,输入框每打一个字就失去焦点、无法连续输入,必须反复重新点击。本版修复,可正常连续输入。 - - 同一编辑器在打标页和 Settings 两处共用,均已修复。 -- **修复 DirectML 版 onnxruntime 永久误报需重启 Studio(#292)** - - 装了 onnxruntime-directml(DirectML 后端)的用户,Settings → ONNX Runtime 卡片持续显示「需重启 Studio / 当前进程仍在用旧的」,重启多少次都消不掉,但 DirectML 其实已正常生效。 - - 原因是该包的版本号与其内部 onnxruntime 核心版本本就不同,旧逻辑据此误判需要重启;本版改为按实际加载的执行后端(EP)判断,不再误报。 -- **修复恢复/启动训练后暂停按钮需切页才出现(#287)** - - 恢复(terminal / paused → resume)或刚启动训练后,监控页 / 队列页 / 概览页的暂停按钮不出现,必须切到别的页面再切回来才刷新。 - - 三页改为监听首个 epoch 备份落盘等事件实时刷新按钮状态,无需切页。 -- **修复 flash_attn 自动安装选到最旧 wheel 致新显卡无 kernel(#289)** - - 自动选 wheel 时评分平手会退回字母序、首选了最旧版本(如 2.6.3),在较新显卡(如 5090 / Blackwell sm_120)上没有对应 kernel,导致 flash_attn 全程回退并刷 "only supports Ampere GPUs or newer" 误导信息。 - - 修复后评分平手时偏向更新版本,自动安装与候选列表都以最新在前。 +New SOAP optimizer, LeapAlign self-distillation, and SRA v2 alignment training + Generate page redesign + fix for VAE stalls on high-VRAM cards + +### Added + +- **New SOAP / Schedule-Free SOAP optimizers for faster convergence on matrix-type adapters (#271)** + - Two new optimizer types: `soap` (Adam in the Shampoo eigenbasis) usually fits the low-rank matrix factors of LoRA / LoKr faster than AdamW / Prodigy, at the cost of more state VRAM. + - `soap_sf` layers a Schedule-Free trajectory on top of SOAP — no learning-rate schedule needed, and it eases abrupt style shifts. Like PPSF, it requires the scheduler to be set to none. +- **New LeapAlign two-step leap self-distillation training, with four FlowBP trajectory variants (#277, #278)** + - Turn it on with `leap_enabled` (under the advanced training options). It builds a two-step leap trajectory on rectified flow so the leap-predicted latent approaches the real latent of the training data — no reward model or full sampling trajectory required, and it fits LoRA fine-tuning. + - `leap_ratio` controls how much it mixes with standard training; `leap_variant` offers four trajectory-integration methods (following the FlowBP approach): original / sparse / bridge / lagrange, with original as the default to keep behavior unchanged. + - Mutually exclusive with InfoNoise, loss_weighting, and huber loss (grayed out via the linked front-end controls); these mechanisms are skipped when LeapAlign is on. +- **New SRA v2 self-representation alignment auxiliary loss, aligning intermediate representations to the VAE latent (#279)** + - Turn it on with `sra_enabled` (off by default, under the advanced training options). It projects the intermediate hidden state of one transformer block back into the VAE latent space and aligns it with the clean latent, acting as an auxiliary regularizer that aids convergence without an extra diffusion forward pass. + - `sra_weight` (default 0.2) controls the weight and supports none / linear / cosine / jump decay schedules; the projection MLP exists only during training and isn't written into the exported LoRA. + - `sra_normalize` is on by default: before alignment it standardizes the prediction and target per sample, preventing oversized video-VAE latent scales from throwing the alignment loss off balance. + - Only attaches to the standard rectified-flow path; not used together with LeapAlign. +- **Project cards can now edit their title and notes, and the action icons appear on hover (#286)** + - Project cards gain a pencil button for editing the title and notes (notes can be used for search / categorization); the slug and LoRA output name stay fixed, so changing the title doesn't affect any output file name or path. + - The action icons at the bottom of the card (edit / archive / restore / delete) now fade in on mouse hover or keyboard focus, for a cleaner look the rest of the time. +- **Regularization sets gain a global default set of excluded tags, so you don't re-enter them when switching projects or versions (#285)** + - Settings → Dataset → Regularization adds a "default excluded tags" field; the regularization set generation page uses it as the initial exclusion seed when that build has no local record yet (common meta tags like watermark / signature / username no longer need to be retyped every time). + - It's only a seed: you can still add or remove individual tags on the page — it's not a hard filter. + +### Changed + +- **The Generate page's left-hand config moves to paged tabs, and the prompt picker gains thumbnail previews (#280)** + - The three blocks on the left — LoRA/XY · Prompt · Config — change from a vertical stack to paged tabs inside one unified card, showing one page at a time, with the current page remembered locally. + - The prompt picker adds a thumbnail per row and a larger preview at the bottom, making it easier to compare against the generation results on the right. + - The XY-axis card is tidied up: the axis name gets its own row, the remove button moves to the right end instead of sharing a row with the dropdown, and weights accept a single comma-separated entry (e.g. `0.2, 0.3, 0.4`). + +### Fixed + +- **Fixed occasional VAE stalls of up to hundreds of seconds during sampling / image generation on high-VRAM cards (#281)** + - On high-VRAM cards (e.g. 32G), when the working set of a full-image VAE encode/decode approaches about half of VRAM, it triggers system VRAM paging — a single operation degrades from under a second to hundreds of seconds, committed system memory spikes by 20-30G, and it doesn't raise a clean CUDA OOM, so the old "only tile after an error" fallback couldn't catch it. + - Adds a `vae_tiling` setting (auto / on / off, default auto): it predicts based on live available VRAM and proactively tiles the encode/decode when crossing the cliff, while staying on the fast full-image path with no performance penalty when it doesn't. +- **Fixed the LLM tagger prompt-template input losing focus after a single character (#291)** + - In the LLM tagger's preset prompt template (the message editor), the input lost focus after every character typed, making continuous typing impossible and forcing repeated clicking. This is now fixed and typing works normally. + - The same editor is shared between the tagging page and Settings; both are fixed. +- **Fixed the DirectML build of onnxruntime permanently reporting that Studio needs a restart (#292)** + - Users with onnxruntime-directml (the DirectML backend) installed saw the Settings → ONNX Runtime card persistently showing "restart Studio / the current process is still using the old one," which no number of restarts could clear — even though DirectML was actually working fine. + - The cause: that package's version number simply differs from its internal onnxruntime core version, and the old logic misjudged a restart was needed based on it. This version judges by the actually loaded execution provider (EP) instead, so it no longer reports falsely. +- **Fixed the pause button only appearing after switching pages once training resumed or started (#287)** + - After resuming (terminal / paused → resume) or just starting training, the pause button didn't appear on the Monitor / Queue / Overview pages — you had to switch to another page and back for it to refresh. + - All three pages now listen for events such as the first epoch backup being written to disk and refresh the button state in real time, with no page switch needed. +- **Fixed flash_attn auto-install picking the oldest wheel, leaving newer GPUs without a kernel (#289)** + - When auto-selecting a wheel, score ties fell back to alphabetical order and preferred the oldest version (e.g. 2.6.3), which has no matching kernel on newer GPUs (e.g. 5090 / Blackwell sm_120) — causing flash_attn to fall back entirely and spam the misleading "only supports Ampere GPUs or newer" message. + - After the fix, score ties favor the newer version, and both the auto-install and the candidate list put the newest first. --- ## v0.13.3 — 2026-06-17 -修复 Python 3.10 下浏览器上传图片 zip 报错 +Fixes an error when uploading an image zip from the browser on Python 3.10 -### 修复 +### Fixed -- **修复 Python 3.10 下浏览器上传图片 zip 报错、图片无法导入(#284)** - - 在 Python 3.10 运行环境下,从训练集页面用浏览器上传图片 zip 包会报错失败、图片无法导入(单张图片上传不受影响);本版修复。Python 3.11 及以上版本不受此问题影响。 +- **Fixed an error uploading an image zip from the browser on Python 3.10, which prevented images from being imported (#284)** + - On Python 3.10, uploading a zip of images from the dataset page failed with an error and the images couldn't be imported (uploading single images was unaffected). This is now fixed. Python 3.11 and later were never affected. --- ## v0.13.2 — 2026-06-15 -首次启动引导放大模型下载修复 + Flash Attention 装失败错误信息明确化 +First-run onboarding upscaler-model download fix + clearer messages when Flash Attention fails to install -### 改进 +### Improved -- **Flash Attention 装失败的错误信息更明确,CPU 版 PyTorch 提前提示重装(#275)** - - Flash Attention 安装失败大多数情况实际是因为 PyTorch 装的是 CPU 版(flash_attn 必须配 CUDA 版 PyTorch),之前只能看到「Internal Server Error」加 trace_id 看不出原因。 - - 本版起在 Settings → 训练 → Flash Attention 段和首次启动引导里检测到 CPU 版 PyTorch 时,直接显示「先去 PyTorch 段重装 CUDA 版」的提示并禁用 flash_attn 安装按钮,避免无效下载。 - - 其它意外错误(GitHub Releases 拉不到、wheel 不兼容等)也会带具体类型回显,整段 UI 不再因为后端意外塌成「加载失败」。 +- **Clearer error messages when Flash Attention fails to install, with an early prompt to reinstall if you're on CPU PyTorch (#275)** + - Flash Attention install failures are usually caused by having the CPU build of PyTorch (flash_attn requires the CUDA build), but previously all you saw was "Internal Server Error" with a trace_id and no hint at the cause. + - As of this release, when the CPU build of PyTorch is detected in the Settings → Training → Flash Attention section and in first-run onboarding, you get a clear "reinstall the CUDA build in the PyTorch section first" message and the flash_attn install button is disabled, avoiding a pointless download. + - Other unexpected errors (GitHub Releases unreachable, an incompatible wheel, and so on) now report their specific type too, so the whole section no longer collapses into "Failed to load" because of a backend surprise. -### 修复 +### Fixed -- **修复首次启动引导里的放大模型下载报错(#275)** - - 之前点放大模型条目的「重试」永远报 `unknown model_id 'upscalers'` 卡住,想在引导里一键装放大模型(4x-AnimeSharp 等)的用户只能绕去 Settings 才能下载;本版起恢复一键下载。 +- **Fixed an error downloading upscaler models from first-run onboarding (#275)** + - Previously, clicking "Retry" on an upscaler-model entry always got stuck on `unknown model_id 'upscalers'`, so anyone wanting to install upscaler models (4x-AnimeSharp and the like) from onboarding had to detour through Settings; one-click downloads work again as of this release. --- ## v0.13.1 — 2026-06-13 -数据集大文件上传性能修复 + 测试出图临时缓存加密 +Dataset large-file upload performance fix + encryption for the test-image temporary cache -### 安全 +### Security -- **测试出图临时缓存改加密落盘,避免云训练时被扫盘识别(#273)** - - 部分云厂商会扫盘按文件名 / PNG header / 内容分类抓敏感图。本版起测试出图临时缓存(不开自动落盘的那一支)改为加密落盘,文件无后缀、首字节为高熵随机字节,扫盘工具识别不出是图。 - - 加密密钥只在服务器进程内存里生成,进程退出后即丢失;下次启动自动清理上一次的残留文件,即使断电 / 强杀也不会留可读副本。 - - Settings 里开启「测试图片自动落盘」的明文 PNG 路径不变(用户主动选择保存的图本就应该可见 / 可拖去 ComfyUI)。 +- **The test-image temporary cache is now written to disk encrypted, so it can't be identified by disk scans during cloud training (#273)** + - Some cloud providers scan disks to flag sensitive images by filename, PNG header, or content. As of this release the test-image temporary cache (the branch where automatic saving is off) is written encrypted: the files have no extension and start with high-entropy random bytes, so disk scanners can't tell they're images. + - The encryption key is generated only in the server process's memory and is lost when the process exits; the next startup automatically cleans up leftover files from the previous run, so even after a power loss or force-kill no readable copy remains. + - The plaintext PNG path used when you enable "automatically save test images" in Settings is unchanged (images you deliberately choose to save should of course stay visible and draggable into ComfyUI). -### 改进 +### Improved -- **数据集上传进度在底部日志抽屉实时显示(#273)** - - 之前上传只能看到进度条停在 100% 然后无反馈等待;现在 zip 内每 25 张 / 5 秒触发一行进度,跟下载任务并列在底部抽屉里。 - - 单张图处理超过 1 秒会额外 emit 一行 `[slow X.Xs: 文件名]`,方便定位拖后腿的素材。 +- **Dataset upload progress now shows live in the bottom log drawer (#273)** + - Previously the progress bar would sit at 100% and leave you waiting with no feedback; now a progress line is emitted every 25 images or 5 seconds as the zip is processed, shown alongside download tasks in the bottom drawer. + - Any single image that takes over a second to process emits an extra `[slow X.Xs: filename]` line, making it easy to spot the assets holding things up. -### 修复 +### Fixed -- **数据集上传大文件由 40 分钟降到 1-2 分钟 + 上传期间不再卡死页面(#273)** - - 上传 1GB 含数百张图的 zip,原来需要 40+ 分钟,现在 1-2 分钟。booru 下载下来已经是 PNG 的图不再被无谓地解码再重新压缩;JPG 等真需要转 PNG 的也省 5-10× 时间。 - - 上传处理期间页面 F5 刷新会"完全卡死"的问题修了:服务器在处理上传时仍能正常响应其它请求,刷新 / 切别的项目都立即响应。 - - 上传期间在服务器终端按 Ctrl-C 没反应、只能关窗口的问题修了,现在 Ctrl-C 立刻生效。 - - 大 zip 不再一次性整个读进内存,1GB zip 内存占用从 1GB 降到 ~1MB,内存紧的云训练机不再触发系统换页拖慢。 -- **测试出图历史栏切路由 / 刷新页面后不再丢失(#273)** - - 之前测试页历史栏(不开自动落盘时)的图,只要切到别的页面或 F5 刷新就消失;现在切走再回来 / 刷新页面都还在,直到 server 重启。 - - 历史索引改成 server 端维护,浏览器零持久化层,不再出现"前端显示但实际拉不到图"的脏数据。 +- **Large dataset uploads drop from 40 minutes to 1–2 minutes, and the page no longer freezes during upload (#273)** + - Uploading a 1GB zip with hundreds of images used to take 40+ minutes and now takes 1–2. Images already in PNG from a booru download are no longer needlessly decoded and re-compressed, and even JPGs that genuinely need converting to PNG are 5–10× faster. + - Fixed the page freezing completely on an F5 refresh during upload processing: the server now stays responsive to other requests while handling an upload, so refreshing or switching to another project responds immediately. + - Fixed Ctrl-C doing nothing in the server terminal during an upload (which forced you to close the window); Ctrl-C now takes effect immediately. + - Large zips are no longer read into memory all at once: a 1GB zip's memory use drops from 1GB to ~1MB, so memory-tight cloud training machines no longer trigger system paging and slow down. +- **The test-image history strip no longer disappears when you change routes or refresh (#273)** + - Previously the images in the Generate page's history strip (when automatic saving was off) vanished the moment you navigated to another page or hit F5; now they survive navigating away and back or refreshing, until the server restarts. + - The history index is now maintained server-side with zero browser persistence layer, eliminating the stale state where the frontend showed an image it couldn't actually fetch. --- ## v0.13.0 — 2026-06-13 -采样链路统一 Comfy parity + T-LoRA / OrthoLoRA 适配器 + 失败 / 取消任务可继续训练 + 测试页历史回填 + 首次启动引导 + 项目页搜索 / 归档 + studio_data 自定义位置 - -### 新增 - -- **测试 / 训练采样 / RegAI 三条统一 Comfy parity runtime v2(#256)** - 测试页出图、训练采样预览、RegAI 先验生成三条采样路径合并成同一套实现,全部对齐 ComfyUI KSampler 行为;老的 parity 开关移除。 - - - 采样器新增 `dpmpp_3m_sde`、调度器新增 `sgm_uniform`,与 ComfyUI 同名版本逐参数对齐。 - - 训练采样新增「Comfy 编码」开关(默认开):caption 原文整段送进 Qwen3 + T5,booru 括号写法保持字面;关闭走旧版逐 tag 编码,方便新旧 A/B 与按旧编码断点续训。 - - 负面提示词对齐 ComfyUI:留空即空串,不再隐式塞默认负面 prompt。旧预设里若 sampler / scheduler 是已不支持的值(如 `euler`),自动归并到默认值。 - - Settings → 测试 新增 VAE 解码精度下拉(bf16 / fp32),默认 bf16;现代显卡下默认路径不再做不必要的模型搬运。 - - 出图过程中偶发的注意力 NaN 退到 fallback 实现,跑完当前这张图后自动复位,不再永久降级整个进程。 -- **T-LoRA + OrthoLoRA 适配器 + Automagic v2 实验路径(#215, #258)** - 训练配置 `lora_type` 新增两个变体,定位小数据集人物 / 主体定制。 - - - **T-LoRA**(arxiv 2507.05964):按 timestep 自适应 rank,高噪声段限制 rank、低噪声段满 rank,缓解小数据过拟合。 - - **OrthoLoRA**:在 LoRA 基上叠一层正交旋转 + 缩放,可单独选 `ortho`、也可与 T-LoRA 叠加(开关 `tlora_use_ortho`,默认开 — T-LoRA 论文的完整配方就是 ortho 变体)。**保存时自动蒸馏成标准 LoRA**,ComfyUI 等推理端零成本加载。 - - `lora_type` 各选项的描述全量重写为「参数干嘛 + 适用场景」,不再只罗列算法名词。 - - 实验性 Automagic v2(per-param scalar lr + fused backward 省显存):UI 不暴露,需手改 `studio_data/secrets.json` 把 `enable_automagic_v2` 设为 true 才出现。CLI / yaml 路径不受影响。 -- **失败 / 取消的训练任务可从 epoch 备份点继续训练(#266)** - 上一版起每个 epoch 末尾就在写自动备份点,但「继续训练」按钮此前只对 paused 状态开放 —— failed(崩溃 / 关机)和 canceled 任务的备份点其实多数完好。本版打开。 - - - Queue 列表 / Queue 详情页对 failed / canceled 行加「继续训练」按钮,与原有「重试(从头)」并列。done 状态仍只能重试。 - - 关机重启后照样能恢复:备份点位置在数据库里有记录,进程或机器一死也不丢。0.12 时代的存量任务会自动回填到新机制下。 - - 自动备份点改落任务档案目录(`studio_data/tasks//state/`),与采样图、运行日志同处,便于整体归档。用户自己周期性 save 的训练 state 位置不变。 - - 写盘改原子方式,断电也不会留半截备份文件;恢复成功后不再删除备份点,避免「刚 resume 到下个 epoch 之间」中间没有恢复点。 -- **测试页历史点击回填参数 + 落盘 PNG 自带参数快照(#239, #244, #245)** - 测试页历史栏从「只切大图」升级为「连同参数一起恢复」;新增 Settings → 测试 → 自动落盘开关,开启后图片直接存到 `studio_data/test/<日期>/`。 - - - 历史栏点击:xy 模式同时回填 xy 轴 + LoRA 列表,single 模式回填 LoRA + 张数。误点给 5 秒撤销提示。 - - 落盘的 PNG 内嵌完整参数快照,拖回 Studio 可读、拖去 ComfyUI / WebUI 也能解出 prompt(single 模式同时写入 a1111 兼容格式)。 - - 跨会话 / 清缓存 / 换设备:Studio 启动时扫描落盘目录补齐历史栏,不再依赖浏览器本地存储。 - - 历史栏每张图左上角的 × 按钮拿掉(误触率高),单条删除走顶部「清空」/「清失效」。 -- **首次启动引导 modal — 基础能力 checklist + 一键安装(#238)** - 新人第一次开 app 不再需要在训练 / 打标时撞墙再回 Settings。语言 modal 选完语言后立刻弹出引导。 - - - 4 条 checklist:底模套件(Anima 主模型 + VAE + Qwen3 + T5)、打标功能(WD14 + ONNX Runtime)、训练加速(flash-attn / xformers)、图像增强。任一未装显示「未安装 (X/N)」,勾选 → 一键开始下载。 - - 中文默认 ModelScope、英文默认 HuggingFace,自动写入下载源。装中实时显示下载速度 / ETA / 当前文件。 - - Settings → 系统 加「重新运行首次引导」按钮,可随时调出。 -- **studio_data 目录可自定义位置 + 复制迁移(#263, #267)** - 装在系统盘的用户可以把不断增长的项目数据挪到大容量盘。Settings → 系统 → 存储位置,选目标目录后弹出确认 modal(显示待复制文件数 / 总大小 / 顶层明细),开始迁移后进度条实时推进。 - - - **只复制不删除**:旧位置数据原样保留;迁移失败时清掉半截目标目录、当作没发生过。 - - 数据库在线快照备份,避免迁移期间被截到写一半的状态。 - - 在所选目录下会创建 `studio_data/` 子目录搬入(不会把内容散到你选的父目录里)。 - - 若指针文件损坏 / 盘符未挂载,自动回退默认位置,旧数据不丢。 - - 顺手修:新创建的 version 目录不再生成空的 `samples/` 文件夹(采样图实际落在任务档案目录)。 -- **项目页加搜索 / 过滤 / 排序 + 软删除归档 + 「准备中」显示当前阶段(#265)** - 项目多了之后的浏览体验改造:找得到项目、不活跃项目能收起、能看出每个项目卡在哪一步。 - - - header 「过滤 ▼」开关折叠时不占空间;展开后是一行:搜索(标题 / slug / 备注,大小写不敏感)+ 状态下拉 + 排序下拉。 - - 卡片右上角 × 从硬删改**归档**:目录 / version / 任务全保留,可随时恢复。header「已归档(n)」可切换到归档视图,归档卡片半透明,提供恢复和真删两个按钮。归档不更新「最近活跃时间」,恢复后项目回到原位置不会顶到最前。 - - 状态徽章「准备中」改为显示当前阶段(如「准备中 · 打标」),6 个阶段全覆盖(下载 / 筛选 / 预处理 / 打标 / 正则集 / 训练)。 -- **测试出图模型闲置自动卸载释放 VRAM(#232)** - - 解决「切到别的页面忘了清理,下次开训练就爆显存」的常见场景:测试页加载完模型后空闲超过设定时间,自动卸载释放显存。 - - 默认 10 分钟;Settings → 测试 可调,设 0 关闭。运行中改值即时生效。 -- **打标器加「已有 caption」策略:覆盖 / 跳过 / 追加(#237)** - - 打标页顶栏「格式」旁新增「已有 caption」下拉:**覆盖**(默认,保留原行为)/ **跳过**(已有 caption 的图直接保留)/ **追加到末尾**(按 tag 合并去重写回原格式)。 - - WD14 / CLTagger / LLM 三个打标器都支持。追加模式下 trigger word 不会被重复加。 - - 本次任务专属选项,不进全局 Settings。 - -### 变更 - -- **全 app 任务日志统一为三态抽屉(下载 / 预处理 / 打标 / 正则集 共 6 页)(#253)** - 各页面的任务日志原本五套不同实现、行为各异,本版统一成挂在底部的三态抽屉。 - - - 无任务时整体隐藏;有任务时默认显示一行收起条(状态徽标 + 最后一行日志 + 耗时),不展开也能看到进度在动。 - - 进入运行状态自动展开(含回到页面发现任务还在跑的回放场景);任务结束不自动收起,需要手动收或切页。 - - 顺手修:正则集页的历史 booru 任务不再永久遮住正在跑的 AI 先验日志;预处理的放大 / 裁剪任务获得日志回放;去重扫描失败的错误进日志(此前只有 toast)。 -- **ONNX Runtime 改 Settings 手动装 + 新增 DirectML 支持(#236)** - - Studio 启动期不再自动 pip install onnxruntime(~150MB / 几分钟),未装时静默跳过;启动更快,不用 WD14 / CLTagger 的用户不被阻塞。 - - 新增 DirectML 后端:走 DX12,**不依赖 CUDA / cuDNN / cuBLAS**,任何 DX12 GPU(NVIDIA / AMD / Intel,Windows-only)都能跑。修复 RTX 5090 上原 onnxruntime-gpu 静默降级 CPU 的问题(issue #231)。 - - Settings → ONNX Runtime 三按钮:DirectML / CUDA / CPU;自动检测按平台分流(Windows + GPU → DirectML / Linux + GPU → CUDA / 无 GPU → CPU)。 - - 打标页未装 onnx 时显示「去安装 ONNX Runtime →」按钮,一键直跳 Settings 对应区域。 -- **Tag 翻译词典默认源换成 ffdkj(每日更新,31 万条)(#268)** - - 原默认源 Physton 已停更 2.5 年(2023-11 最后更新,9.9 万条)。换成 ffdkj,每日更新、覆盖 31.1 万条(post_count ≥ 10 的全部 tag,Gemini 翻译 + 人工校对)。 - - 按热度取前 20 万条加载,截掉的是引用数最低的长尾。 - - 用户上传自定义词典的流程不变;Settings 「恢复默认词典」按新源重新下载。 - -### 改进 - -- **Tag 自动补全排序改热度优先 + 紧凑匹配(#248)** - - 之前按 tag 名长度排序,输入 "red" 时偏门短 tag 排在 "red eyes" 前面。改为按热度排(post_count 降序)。 - - 加紧凑匹配:去掉空格 / 下划线后也能命中,让 "redey" 匹配 "red eyes"。 -- **更新日志 modal 加左右版本切换 + Settings 独立入口(#269)** - - 之前的更新日志详情 modal 只能看「当前显示版本」一版,hotfix 后老 feature 内容会从 UI 消失。modal 加左右切换按钮 + 位置 `(n / N)`,键盘 ← / → 也能切。 - - Settings → 系统 → 版本卡片右上角加「📜 更新日志」独立按钮,点击打开 modal 并定位到最新版本。 -- **测试页加采样器 / 调度器下拉框 + 提示词输入自动撑高(#262)** - - 测试页「采样参数」卡片补齐采样器和调度器下拉框(`er_sde` / `dpmpp_3m_sde`,`simple` / `sgm_uniform`),选择记住到本地。 - - 枚举显示文案去下划线、正确大写(如 `dpmpp_3m_sde` → `DPM++ 3M SDE`)。 - - 正负向提示词输入框最小 5 行,随内容自动撑高、不再需要拖动或滚动。 -- **VAE 缓存合批默认跟随训练 batch size + 权重加载更省内存(#254)** - - `vae_cache_batch_size` 默认值改 `0` = 跟随训练 batch size(对齐 kohya GUI `vae_batch_size` 语义)。手动设 1 仍可保留为保守路线。 - - 修复低显存卡 + 3D VAE 全分辨率场景下,首次建 VAE 缓存可能 OOM 的问题。 - - 权重加载时省一次内存拷贝。 -- **切路由时取消半途缩略图加载,避免饿死数据请求(#264)** - -### 修复 - -- **LoKr 分层 rank 推理强度与训练对齐(#241)** - 分层 rank 训练的 LoKr 之前在 ComfyUI 等推理端加载时强度与训练不一致。本版保存的 metadata 会写出每层实际的 rank 与 alpha,推理端按层读取做缩放,与训练前向路径一致。 -- **Booru 下载区分 429 / 503 + 不再永久减速 + 日志去重(#234)** - - gelbooru 服务端偶发 503 时,原日志被误写成「429 backoff,等 59.5s」误导排查方向,且单次 503 会把下载速率永久减半(不可恢复)。 - - 区分两种语义:429(「你太快了」)→ 退避 60 秒 + 减半速率;503(「我挂了」)→ 退避 15 秒,**不动速率**。 - - API 和 CDN 各自独立退避,CDN 风暴期间 API 搜索不受连累。同一退避窗口内连续 503 只打 1 行日志,不再刷屏。 -- **Windows 部分机器开 Studio 白屏(主动注册 .js / .css MIME 类型)(#233)** - 部分 Windows 机器因为系统注册表被其它软件改过,浏览器拿到 `.js` / `.css` 的 MIME 类型不正确,导致 Studio 加载白屏。后端主动注册正确类型回避注册表读取。 -- **标签 chip 模式输入框 Backspace 不再误删已确认的标签(#247)** -- **预处理质量候选审核面板布局 — 卡片响应式多列 + 高度占满(#252)** -- **UI 小修:日志回放 / 打标 skip 前置过滤 / 裁剪残留 / 去重面板(#249)** - - 切页面 / 刷新后恢复 RegAI 先验生成页和打标页的实时日志。 - - 打标选「已有 caption = 跳过」时,提前过滤掉已有 caption 的图,避免空转加载打标器。 - - 训练范围裁剪完成后删除不再属于输出的旧源图及 sidecar,修 bundle / 版本复制后原图 + 裁剪图同时残留的问题。 - - 去重质量审核面板改切换显示模式(分组 / 质量),防窄屏面板互挤;打标高级选项展开不再挤掉日志面板。 - - Bundle 导入导出保留 `version_label` 与 `preset_name`,导入时还原而不是固定为 v1。 - - 复选框 / radio 颜色统一为主题强调色。 +Unified Comfy-parity sampling pipeline + T-LoRA / OrthoLoRA adapters + resume training on failed/canceled tasks + Generate-page history backfill + first-run onboarding + project-page search/archive + custom studio_data location + +### Added + +- **Test, training-sample, and RegAI sampling unified on Comfy-parity runtime v2 (#256)** + The three sampling paths — test image generation, training-sample previews, and RegAI prior generation — are merged into one implementation, all aligned with ComfyUI KSampler behavior; the old parity toggle is gone. + + - Added the `dpmpp_3m_sde` sampler and the `sgm_uniform` scheduler, matched parameter-for-parameter to their ComfyUI namesakes. + - Training sampling gains a "Comfy encoding" toggle (on by default): the full caption text is sent to Qwen3 + T5 as-is, with booru bracket notation kept literal; turning it off uses the old per-tag encoding, which is handy for A/B-ing old vs. new and for resuming a checkpoint trained under the old encoding. + - Negative prompts now match ComfyUI: leave it empty and it's an empty string — no implicit default negative prompt. If an old preset has a sampler / scheduler value that's no longer supported (like `euler`), it's folded back to the default. + - Settings → Test gains a VAE decode precision dropdown (bf16 / fp32), defaulting to bf16; on modern GPUs the default path no longer does unnecessary model moves. + - Occasional attention NaNs during generation now drop to a fallback implementation and reset automatically after the current image finishes, instead of permanently downgrading the whole process. +- **T-LoRA + OrthoLoRA adapters + Automagic v2 experimental path (#215, #258)** + The training config's `lora_type` gains two new variants aimed at small-dataset character / subject customization. + + - **T-LoRA** (arXiv 2507.05964): adapts rank per timestep — capping rank in high-noise steps and using full rank in low-noise steps — to ease overfitting on small datasets. + - **OrthoLoRA**: layers an orthogonal rotation + scaling on top of the LoRA base; you can pick `ortho` on its own or stack it with T-LoRA (the `tlora_use_ortho` toggle, on by default — the full recipe from the T-LoRA paper is the ortho variant). **It's automatically distilled into a standard LoRA on save**, so inference engines like ComfyUI load it at zero cost. + - Every `lora_type` option's description was rewritten as "what the parameter does + when to use it", instead of just listing algorithm names. + - Experimental Automagic v2 (per-param scalar LR + fused backward to save VRAM): not exposed in the UI — it only appears if you manually set `enable_automagic_v2` to true in `studio_data/secrets.json`. The CLI / yaml path is unaffected. +- **Failed / canceled training tasks can resume from an epoch backup point (#266)** + Since the last release, an automatic backup point has been written at the end of every epoch, but the "Resume training" button was previously only available for paused tasks — even though the backup points for failed (crashed / shut down) and canceled tasks are usually intact. This release opens it up. + + - The Queue list and Queue detail page add a "Resume training" button on failed / canceled rows, alongside the existing "Retry (from scratch)". Done tasks can still only be retried. + - Recovery survives a reboot: the backup point's location is recorded in the database, so it isn't lost even if the process or machine dies. Existing tasks from the 0.12 era are backfilled into the new mechanism automatically. + - Automatic backup points now land in the task archive directory (`studio_data/tasks//state/`), alongside sample images and run logs for easy archiving as a unit. The location of training state you save yourself periodically is unchanged. + - Writes are now atomic, so a power loss won't leave a half-written backup file; after a successful recovery the backup point is no longer deleted, avoiding a window with no recovery point between resuming and the next epoch. +- **Generate-page history click restores parameters + saved PNGs carry a parameter snapshot (#239, #244, #245)** + The Generate page's history strip is upgraded from "just swap the main image" to "restore the parameters too"; a new Settings → Test → auto-save toggle saves images straight to `studio_data/test//` when enabled. + + - Clicking in the history strip: in xy mode it restores both the xy axes and the LoRA list; in single mode it restores the LoRAs and the image count. A misclick gets a 5-second undo prompt. + - Saved PNGs embed a full parameter snapshot — drag one back into Studio and it reads, drag it into ComfyUI / WebUI and the prompt comes out too (single mode also writes the a1111-compatible format). + - Across sessions / cache clears / device changes: Studio scans the save directory at startup to repopulate the history strip, no longer relying on browser local storage. + - The × button in the top-left of each history thumbnail is removed (too easy to hit by accident); individual deletion now goes through "Clear" / "Clear stale" at the top. +- **First-run onboarding modal — a basic-capabilities checklist with one-click install (#238)** + New users no longer have to hit a wall during training / tagging and then go back to Settings. The onboarding pops up right after you pick a language in the language modal. + + - Four checklist items: the base-model suite (the Anima base model + VAE + Qwen3 + T5), tagging (WD14 + ONNX Runtime), training acceleration (flash-attn / xformers), and image enhancement. Anything not installed shows "Not installed (X/N)"; check it → one click to start downloading. + - The download source is picked automatically. During install it shows live download speed / ETA / current file. + - Settings → System adds a "Re-run first-run setup" button to bring it back anytime. +- **studio_data directory location is customizable + copy-based migration (#263, #267)** + If your install lives on the system drive, you can move the ever-growing project data to a larger drive. Settings → System → Storage location: pick a target directory and a confirmation modal appears (showing the number of files to copy / total size / top-level breakdown); once migration starts, a progress bar advances in real time. + + - **Copy only, never delete**: data at the old location is left exactly as is; if migration fails, the half-written target directory is cleaned up and it's as if nothing happened. + - The database is backed up with an online snapshot, so it isn't captured mid-write during migration. + - A `studio_data/` subdirectory is created under the directory you chose and moved into (it won't scatter contents across the parent directory you picked). + - If the pointer file is corrupted or the drive isn't mounted, it falls back to the default location automatically, and old data isn't lost. + - Also fixed: newly created version directories no longer generate an empty `samples/` folder (sample images actually land in the task archive directory). +- **Project page gains search / filter / sort + soft-delete archiving + "Preparing" shows the current stage (#265)** + A browsing overhaul for when you have a lot of projects: find projects, collapse inactive ones, and see which step each project is stuck at. + + - The header "Filter ▼" toggle takes no space when collapsed; expanded, it's a single row: search (title / slug / notes, case-insensitive) + a status dropdown + a sort dropdown. + - The × in a card's top-right corner changes from hard-delete to **archive**: the directory / versions / tasks are all kept and can be restored anytime. The header's "Archived (n)" switches to the archive view, where archived cards are semi-transparent and offer Restore and permanent-delete buttons. Archiving doesn't update "last active time", so a restored project returns to its original spot rather than jumping to the front. + - The "Preparing" status badge now shows the current stage (e.g. "Preparing · Tagging"), covering all six stages (Download / Filter / Preprocess / Tagging / Regularization set / Training). +- **Test-generation model auto-unloads when idle to free VRAM (#232)** + - Solves the common "switched to another page and forgot to clean up, then ran out of VRAM next time I started training" scenario: once the Generate page has loaded the model, it auto-unloads to free VRAM after sitting idle past a set time. + - Defaults to 10 minutes; adjustable in Settings → Test, set 0 to disable. Changing the value takes effect immediately while running. +- **Tagger gains an "existing caption" policy: overwrite / skip / append (#237)** + - The tagging page's top bar adds an "Existing caption" dropdown next to "Format": **Overwrite** (default, the original behavior) / **Skip** (images that already have a caption are left as-is) / **Append to end** (merged and deduplicated by tag, written back in the original format). + - All three taggers — WD14 / CLTagger / LLM — support it. In append mode the trigger word isn't added twice. + - This is a per-task option, not in global Settings. + +### Changed + +- **Task logs across the whole app are unified into a three-state drawer (Download / Preprocess / Tagging / Regularization set — six pages in all) (#253)** + Each page's task log used to be five different implementations behaving differently; this release unifies them into a three-state drawer docked at the bottom. + + - With no task it's hidden entirely; with a task it shows a one-line collapsed bar by default (status badge + last log line + elapsed time), so you can see progress moving without expanding. + - It auto-expands when a task enters the running state (including the replay case where you return to the page and find the task still running); it doesn't auto-collapse when the task finishes — collapse it or switch pages yourself. + - Also fixed: an old historical booru task on the regularization-set page no longer permanently covers the AI-prior log that's currently running; preprocess upscale / crop tasks get log replay; dedup-scan failures now go to the log (previously only a toast). +- **ONNX Runtime moves to manual install in Settings + new DirectML support (#236)** + - Studio no longer auto-runs pip install onnxruntime (~150MB / several minutes) at startup; when it's not installed it's silently skipped — faster startup, and users who don't need WD14 / CLTagger aren't blocked. + - New DirectML backend: it goes through DX12 and **doesn't depend on CUDA / cuDNN / cuBLAS**, so any DX12 GPU (NVIDIA / AMD / Intel, Windows-only) can run it. Fixes the issue where the original onnxruntime-gpu silently fell back to CPU on an RTX 5090 (issue #231). + - Settings → ONNX Runtime has three buttons: DirectML / CUDA / CPU; auto-detection routes by platform (Windows + GPU → DirectML / Linux + GPU → CUDA / no GPU → CPU). + - When onnx isn't installed, the tagging page shows a "Go install ONNX Runtime →" button that jumps straight to the matching Settings section. +- **Tag-translation dictionary's default source switched to ffdkj (updated daily, 310k entries) (#268)** + - The previous default, Physton, has been unmaintained for 2.5 years (last updated 2023-11, 99k entries). The new ffdkj source updates daily and covers 311k entries (every tag with post_count ≥ 10, Gemini-translated + human-proofread). + - The top 200k entries by popularity are loaded; what's cut is the lowest-reference long tail. + - The flow for uploading your own custom dictionary is unchanged; Settings "Restore default dictionary" re-downloads from the new source. + +### Improved + +- **Tag autocomplete sorting changed to popularity-first + compact matching (#248)** + - It used to sort by tag-name length, so typing "red" put obscure short tags ahead of "red eyes". Now it sorts by popularity (post_count descending). + - Added compact matching: it also matches after stripping spaces / underscores, so "redey" matches "red eyes". +- **Changelog modal gains previous/next version navigation + a dedicated Settings entry point (#269)** + - The changelog detail modal could previously only show the "currently displayed version", so after a hotfix the content of an older feature would disappear from the UI. The modal gains previous/next buttons + a `(n / N)` position, and the keyboard ← / → switch too. + - The version card in Settings → System gets a dedicated "📜 Changelog" button in its top-right corner that opens the modal and jumps to the latest version. +- **Generate page gains sampler / scheduler dropdowns + auto-growing prompt inputs (#262)** + - The Generate page's "Sampling parameters" card fills in the sampler and scheduler dropdowns (`er_sde` / `dpmpp_3m_sde`, `simple` / `sgm_uniform`), and your choice is remembered locally. + - Enum labels drop underscores and are capitalized correctly (e.g. `dpmpp_3m_sde` → `DPM++ 3M SDE`). + - The positive and negative prompt inputs are at least 5 lines tall and grow with content, so you no longer need to drag or scroll. +- **VAE cache batching defaults to follow the training batch size + more memory-efficient weight loading (#254)** + - `vae_cache_batch_size` now defaults to `0` = follow the training batch size (matching kohya GUI's `vae_batch_size` semantics). Setting it to 1 manually still keeps the conservative path. + - Fixes a possible OOM when first building the VAE cache on a low-VRAM card with a 3D VAE at full resolution. + - Saves one memory copy during weight loading. +- **Half-loaded thumbnail loads are canceled on route changes to avoid starving data requests (#264)** + +### Fixed + +- **LoKr layer-wise rank inference strength now matches training (#241)** + Layer-wise-rank LoKr previously loaded at a strength inconsistent with training in inference engines like ComfyUI. The metadata saved in this release writes out each layer's actual rank and alpha, and the inference side reads them per layer to scale, matching the training forward path. +- **Booru downloads distinguish 429 / 503 + no more permanent slowdown + log dedup (#234)** + - When the gelbooru server hit an occasional 503, the log mislabeled it as "429 backoff, waiting 59.5s", steering troubleshooting the wrong way, and a single 503 would permanently halve the download rate (unrecoverable). + - The two are now distinguished: 429 ("you're too fast") → back off 60 seconds + halve the rate; 503 ("I'm down") → back off 15 seconds, **leaving the rate alone**. + - The API and CDN back off independently, so an API search isn't dragged down during a CDN storm. Consecutive 503s within the same backoff window now log just one line instead of flooding. +- **Studio showed a blank screen on some Windows machines (now actively registers .js / .css MIME types) (#233)** + On some Windows machines, because the system registry had been altered by other software, the browser got incorrect MIME types for `.js` / `.css`, causing Studio to load to a blank screen. The backend now registers the correct types to avoid reading from the registry. +- **Tag chip-mode input: Backspace no longer accidentally deletes a confirmed tag (#247)** +- **Preprocess quality-candidate review panel layout — responsive multi-column cards + full height (#252)** +- **Minor UI fixes: log replay / tagging skip pre-filter / crop leftovers / dedup panel (#249)** + - Restore live logs on the RegAI prior-generation page and the tagging page after switching pages / refreshing. + - When tagging with "Existing caption = Skip", images that already have a caption are filtered out up front, avoiding spinning up the tagger for nothing. + - After a training-scope crop finishes, old source images and sidecars that are no longer part of the output are deleted, fixing the issue where the original + cropped images both lingered after a bundle / version copy. + - The dedup quality-review panel switches display mode (grouping / quality) to keep panels from crowding each other on narrow screens; expanding the tagging advanced options no longer squeezes out the log panel. + - Bundle import/export preserves `version_label` and `preset_name`, restoring them on import instead of fixing them to v1. + - Checkboxes / radios use a unified theme accent color. --- ## v0.12.0 — 2026-06-05 -预处理范围下沉到 train version(ADR 0010)+ Lion / Automagic 优化器 + tag 翻译词典 / 全前端 autocomplete + WandB artifact 上传 + InfoNoise 算法对齐 - -### 新增 - -- **Lion / Automagic 优化器 + cosine_with_warmup scheduler(#214)** - - **Lion**(Chen et al. 2023, arxiv 2302.06675):sign-momentum 优化器,状态比 AdamW 少一半显存;公式逐行对齐 Google reference impl 与 lucidrains/lion-pytorch。 - - **Automagic**:AI Toolkit 风格 per-parameter adaptive lr + sign-agreement 追踪 + Adafactor factored 2nd moment + 8-bit lr_mask;核心 1:1 对齐 tdrussell/diffusion-pipe `optimizers/automagic.py`。新增依赖 `optimum-quanto>=0.2.0`。bf16 path 用 Kahan compensated summation 替代 stochastic rounding,避免每步 ~scale/2 噪声注入 lr_mask;故意不注册 `grad-accum hook`,绕过上游会与 `GradScaler.unscale_` / `clip_grad_norm_` 静默失效的冲突。 - - **cosine_with_warmup**:LambdaLR-based cosine schedule + linear warmup + eta_min floor;warmup 用 step 0-indexed,与 transformers / diffusers / PEFT / sd-scripts 全社区约定一致。 -- **Tag 翻译词典 + 全前端 autocomplete(#225)** - - **默认词典**:首次启动后台拉 Physton/sd-webui-prompt-all-in-one-assets `danbooru.zh_CN.csv`(~25k 条 zh 翻译,~3MB,**不进仓库**),失败时 Settings 提供「恢复默认词典」重试。 - - **chip 翻译显示**:TagEditor / TagsInput / BulkActionBar dropdown / TagStatsPanel / Overview 标签分布 / Regularization 排除 tag 6 处统一显示中文翻译;首次按 i18n 语言决定默认是否开(`zh*` → on,否则 off)。 - - **Autocomplete 输入**:6 处 input/textarea 接补全 — TagEditor chip + 文本模式、TagsInput(Settings + Tagging blacklist)、TagStatsPanel inline rename、Regularization 自定义排除、Generate 正向 / 负向 prompt;中文输入支持反向查英文 tag。 - - **Settings 新增 tag-dictionary section**:当前词典 meta / 上传 csv 替换 / 恢复默认 / 显示翻译 toggle。 -- **WandB 加 LoRA / training state artifact 上传 + Settings / preset 覆盖(#146)** - WandB 集成不只是 metrics / sample log: - - 新增三类 artifact 上传:LoRA 模型、手动 training state、自动 training state。 - - per-category retention:`all` 保留全部版本 / `last` 仅保留最新(旧版本在新版本上传成功后从 artifact collection 自动清理)。 - - 配置可在全局 Settings 设默认,preset 字段留空自动回退到 Settings,便携 / 云端 preset 不必重复填 WandB 配置。 - - 上传路径用 `run.log_artifact(...).wait()` 等完成才清理,避免对 draft artifact 操作;大文件加周期性进度日志。 -- **Outputs 加批量删除 + 复制路径 / 打开文件夹按钮文案样式统一(#217, #224)** - Queue 任务详情页 + 项目 Overview 共用的「输出」面板: - - 批量模式新增「删除所选 (n)」,二次确认后按相对路径删;任一文件不存在 → 整批 404 拒绝不留半删状态。 - - 按钮文案:「复制」→「复制路径」、「打开」→「打开文件夹」。 - - 按钮样式:「打开文件夹」从 `btn-secondary` 改 `btn-ghost`,跟旁边复制 / 刷新对齐;「删除所选」从 `btn-danger` 红填充改 `btn-ghost text-err`,危险提示只留文字色。 -- **训练 / 预设页右栏加 SchemaForm 章节锚点 nav(#224)** - - 训练页右栏 stats panel 之下、预设页右侧新增 sticky 章节索引;点击平滑滚动到 SchemaForm 对应 group,scroll 时当前 group 自动高亮。 - - 训练 / 预设页主区比例从 `1.5fr:1fr` 调整为 `3fr:1fr`,给字段输入框更多舒展空间。 - -### 变更 - -- **预处理范围下沉到 train version + 加 preprocessing phase(ADR 0010)(#209-#213)** - ADR 0010 把预处理(去重 / 放大 / 裁剪)的工作范围从项目级 `download/` 整集下沉到训练 version 级 `train/`,路径与服务端 API 全套切换。 - - - **Sidebar 步骤顺序换**:① 下载 / ② 筛选 / ③ 预处理(可选)/ ④ 打标 ... ⑦ 训练。预处理从「下载之后整集做」变成「筛选完每个 train version 自己做」,跟正则集同 pattern「可选 + 可跳过」。 - - **预处理 grid 是当前 version 的 train 集**:不再展示 download 全集;同一项目不同 version 可独立去重 / 放大 / 裁剪。 - - **路径变化**:URL 从 `/projects/:pid/preprocess` 改 `/projects/:pid/v/:vid/preprocess`;老书签自动 redirect 到当前 active version。 - - **老项目自动迁移**:进入新版本后 `_v11` 迁移把已有 `train/{sub-folder}/` 非空的 version phase 从 `curating` 推进到 `preprocessing`,旧 `projects/{id}/preprocess/manifest.json` 在选 version 时被 lazy 转换成 `versions/{label}/train/manifest.json`,无感知。 - - **restore 行为改**:恢复某张图时,从 `download/{origin}` 复制覆盖回 `train/{name}`;若 download 已不存在,UI 列出「无 origin」列表让用户决定拖入替换 / 保留 / 移除(老版本会直接报 missing)。 - - 旧 `/api/projects/{pid}/preprocess/*` 端点 / 老 `preprocess_worker.run` project-scope 分发整套删除(ADR 0010 PR-5)。第三方脚本调老 URL 的需切到 version-scope 路径。 -- **noise_enhancement 描述重写 + pyramid 默认 0.5 + 与 InfoNoise 加互斥(#220)** - 三轮经验性调研后改写 `noise_enhancement_type` / `noise_offset` / `pyramid_noise_*` 描述与默认值: - - **`pyramid_noise_discount` 默认 0.35 → 0.5**:旧默认让 `noise_enhancement_type=pyramid` 实际无 op(anima 的 `cur / cur.std()` 归一化会把 ≤0.4 discount 抵消回 baseline);0.5 起 pyramid noise 才真生效。已显式填值的 yaml 不动;只有 fallback-to-default 才漂移。 - - **描述去 SD/DDPM 时代经验**:`noise_offset` 明示 0-0.2 范围,<0.05 等同 off,>0.1 显著抬初始 loss;`pyramid_noise_iters` 明示 0-6 控制频段覆盖;`pyramid_noise_discount` 明示 0.1-0.4 等同 off,0.5-0.7 显著改低频结构。 - - **`noise_enhancement_type != none` 与 InfoNoise 加 schema 互斥**:补完 InfoNoise 4 对互斥(与 `loss_weighting` / `loss_type=huber` / `timestep_schedule_shift` 已在 #216 落地)。InfoNoise 的 I-MMSE 推导假设标准高斯噪声 — `noise_enhancement_type` 会改噪声场让学到的 schedule 偏离 paper-optimal 形态。 - - **老 config 不自动迁移**:旧 yaml 同时 `infonoise_enabled=true` + 4 个互斥字段任一在 `TrainingConfig(...)` 实例化时直接 `ValueError`;报错文案点出冲突字段并给二选一选项「(a) 关 InfoNoise 保留 X;或 (b) X 改回默认走 InfoNoise」。 -- **Reg 排除 train top tag 改单列频次降序 + 删 hint(#218)** - Reg 页「排除 train top tag」卡之前按出现率分高 / 中 / 低三档显示并带副标题;用户反馈分档对实际选择无帮助。本版改成一栏按 count 降序铺开 tag chip,AI 模式 meta 行去掉「反向出图把概念推回 base」hint,只留「已排除 N」计数。 - -### 改进 - -- **lycoris LoRA forward 默认走 bypass_mode,约 1.8x 加速(#196)** - Issue #182:相同模型 / 配置(rank=8 / batch=1 / Anima 1024)AnimaLoraStudio 0.11 it/s 比 sd-scripts 0.19 it/s 慢约 1.8x。根因 lycoris `LoConModule.forward` 默认路径每步做一遍 full-rank `F.linear` + 重建 `(out, in)` ΔW 矩阵;280 层 patch 累计开销显著。本版默认走 lycoris 自带 `bypass_mode=True` (`org_forward(x) + lora_up(lora_down(x)) * scale`),与 LoRA 论文 / sd-scripts / PEFT / diffusers 全社区标准 forward 一致;数学等价,速度回归到 sd-scripts 量级。 -- **SettingsDrawer 关闭后卸载 + 去 backdrop blur + 输入框本地态消除卡顿(#208)** - - 抽屉关闭过渡完成后从 DOM 卸载,不再为整 viewport 维持 `backdrop-filter: blur(0px)` 合成层。 - - 遮罩 `backdrop-blur-sm` 改纯半透明色过渡,消除 GPU 实时模糊在 2K/4K / 核显 / 缩放场景的滚动掉帧。 - - 新增 `SettingsInput` 本地态输入:打字时只更内部 state,`onBlur` / Enter 才一次性 sync 父组件 `draft`,4200 行 SettingsPage 不再每键击就整页 re-render。 -- **训练子进程加 expandable_segments 减少 CUDA 碎片避免低显存 OOM(#205)** - 8GB 显存 + LoKr full-matrix 模式(27.5M 参数)下 PyTorch 默认 caching allocator 的「reserved 但 unallocated」内存累积,allocated + reserved 接近显存上限就会 OOM。本版训练子进程在 `import torch` 前 `os.environ.setdefault("PYTORCH_CUDA_ALLOC_CONF", "expandable_segments:True")`,只影响训练 subprocess 不影响 shell 环境;用 `setdefault` 不覆盖用户已设值。 - -### 修复 - -- **InfoNoise 核心算法多处修正 + reg 样本按 is_reg 排除(#216, #223)** - 端到端 mock simulation(96 配置 × 4 mmse 形状 × 3 grad_accum × 2 baseline × 4 pivot 策略)暴露默认 InfoNoise 把 99% 概率质量塞到 σ_min 四分位区,自适应 sampler 实际从未生效。本次同时修复 4 处算法层 + 1 处 reg 集污染: - - - **gate pivot `c` 默认 0.15**(论文 §5 CIFAR 报告值):旧 `above.argmax()` 因 1/σ³ 通用尾在低 σ 端饱和 `r_norm` 到 1.0(论文 §B.6 警告点),永远返 `c ≈ σ_min`,gate `σ³/(σ³+c³)` 退化为近似恒等。新增 `infonoise_gate_pivot_c` 字段做 escape hatch,填 `0` 走 paper-faithful 的动态 Eq 87,填正值显式 override。 - - **`r̂_k = mse / σ²`**(论文 Appendix B.2 Eq 61,log-σ 熵率):旧 `mse / σ³` 来自 VE channel(Eq 59),与 `Δlog σ` 求和不一致;漏的 Jacobian 正是让通用尾主导归一化的根因。 - - **`maybe_refresh` 按 `global_step` gate**(optimizer step),不再按 `_internal_step`(micro-batch count):`N_warm` 与默认 `total_steps × 20%` 都假设 optimizer step,`grad_accum=4` 时会让 warmup 提前 4 倍退出,跑到 EMA 还没收敛就开自适应。 - - **`state_dict __version__ = 2`**:σ³ → σ² 是语义破坏;resume 读到 v1 ckpt 时 cold-start 重新累 EMA 并打 warning,不抛异常,长 run 升级安全。 - - **Reg 样本按 `is_reg` flag 硬排除 InfoNoise record**(#223):之前用 `loss_weight >= 0.99` 阈值跳过 reg 样本,`reg_weight=1.0` 边界让 reg 仍进 record 污染 schedule。改成 dataset 层透传 `is_reg` flag 到 batch,loop 用 `~batch["is_reg"]` mask,跟 `loss_weight` 解耦。reg 样本仍照常进梯度按 `reg_weight` 加权,只是不进 InfoNoise schedule 学习。 -- **InfoNoise 4 对互斥字段前端实时锁 + 老 config 容错关 + 顶部 banner(#221, #226)** - InfoNoise 与 `noise_enhancement_type` / `loss_weighting` / `loss_type=huber` / `timestep_schedule_shift` 的 4 对 schema 互斥之前只有后端 validator,前端无 disable_when。用户操作时不被拦截,得等 600ms debounce auto-save 后弹 `train.saveFailed` toast,表单仍 dirty。 - - - **对称锁,先非默认的赢**:4 个被控字段装 `disable_when=infonoise_enabled==true`;`infonoise_enabled` 装反向复合 OR `disable_when`,任一冲突字段非默认时 checkbox 灰显锁住。实时切换时另一侧自动 reset 到默认。 - - **老 config 容错**:旧 yaml 同时 `infonoise=on` + 任一冲突字段非默认进入 Train 页时,后端 `_tolerant_validate` 自动关掉 InfoNoise 保留用户原值,避免 4 个字段同时灰显锁死无法编辑。 - - **顶部 banner 提示**:「已重置:infonoise_enabled」横幅在初次 config load 也亮起(不只 fork preset / save as preset 路径),用户能立刻看到被自动改了什么字段。 - - 「严格保存」路径(前端绕过 disable_when 直接 PUT)仍报错防 silent 改值。 -- **VAE 整图 decode OOM 自动回退分块 decode(#207)** - 8GB 显存跑 1024×1024 reg 生成时,VAE decode 工作峰值可吃满剩余显存第一张就 OOM。本版在 `VAEWrapper.decode` 入口加 try-full + `torch.cuda.OutOfMemoryError` fallback 走 `_tiled_decode`(tile=64 latent / 512 px,stride=48,cosine blend mask,fp32 accumulator 防 bf16 精度损失)。 - - - **大显存零成本**:try 一次就过,永远不进 tile fallback。 - - **小显存 fallback**:单 tile 工作峰值 ~75 MB,每张图慢约 30%,能跑完。 - - **6 处调用点全自动受益**:reg_ai / generate CLI / generate XY / daemon generate / daemon XY / training sample preview 共用 `sample_image` 走同一入口。 -- **cache_latents 按 npz 去重避免 repeat 文件夹重复 VAE encode(#199)** - Kohya 风格 per-folder repeat(`5_concept` 前缀)会把同一张图按 N 倍展开到 `samples`,旧 `_build_cache` 直接遍历 `samples` 收集 `to_encode`,同一张图被 VAE encode N 次反复覆盖同一个 npz;`flip_augment` 模式下还要再乘 2。改成按 `npz_path` 去重,首次建 cache 时间按唯一图数量计;二次运行(cache 命中)行为不变。 -- **Reg AI 先验支持 JSON caption + tag 保留空格不转下划线(#185)** - AI 先验生成之前只读 `.txt` caption 且自动把空格转下划线,导致 JSON caption 训练集无法被 AI 先验读取,prompt 也会变成 `brown_hair` 不符合 Anima 推荐的空格 tag 格式。本版: - - 读取 `.json / .txt / .caption` 三种 caption 格式,prompt 保留空格 tag 形态; - - `excluded_tags` 同步写回 reg sidecar(JSON 结构化删除字段保留原形态和 `nl` 自然语言;TXT 删除后写回); - - excluded tag 匹配兼容空格 / 下划线,旧的 booru / UI 风格排除项不会失效; - - full 模式先清空旧 reg 输出避免旧 caption 混入训练;incremental 模式只补新图。 -- **CLI argparse 兼容 py3.13+ + description 裸 % 自动转义(#201)** - Issue #170:py3.13+ 跑 `anima_train.py` 撞 `ValueError: invalid option name '--no-progress' for BooleanOptionalAction`(argparse 收紧不再允许 flag 本身就以 `--no-` 开头)。本版 schema bool 字段名以 `no_` 开头时改用一对 `store_true` / `store_false` 替代 `BooleanOptionalAction`,CLI 行为不变(`--no-progress` → True,`--progress` → False)。顺手把 description 里裸 `%` 自动转义为 `%%`,避免 argparse `format_help()` 把 description 当 printf 模板崩。 -- **数据集上传走 convert_to_png + 同 stem 文件自动加后缀(#222)** - `gelbooru.convert_to_png` 开启时,booru 下载会归一为 `.png`,但 dataset 上传 / 服务端路径导入是 raw bytes 透传 — 既下载又上传时 `download/` 会同时存在 `1.png` 和 `1.jpg`(不同图),后续 `{stem}.txt` caption 配对捕捉不到。本版上传 / zip 内 entry / 服务端路径导入全套走 PIL 解码 + PNG 重编码 + 同 stem 冲突加 `_1` / `_2` 后缀(不再因撞名跳过用户图)。设置仍复用 `gelbooru.convert_to_png` / `gelbooru.remove_alpha_channel`(命名搬家留单独 PR)。 +Preprocessing scope pushed down to the train version (ADR 0010) + Lion / Automagic optimizers + tag autocomplete across the front end + WandB artifact uploads + InfoNoise algorithm alignment + +### Added + +- **Lion / Automagic optimizers + cosine_with_warmup scheduler (#214)** + - **Lion** (Chen et al. 2023, arxiv 2302.06675): a sign-momentum optimizer whose state uses half the VRAM of AdamW; the formula is aligned line by line with Google's reference impl and lucidrains/lion-pytorch. + - **Automagic**: AI Toolkit-style per-parameter adaptive lr + sign-agreement tracking + Adafactor factored 2nd moment + 8-bit lr_mask; the core is aligned 1:1 with tdrussell/diffusion-pipe `optimizers/automagic.py`. Adds a dependency on `optimum-quanto>=0.2.0`. The bf16 path uses Kahan compensated summation instead of stochastic rounding to avoid injecting ~scale/2 noise into the lr_mask every step; it deliberately doesn't register a `grad-accum hook`, sidestepping the upstream conflict that silently breaks `GradScaler.unscale_` / `clip_grad_norm_`. + - **cosine_with_warmup**: a LambdaLR-based cosine schedule + linear warmup + eta_min floor; warmup uses step 0-indexing, consistent with the community convention across transformers / diffusers / PEFT / sd-scripts. +- **Tag autocomplete across the front end (#225)** + - **Default tag list**: downloaded in the background after first launch (**not in the repo**); on failure, Settings offers "restore default" to retry. + - **Autocomplete input**: 6 input/textarea spots get completion — TagEditor chip + text mode, TagsInput (Settings + Tagging blacklist), TagStatsPanel inline rename, Regularization custom exclusions, and the Generate positive / negative prompt. + - **New tag-list section in Settings**: current list info / upload a csv to replace / restore default. +- **WandB gains LoRA / training state artifact uploads + Settings / preset overrides (#146)** + The WandB integration is more than metrics / sample logs: + - Adds three kinds of artifact upload: the LoRA model, manual training state, and automatic training state. + - Per-category retention: `all` keeps every version / `last` keeps only the latest (older versions are cleaned from the artifact collection automatically once a new version uploads successfully). + - Defaults can be set in global Settings, and an empty preset field falls back to Settings automatically, so portable / cloud presets don't need to repeat the WandB config. + - The upload path waits for completion via `run.log_artifact(...).wait()` before cleanup, avoiding operations on a draft artifact; large files get periodic progress logs. +- **Outputs gains bulk delete + unified copy-path / open-folder button wording and styling (#217, #224)** + The "Outputs" panel shared by the Queue task detail page and the project Overview: + - Bulk mode adds "delete selected (n)," which deletes by relative path after a second confirmation; if any file is missing, the whole batch is refused with a 404, leaving no half-deleted state. + - Button wording: "Copy" → "Copy path," "Open" → "Open folder." + - Button styling: "Open folder" changes from `btn-secondary` to `btn-ghost` to align with the adjacent copy / refresh buttons; "Delete selected" changes from a red `btn-danger` fill to `btn-ghost text-err`, keeping the danger cue in the text color only. +- **A sticky section-anchor nav in the right column of the training / preset pages (#224)** + - Below the stats panel in the right column of the training page, and on the right of the preset page, a sticky section index is added; clicking smoothly scrolls to the matching SchemaForm group, and the current group highlights automatically as you scroll. + - The main-area ratio on the training / preset pages changes from `1.5fr:1fr` to `3fr:1fr`, giving the field inputs more room to breathe. + +### Changed + +- **Preprocessing scope pushed down to the train version + a preprocessing phase added (ADR 0010) (#209-#213)** + ADR 0010 pushes the working scope of preprocessing (dedup / upscale / crop) down from the project-level `download/` whole set to the training-version-level `train/`, switching the paths and server-side API across the board. + + - **Sidebar step order changes**: ① Download / ② Curate / ③ Preprocess (optional) / ④ Tag ... ⑦ Train. Preprocessing changes from "done on the whole set after download" to "done by each train version after curation," following the same pattern as regularization sets — "optional + skippable." + - **The preprocess grid is the current version's train set**: it no longer shows the whole download set; different versions of the same project can dedup / upscale / crop independently. + - **Path change**: the URL changes from `/projects/:pid/preprocess` to `/projects/:pid/v/:vid/preprocess`; old bookmarks auto-redirect to the current active version. + - **Old projects migrate automatically**: on entering the new version, the `_v11` migration advances any version whose existing `train/{sub-folder}/` is non-empty from the `curating` phase to `preprocessing`; the old `projects/{id}/preprocess/manifest.json` is lazily converted to `versions/{label}/train/manifest.json` when a version is selected, transparently. + - **Restore behavior changes**: when restoring an image, it's copied from `download/{origin}` back over `train/{name}`; if download no longer exists, the UI lists the "no origin" items so you can decide to drag in a replacement / keep / remove (the old version reported a missing error outright). + - The old `/api/projects/{pid}/preprocess/*` endpoints / the old `preprocess_worker.run` project-scope dispatch are removed entirely (ADR 0010 PR-5). Third-party scripts calling the old URL need to switch to the version-scope path. +- **noise_enhancement description rewritten + pyramid default 0.5 + a mutual exclusion added with InfoNoise (#220)** + After three rounds of empirical research, the descriptions and defaults of `noise_enhancement_type` / `noise_offset` / `pyramid_noise_*` were rewritten: + - **`pyramid_noise_discount` default 0.35 → 0.5**: the old default made `noise_enhancement_type=pyramid` effectively a no-op (anima's `cur / cur.std()` normalization cancels a discount ≤0.4 back to baseline); pyramid noise only takes real effect from 0.5 up. yaml with an explicit value is untouched; only fallback-to-default values drift. + - **Descriptions drop SD/DDPM-era lore**: `noise_offset` now states a 0-0.2 range, <0.05 equals off, >0.1 noticeably raises initial loss; `pyramid_noise_iters` states 0-6 controls frequency-band coverage; `pyramid_noise_discount` states 0.1-0.4 equals off, 0.5-0.7 noticeably changes low-frequency structure. + - **`noise_enhancement_type != none` made schema-mutually-exclusive with InfoNoise**: this completes InfoNoise's 4 mutual-exclusion pairs (with `loss_weighting` / `loss_type=huber` / `timestep_schedule_shift` already landed in #216). InfoNoise's I-MMSE derivation assumes standard Gaussian noise — `noise_enhancement_type` alters the noise field, pulling the learned schedule away from its paper-optimal shape. + - **Old configs don't auto-migrate**: an old yaml with `infonoise_enabled=true` plus any of the 4 mutually exclusive fields raises a `ValueError` outright at `TrainingConfig(...)` instantiation; the message names the conflicting field and offers an either/or choice: "(a) turn off InfoNoise and keep X; or (b) reset X to default and use InfoNoise." +- **Reg's "exclude train top tags" changed to a single column in descending frequency + hint removed (#218)** + The Reg page's "exclude train top tags" card previously sorted tags into high / medium / low tiers by occurrence rate with a subtitle; user feedback was that the tiers didn't help actual selection. This version lays out the tag chips in a single column sorted by count descending, and the AI-mode meta line drops the "reverse-generate to push the concept back to base" hint, keeping only the "N excluded" count. + +### Improved + +- **lycoris LoRA forward defaults to bypass_mode, ~1.8x speedup (#196)** + Issue #182: at the same model / config (rank=8 / batch=1 / Anima 1024), AnimaLoraStudio's 0.11 it/s was about 1.8x slower than sd-scripts' 0.19 it/s. The root cause: the default path of lycoris `LoConModule.forward` does a full-rank `F.linear` plus rebuilds the `(out, in)` ΔW matrix every step; across 280 patched layers the cumulative overhead is significant. This version defaults to lycoris's built-in `bypass_mode=True` (`org_forward(x) + lora_up(lora_down(x)) * scale`), matching the standard forward across the community (LoRA paper / sd-scripts / PEFT / diffusers); it's mathematically equivalent, and the speed returns to sd-scripts' level. +- **SettingsDrawer unmounts after closing + backdrop blur removed + local input state eliminates lag (#208)** + - The drawer unmounts from the DOM once the close transition finishes, no longer maintaining a `backdrop-filter: blur(0px)` compositing layer for the whole viewport. + - The overlay's `backdrop-blur-sm` changes to a plain semi-transparent color transition, eliminating scroll frame drops from real-time GPU blur on 2K/4K / integrated-GPU / scaled scenarios. + - Adds a `SettingsInput` with local input state: typing only updates internal state, and `onBlur` / Enter sync to the parent's `draft` in one go, so the 4200-line SettingsPage no longer re-renders the whole page on every keystroke. +- **Training subprocess gets expandable_segments to reduce CUDA fragmentation and avoid low-VRAM OOM (#205)** + At 8GB VRAM + LoKr full-matrix mode (27.5M params), PyTorch's default caching allocator accumulates "reserved but unallocated" memory, and OOM occurs once allocated + reserved approaches the VRAM ceiling. This version sets `os.environ.setdefault("PYTORCH_CUDA_ALLOC_CONF", "expandable_segments:True")` in the training subprocess before `import torch`, affecting only the training subprocess and not the shell environment; using `setdefault` doesn't override a value you've already set. + +### Fixed + +- **Several InfoNoise core-algorithm corrections + reg samples excluded by is_reg (#216, #223)** + An end-to-end mock simulation (96 configs × 4 mmse shapes × 3 grad_accum × 2 baseline × 4 pivot strategies) exposed that the default InfoNoise crammed 99% of the probability mass into the σ_min quartile, so the adaptive sampler never actually took effect. This round fixes 4 algorithm-layer issues + 1 reg-set contamination: + + - **gate pivot `c` default 0.15** (the CIFAR value reported in the paper's §5): the old `above.argmax()`, because the generic 1/σ³ tail saturates `r_norm` to 1.0 at the low-σ end (the warning point in the paper's §B.6), always returned `c ≈ σ_min`, degenerating the gate `σ³/(σ³+c³)` into a near-identity. A new `infonoise_gate_pivot_c` field is an escape hatch: `0` follows the paper-faithful dynamic Eq 87, a positive value overrides explicitly. + - **`r̂_k = mse / σ²`** (the paper's Appendix B.2 Eq 61, the log-σ entropy rate): the old `mse / σ³` came from the VE channel (Eq 59), inconsistent with summing over `Δlog σ`; the missing Jacobian is exactly what let the generic tail dominate normalization. + - **`maybe_refresh` gated by `global_step`** (the optimizer step), no longer by `_internal_step` (the micro-batch count): `N_warm` and the default `total_steps × 20%` both assume optimizer steps, so `grad_accum=4` made warmup exit 4x early, going adaptive before the EMA had converged. + - **`state_dict __version__ = 2`**: σ³ → σ² is a semantic break; resuming a v1 ckpt cold-starts and re-accumulates the EMA with a warning rather than raising, making long-run upgrades safe. + - **Reg samples hard-excluded from InfoNoise records by the `is_reg` flag** (#223): previously a `loss_weight >= 0.99` threshold skipped reg samples, but the `reg_weight=1.0` boundary let reg still enter the records and contaminate the schedule. This now passes an `is_reg` flag through the dataset layer into the batch, and the loop masks with `~batch["is_reg"]`, decoupled from `loss_weight`. Reg samples still enter the gradient weighted by `reg_weight` as usual — they just don't enter InfoNoise schedule learning. +- **InfoNoise's 4 mutually exclusive field pairs get real-time front-end locking + tolerant disabling for old configs + a top banner (#221, #226)** + InfoNoise's 4 schema mutual exclusions with `noise_enhancement_type` / `loss_weighting` / `loss_type=huber` / `timestep_schedule_shift` previously had only a backend validator, with no front-end disable_when. You weren't stopped while editing — you had to wait for the 600ms debounced auto-save to pop a `train.saveFailed` toast, with the form still dirty. + + - **Symmetric lock, non-default wins**: the 4 controlled fields get `disable_when=infonoise_enabled==true`; `infonoise_enabled` gets a reverse compound-OR `disable_when`, so when any conflicting field is non-default its checkbox grays out and locks. On a live switch, the other side resets to default automatically. + - **Tolerant for old configs**: an old yaml with `infonoise=on` plus any conflicting field non-default, when entering the Train page, has the backend `_tolerant_validate` turn InfoNoise off automatically while keeping your original values, so you don't end up with all 4 fields grayed out and locked uneditable. + - **Top banner**: a "Reset: infonoise_enabled" banner now also lights up on the initial config load (not only the fork-preset / save-as-preset paths), so you can immediately see which field was changed for you. + - The "strict save" path (front end bypassing disable_when to PUT directly) still errors out, guarding against a silent value change. +- **VAE full-image decode OOM auto-falls back to tiled decode (#207)** + When generating 1024×1024 reg images on 8GB VRAM, the VAE decode working peak can fill the remaining VRAM and OOM on the very first image. This version adds a try-full + `torch.cuda.OutOfMemoryError` fallback to `_tiled_decode` at the `VAEWrapper.decode` entry (tile=64 latent / 512 px, stride=48, cosine blend mask, fp32 accumulator to prevent bf16 precision loss). + + - **Zero cost on large VRAM**: it passes on the first try and never enters the tile fallback. + - **Fallback on small VRAM**: a single tile's working peak is ~75 MB, each image is ~30% slower, but it completes. + - **All 6 call sites benefit automatically**: reg_ai / generate CLI / generate XY / daemon generate / daemon XY / training sample preview share `sample_image` through the same entry. +- **cache_latents deduplicated by npz to avoid re-encoding the VAE for repeat folders (#199)** + Kohya-style per-folder repeats (a `5_concept` prefix) expand the same image N-fold into `samples`; the old `_build_cache` walked `samples` directly to collect `to_encode`, so the same image was VAE-encoded N times, repeatedly overwriting the same npz — and `flip_augment` mode doubled that again. It now deduplicates by `npz_path`, so first-time cache build time scales with the unique image count; a second run (cache hit) behaves the same. +- **Reg AI priors support JSON captions + tags keep spaces instead of converting to underscores (#185)** + AI-prior generation previously only read `.txt` captions and auto-converted spaces to underscores, so JSON-caption training sets couldn't be read for AI priors, and prompts became `brown_hair` rather than the space-separated tag format Anima recommends. This version: + - reads all three caption formats — `.json / .txt / .caption` — keeping the space-separated tag form in the prompt; + - writes `excluded_tags` back into the reg sidecar in sync (JSON's structured deleted field keeps the original form and `nl` natural language; TXT writes back after deletion); + - matches excluded tags across spaces / underscores, so old booru / UI-style exclusions don't stop working; + - full mode clears the old reg output first to avoid old captions mixing into training; incremental mode only adds new images. +- **CLI argparse compatible with py3.13+ + bare `%` in descriptions auto-escaped (#201)** + Issue #170: on py3.13+, running `anima_train.py` hit `ValueError: invalid option name '--no-progress' for BooleanOptionalAction` (argparse tightened up and no longer allows a flag that itself starts with `--no-`). This version, when a schema bool field name starts with `no_`, uses a `store_true` / `store_false` pair instead of `BooleanOptionalAction`, with CLI behavior unchanged (`--no-progress` → True, `--progress` → False). Also auto-escapes bare `%` to `%%` in descriptions, so argparse's `format_help()` doesn't crash treating a description as a printf template. +- **Dataset uploads go through convert_to_png + same-stem files get an auto suffix (#222)** + When `gelbooru.convert_to_png` is on, booru downloads normalize to `.png`, but dataset uploads / server-side path imports were raw byte passthrough — so downloading and uploading both could leave `1.png` and `1.jpg` (different images) in `download/` at once, and the later `{stem}.txt` caption pairing wouldn't catch them. This version routes uploads / zip entries / server-side path imports all through PIL decode + PNG re-encode + a same-stem collision suffix of `_1` / `_2` (no longer skipping your image on a name clash). The setting still reuses `gelbooru.convert_to_png` / `gelbooru.remove_alpha_channel` (the renaming move is left to a separate PR). --- ## v0.11.1 — 2026-06-01 -Hotfix — v0.10.2 用户 webui 自更新链路双重失败 +Hotfix — a double failure in the webui self-update path for v0.10.2 users -### 修复 +### Fixed -- **v0.10.2 用户检查新版本永远显示「已是最新」,看不到 v0.11.0+(#198)** - 装了 v0.10.2 的用户点 Settings → 系统 → 检查新版本,即便 v0.11.0 已经发布也会显示「已是最新版本」,按钮置灰。受影响:所有 v0.10.2 安装看不到后续任何版本,包括新功能和重要修复。 +- **v0.10.2 users always saw "you're up to date" when checking for updates and couldn't see v0.11.0+ (#198)** + Users on v0.10.2 who clicked Settings → System → Check for updates saw "you're on the latest version" with the button grayed out, even after v0.11.0 had shipped. Affected: every v0.10.2 install couldn't see any later release, including new features and important fixes. - 临时绕过办法(v0.11.1 起不再需要):删除安装目录的 `.git/` 文件夹再重启 Studio。启动期会自动重新初始化仓库并拉齐所有版本标签,再点检查就能看到新版本,然后正常走 webui 升级即可。 -- **v0.10.2 用户点确认更新被 preflight 误判「目标版本早于自更新 feature」阻断(#198)** - v0.10.2 用户即便看到 v0.11.0(如用上面那条临时办法绕过),点「确认更新 → v0.11.0」时也会被 preflight 弹出红色 X「目标版本早于 webui 自更新 feature — 切过去后只能 CLI / shell 升级(webui 无救援能力)」并禁用确认按钮,没法继续。 + Temporary workaround (no longer needed as of v0.11.1): delete the `.git/` folder in the install directory and restart Studio. On startup it automatically reinitializes the repository and pulls down every version tag, after which Check for updates shows the new releases and you can upgrade through the webui as usual. +- **A preflight check wrongly blocked v0.10.2 users from confirming an update, claiming "the target version predates self-update" (#198)** + Even when a v0.10.2 user could see v0.11.0 (for example via the workaround above), clicking "Confirm update → v0.11.0" hit a preflight check that popped a red X — "the target version predates the webui self-update feature — after switching you can only upgrade via CLI / shell (the webui can't rescue you)" — and disabled the confirm button, leaving no way forward. - v0.11.0 服务端文件搬位置后 v0.10.2 client 用旧路径找不到自更新检测标志导致的误判 — 已发布的 v0.10.2 client 没法热修,本版本在仓库里保留兼容文件让旧 client 的检测能通过。升上来之后下一次再升级走正常路径,不会再撞这个问题。 + The misjudgment came from v0.11.0 moving server-side files: the v0.10.2 client looked at the old path and couldn't find the self-update detection flag. The already-released v0.10.2 client can't be hot-fixed, so this release keeps compatibility files in the repository so the old client's check passes. Once you've upgraded, the next upgrade follows the normal path and won't hit this again. --- ## v0.11.0 — 2026-06-01 -正则集编辑工具 + 测试页推理对齐 ComfyUI + flip_augment / LoRA 加载修复 + 日志错误体系(ADR 0009) +Regularization-set editing tools + Generate-page inference aligned with ComfyUI + flip_augment / LoRA loading fixes + logging & error system (ADR 0009) -### 新增 +### Added -- **正则集改可编辑:删图 / 自动去重 / 双 tagger / mirror+flat / 目标数量(#171, #172, #173)** - 正则集从「生成完只能整集清空再重建」演进成可编辑、可去重、可选 tagger 的编辑器,并把生成路线扩展为来源 + 结构两个选择。 +- **Regularization sets are now editable: delete images / auto-dedup / dual tagger / mirror+flat / target count (#171, #172, #173)** + The regularization set has evolved from "once generated, you can only wipe the whole set and rebuild" into an editor that you can edit, dedup, and choose a tagger for, and the generation path now splits into two choices: source + structure. - - **删特定图片**:`RegPreview` 按 `5_concept` / `1_data` 等子文件夹分 tab,多选删除生效到当前 tab;删的 booru ID 写入 `reg/.deleted_ids.json`,下次增量补足时自动 exclude,删掉的图不会再被拉回来。 - - **自动去重循环**:build 完后内置 dedup 循环最多 3 轮(扫重复 → 每组留 1 张 → 不够则自动 incremental 补足),默认开。手动入口「自动去重」按钮保留。 - - **打标方式可选**:auto-tag 复选框旁加 WD14 / CLTagger 二选一下拉(reg 图量大不适合默认走 LLM / JoyCaption,留单独 PR)。 - - **mirror 与 flat 结构**:新增 `结构` 下拉。mirror 沿用旧行为按 train 子文件夹镜像;flat(默认)一桶 `1_data/`,配合「目标数量」输入按数量拉,与 train 张数解耦。 - - **AI 先验默认 + 来源 picker**:deep research 结论 + 用户决策,`generation_method="ai_base"` 升为默认(唯一对齐 DreamBooth 原论文 neutral prior);顶部 radio 切 booru / AI 来源,按 source 渲染 BooruConfig 区或 AiGenPanel,单一「开始生成」按钮派发。 - - **find_best_match 排序公式归一化**:旧公式 `tag_score + res_score * 0.1` 实际 res 反主导排序,新公式按 tag 归一化后 `0.7 * tag_norm + 0.3 * res_score`,让 aspect 真能影响选图(reg 集要跟 train 落同一个 ARB 桶)。 - - 「正则集」改名「正则集(可选)」明示不强制(现代 DiT 训练实际不依赖 reg 集)。 - - `docs/user-guide/training-tips.md` 加 Caption 策略章节,固化人物 / 画风 / 人物+衣服 LoRA 与 reg 集如何配合实现 trigger-as-switch 效果。 -- **PPSF / Prodigy 优化器实际学习率 + d 值监控曲线(#127)** - Prodigy / PPSF 常见配置把 base lr 设为 1,旧监控图因此容易看起来「学习率完全不变」 — 即使实际 schedule 已经通过 `d` 发生收缩或跳变。本次在 Monitor 和 WandB 日志里暴露 PPSF / Prodigy 的真实学习率行为:派生后的实际 lr、`d`、base lr、effective lr,以及可用时的共享分组 `d`。Studio Monitor 加 d 曲线图。 + - **Delete specific images**: `RegPreview` tabs images by subfolder (`5_concept` / `1_data`, etc.); multi-select delete applies to the current tab. Deleted booru IDs are written to `reg/.deleted_ids.json` and auto-excluded on the next incremental top-up, so deleted images won't be pulled back in. + - **Automatic dedup loop**: after a build, a built-in dedup loop runs up to 3 rounds (scan for duplicates → keep 1 per group → top up incrementally if short), on by default. The manual "Auto-dedup" button is still there. + - **Choice of tagging method**: a WD14 / CLTagger dropdown sits next to the auto-tag checkbox (reg sets have a lot of images, so defaulting to LLM / JoyCaption isn't a good fit — that's left for a separate PR). + - **Mirror and flat structures**: a new `Structure` dropdown. mirror keeps the old behavior, mirroring the train subfolders; flat (the default) uses a single `1_data/` bucket and, together with the "target count" input, pulls by count, decoupled from the number of train images. + - **AI priors by default + a source picker**: from deep-research findings + a user decision, `generation_method="ai_base"` is now the default (the only path that aligns with the original DreamBooth paper's neutral prior). A radio at the top switches between booru / AI sources, rendering the BooruConfig area or AiGenPanel by source, with a single "Generate" button dispatching either. + - **find_best_match ranking formula normalized**: the old formula `tag_score + res_score * 0.1` let resolution actually dominate the ranking; the new formula normalizes the tag score first, then uses `0.7 * tag_norm + 0.3 * res_score`, so aspect ratio can really influence which images are picked (the reg set needs to land in the same ARB bucket as train). + - "Regularization set" renamed "Regularization set (optional)" to make clear it isn't required (modern DiT training doesn't actually depend on a reg set). + - `docs/user-guide/training-tips.md` gains a Caption strategy chapter, codifying how character / style / character+outfit LoRAs and the reg set work together to get the trigger-as-switch effect. +- **Actual learning rate for PPSF / Prodigy optimizers + a d-value monitor curve (#127)** + Common Prodigy / PPSF configs set the base lr to 1, so the old monitor chart could easily look like "the learning rate never changes" — even when the actual schedule has already contracted or jumped via `d`. This release surfaces the real learning-rate behavior of PPSF / Prodigy in the Monitor and WandB logs: the derived actual lr, `d`, base lr, effective lr, and the shared group `d` when available. The Studio Monitor gains a d-curve chart. - 同时把 `ppsf_prodigy_steps=0` 恢复 PPSF 上游语义 — 训练全程不冻结 `d`;用户显式填具体步数时才开启冻结。旧默认值反向冻结了 `d`,对小数据集 LoRA 系统性低估 lr。 -- **预处理去重审核加模糊候选 + 裁剪 / 缩放关系报告(#161)** - 预处理「去重审核」工具在已有完全重复 / 近似重复审核基础上,新增两类质量报告: + It also restores `ppsf_prodigy_steps=0` to PPSF's upstream semantics — `d` is never frozen during training; freezing only kicks in when you explicitly set a step count. The old default froze `d` the wrong way around, systematically underestimating lr for small-dataset LoRAs. +- **Preprocess dedup review gains blurry candidates + a crop / resize relationship report (#161)** + On top of its existing exact-duplicate / near-duplicate review, the preprocess "dedup review" tool adds two new kinds of quality reports: - - **模糊 / 低细节候选**:用全局 Laplacian variance + 12×12 局部低细节区域 + 最大连通区域比例,避免只看整图均值漏掉局部低细节图。 - - **裁剪 / 缩放关系**:检测一张图是另一张的裁剪 / 局部放大 / 裁剪后重新缩放保存。用 crop-resistant hash + 结构 hash 预筛,灰度窗口匹配 + 局部精修验证,返回源图 + 候选 + 窗口坐标 + 匹配分 + 关系类型(`crop_smaller` / `crop_upscaled` / `crop_same_area`)。 + - **Blurry / low-detail candidates**: uses a global Laplacian variance + 12×12 local low-detail regions + the largest connected-region ratio, so it doesn't miss images with locally low detail just by looking at the whole-image average. + - **Crop / resize relationships**: detects when one image is a crop / local upscale / crop-then-resave of another. It prefilters with a crop-resistant hash + a structure hash, then verifies with grayscale-window matching + local refinement, returning the source image + candidate + window coordinates + match score + relationship type (`crop_smaller` / `crop_upscaled` / `crop_same_area`). - 参数区加模糊分数 / 局部模糊 / 裁剪分数 / 裁剪 Hash / 裁剪边长等高级阈值,以及裁剪线程 / 裁剪分段 / 分段覆盖 / 裁剪宽高比 / 每图候选等性能 / 召回控制。质量候选默认只是报告不自动入移除队列,用户逐张标记或一键批量选中,仍需点「确认去除」才写入 manifest 的 `duplicate_removed`。 -- **CLTagger 加 Copyright / Meta / Quality 等 5 类 category 开关(#184)** - CLTagger 模型输出 7 个 category(General / Character / Copyright / Meta / Model / Rating / Quality),此前只对 Rating / Model 提供开关,其余 5 类全部隐式走通用阈值。结果 LoRA caption 默认会混进 `highres` / `lowres`(Meta)、`best quality`(Quality)、`fate_series`(Copyright)这些 LoRA 训练通常不想要的标。 + The parameters area gains advanced thresholds for blur score / local blur / crop score / crop hash / crop edge length, plus performance / recall controls for crop threads / crop tiles / tile coverage / crop aspect ratio / candidates per image. Quality candidates are reports only by default and aren't auto-added to the removal queue; you mark them one by one or batch-select them all at once, and you still have to click "Confirm removal" to write `duplicate_removed` into the manifest. +- **CLTagger gains Copyright / Meta / Quality and other category toggles (5 in all) (#184)** + The CLTagger model outputs 7 categories (General / Character / Copyright / Meta / Model / Rating / Quality). Previously only Rating / Model had toggles, while the other 5 all implicitly used the general threshold. As a result, LoRA captions would by default mix in tags like `highres` / `lowres` (Meta), `best quality` (Quality), and `fate_series` (Copyright) that you usually don't want for LoRA training. - 新增 3 个 checkbox:`add_copyright_tag`(默认开)、`add_meta_tag`(默认关)、`add_quality_tag`(默认关),加上原有的 rating / model 共 5 类 gate。General / Character 由阈值控制不加 checkbox。Tagging 页 + Settings 全局两处 UI 同步暴露。 + Three new checkboxes: `add_copyright_tag` (on by default), `add_meta_tag` (off by default), `add_quality_tag` (off by default), making 5 category gates with the existing rating / model. General / Character are controlled by the threshold and have no checkbox. Both the Tagging page and the global Settings expose this UI in sync. - 升级后默认 caption 不再包含 `highres` / `best quality` / `explicit` / `lora_v1` 这类标;Copyright(作品名)保留是 LoRA 标准 caption 形态。要恢复任意 category 在 Settings 或 Tagging 页勾上即可。 -- **后端 + 前端日志 / 错误体系统一(ADR 0009)(#155, #156, #157)** - 按 [ADR 0009](docs/adr/0009-logging-error-system.md) 把后端 + 前端 + CLI 的日志 / 错误处理收编到统一基础设施。用户能感知到的变化: + After upgrading, default captions no longer include tags like `highres` / `best quality` / `explicit` / `lora_v1`; Copyright (the work's title) is kept, which is the standard LoRA caption shape. To bring any category back, just tick it in Settings or on the Tagging page. +- **Unified logging / error system across backend + frontend (ADR 0009) (#155, #156, #157)** + Following [ADR 0009](docs/adr/0009-logging-error-system.md), backend + frontend + CLI logging / error handling are brought together onto shared infrastructure. What you'll notice: - - **错误 toast / ErrorBoundary 带 trace ID 后 8 位**:API 4xx / 5xx 出错时 toast 末尾显示 `trace ab12cd34`,前端崩溃 ErrorBoundary 也显示同一 trace;截图给开发能直接定位后端日志条目。 - - **前端崩溃自动上报**:`window.error` / `unhandledrejection` / `ErrorBoundary` 三路全局错误捕获,POST `/api/client-errors`(per-IP 10/min 限流),后端落 `studio.client` logger。tab 关闭瞬间也尽量送出(`fetch keepalive: true`)。 - - **后端 trace_id 全链路**:每个请求在 middleware 生成 trace_id(X-Trace-Id header 回带),跨进程通过 env 传到 worker 子进程,落进 `studio_data/logs/studio.log` 的 JSON 行,方便 `jq '.trace_id'` 过滤一条请求的全部日志。 - - **CLI 输出统一**:`python -m studio` 命令 48 处 `print("[studio] ...")` 收编到 `_say()` wrapper,终端看到的 `[studio] X` 格式不变,新加内容也走它。 - - **error_msg 写回 db**:训练 / tagging 等 worker 顶层异常 traceback 写进 `tasks.error_msg`,任务详情页能直接看到失败原因而不是仅「exit code 1」。 -- **新建预设改一键创建并套用,删两步式草稿表单(#177)** - 旧流程:项目页 Train 点「+ 新建预设」会进入「内联草稿表单」状态(仅 React state flag,零持久化),用户填字段后切到别的页面会把整个草稿丢光 — `savePreset` 一次都没调,再回来又显示「请选择预设」。底部的「创建并套用到当前 version」按钮才是真正的保存入口,但 `noConfigHint` 文案让用户以为点「+ 新建预设」就在创建了。 + - **Error toasts / ErrorBoundary carry the last 8 digits of a trace ID**: on an API 4xx / 5xx error, the toast ends with `trace ab12cd34`, and a frontend crash ErrorBoundary shows the same trace; a screenshot lets a developer go straight to the matching backend log entry. + - **Frontend crashes auto-report**: global error capture across three paths — `window.error` / `unhandledrejection` / `ErrorBoundary` — POSTs to `/api/client-errors` (rate-limited 10/min per IP), and the backend writes it to the `studio.client` logger. It also tries to send the moment a tab closes (`fetch keepalive: true`). + - **Backend trace_id end to end**: every request generates a trace_id in middleware (returned via the X-Trace-Id header), passed across processes via env to the worker subprocess, and written into the JSON lines of `studio_data/logs/studio.log`, so `jq '.trace_id'` can filter every log line for one request. + - **Unified CLI output**: the 48 `print("[studio] ...")` calls across the `python -m studio` commands are folded into a `_say()` wrapper; the `[studio] X` format you see in the terminal is unchanged, and new output goes through it too. + - **error_msg written back to the db**: top-level exception tracebacks from the training / tagging and other workers are written into `tasks.error_msg`, so the task detail page shows the failure reason directly instead of just "exit code 1." +- **Creating a preset is now one-click create-and-apply; the two-step draft form is gone (#177)** + Old flow: clicking "+ New preset" under Train on the project page entered an "inline draft form" state (just a React state flag, zero persistence). After filling fields, switching to another page lost the whole draft — `savePreset` was never called once, and coming back showed "please select a preset" again. The "Create and apply to the current version" button at the bottom was the real save action, but the `noConfigHint` copy made you think clicking "+ New preset" was already creating one. - 新流程:点「+ 新建预设」直接创建并套用 — 自动生成名称 `<项目 slug>_<版本 label>`,重名加 `_1` / `_2` 后缀,配置取 schema 默认(开启「自动同步模型路径」时项目路径覆盖),写入全局预设池并 fork 到当前 version。version 已有 config 时弹覆盖 confirm。删除内联草稿表单与相关 state,UX 陷阱消失。 -- **浏览器原生上传进度条 + 网速 + ETA(#139)** - 三处上传(项目本地图 / zip、训练集 bundle import、train zip import)从 `fetch` 迁到 `XMLHttpRequest` 拿到 body progress 事件(fetch 无此能力),显示进度条 + 网速 + 剩余时间。1.5s 滑动窗口算速度,`loaded === total` 后自动切到「服务器处理中」状态区分上传完成与解 zip / 落盘等待。Bundle import dialog 改为上传期间保持打开显示进度条,成功后才关闭。 + New flow: clicking "+ New preset" creates and applies immediately — it auto-generates the name `_`, adds a `_1` / `_2` suffix on name collisions, takes config from the schema defaults (with project paths overriding when "auto-sync model paths" is on), writes to the global preset pool, and forks to the current version. If the version already has a config, an overwrite confirm pops up. The inline draft form and its state are removed, and the UX trap is gone. +- **Native browser upload progress bar + speed + ETA (#139)** + All three uploads (project local images / zip, training-set bundle import, train zip import) move from `fetch` to `XMLHttpRequest` to get body progress events (which fetch can't provide), showing a progress bar + speed + time remaining. A 1.5s sliding window computes the speed, and once `loaded === total` it switches to a "server processing" state to distinguish upload completion from the unzip / disk-write wait. The bundle import dialog now stays open during upload to show the progress bar, and only closes on success. -### 变更 +### Changed -- **`noise_offset` 与金字塔噪声从同开改为互斥单选,对齐 kohya 上游(#175)** - kohya-ss/sd-scripts PR #477(2023-05)在 SD1 / SDXL / SD3 / Flux 全模型层面禁止 `noise_offset` 与 `multires_noise_iterations` 同开 — 两者都向噪声注入低频成分,pyramid 最低分辨率那层 ≈ noise_offset 等价物,叠加导致低频双倍灌入。Anima 旧实现没禁,且 `make_noise` 末尾的归一化会稀释先加的 noise_offset 常数偏移 — 用户以为两者都生效,实际等效「只 pyramid 在跑、offset 被吃掉」,比 kohya 报错更隐蔽。 +- **`noise_offset` and pyramid noise change from both-on to a mutually exclusive single choice, aligning with kohya upstream (#175)** + kohya-ss/sd-scripts PR #477 (2023-05) forbids `noise_offset` and `multires_noise_iterations` from being on together across all models (SD1 / SDXL / SD3 / Flux) — both inject low-frequency content into the noise, and pyramid's lowest-resolution layer ≈ a noise_offset equivalent, so stacking them double-injects low frequencies. Anima's old implementation didn't forbid it, and the normalization at the end of `make_noise` would dilute the constant noise_offset bias added earlier — you'd think both were in effect, when it was effectively "only pyramid running, the offset eaten," which is more insidious than kohya's hard error. - 新增 `noise_enhancement_type: "none" | "offset" | "pyramid"` 字段(前端 dropdown),互斥反组字段自动隐藏并强制清零。老 yaml 自动迁移:两者都 > 0 时按 pyramid 优先(旧实现里 pyramid 才是实际生效的),offset 字段清零。 -- **Settings 改右滑抽屉,全局可调起 + 不打断当前页(#132, #133)** - Settings 页(原路由 `/tools/settings`)改成右侧滑出抽屉,宽度 `max(1024px, 70vw)`,关闭支持点 backdrop / ESC / 标题栏 ×。8 处入口(侧栏「设置」/ Topbar / CommandPalette / Train / Presets / Tagging 4 处)改为打开抽屉而非跳转路由,旧 URL 由兼容 redirect 落地。抽屉打开时侧栏仍可点(切项目 / 切版本 / 切主题不必先关),dirty 状态关抽屉弹 confirm。 + Adds a `noise_enhancement_type: "none" | "offset" | "pyramid"` field (a frontend dropdown); the mutually exclusive opposing fields auto-hide and are forced to zero. Old yaml migrates automatically: when both are > 0, pyramid takes priority (pyramid was the one actually in effect in the old implementation), and the offset field is zeroed. +- **Settings becomes a right-slide drawer, available globally + without interrupting the current page (#132, #133)** + The Settings page (formerly the `/tools/settings` route) becomes a right-side slide-out drawer, width `max(1024px, 70vw)`, dismissible via backdrop / ESC / the × in the title bar. The 8 entry points (sidebar "Settings" / Topbar / CommandPalette / Train / Presets / Tagging at 4 spots) now open the drawer instead of navigating a route, and the old URL is handled by a compatibility redirect. The sidebar stays clickable while the drawer is open (switching project / version / theme doesn't require closing it first), and closing the drawer in a dirty state pops a confirm. - 数据层提到根级 Provider(secrets / catalog / SSE 持久化),抽屉 mount / unmount 不再重拉 secrets。Settings 改 `React.lazy` 独立分包(~116KB / gzip 27KB),不进首屏 bundle。 -- **task 档案搬到 tasks/id/ — 同 version 多 task 不再串台 + 删 version 不丢历史(#166, #192)** - 训练 task 的 monitor state / 采样图 / run log 从 `versions//monitor/task_/` 搬到 `studio_data/tasks//`,跟 task config snapshot 同根: + The data layer is lifted to a root-level Provider (secrets / catalog / SSE persistence), so mounting / unmounting the drawer no longer re-fetches secrets. Settings is split into its own `React.lazy` chunk (~116KB / 27KB gzipped), kept out of the first-screen bundle. +- **Task archives moved to tasks/id/ — multiple tasks in the same version no longer cross-contaminate + deleting a version doesn't lose history (#166, #192)** + A training task's monitor state / sample images / run log move from `versions//monitor/task_/` to `studio_data/tasks//`, sharing a root with the task config snapshot: ``` - tasks//snapshot/config.yaml ← task 启动 freeze 的 config - tasks//monitor/state.json ← loss / LR / sample 索引 - tasks//samples/*.png ← 训练采样图 + tasks//snapshot/config.yaml ← the config frozen at task launch + tasks//monitor/state.json ← loss / LR / sample index + tasks//samples/*.png ← training sample images tasks//run.log ← worker stdout/stderr ``` - - 删 version 不再丢历史 task:回头想看「上次跑这套配置的 loss 曲线 / 采样图」不必为此保留对应 version。 - - 同 version 下连跑多个 task 不再串台:旧实现里第二个 task 的 monitor state 会盖掉第一个,点 task1 「查看」看到的是 task2,本 release 修复。 - - 留在 version 级:`reg/`(多 task 复用)、`output/*.safetensors`(version 交付物)、`config.yaml`、`train/`、`caption_snapshots/`。 - - 老 task 兼容:DB `monitor_state_path` 列保留旧值不动,sample / log 读端保留对旧路径的 fallback 候选,pre-PP6.1 (< v0.5.0) 历史 task 仍可读。 -- **schema 字段说明全量审阅 + 修 4 处方向反的描述 + `noise_schedule` 拆 3 组(#194)** - - **4 处方向反的描述修正**:`ppsf_d_coef` / `prodigy_d_coef` 旧说「过拟合 → 2.0」反了,正确是欠拟合调高、过拟合调低;`huber_c` 旧说反了 δ 大小方向(δ 越大越接近 MSE,越小越宽容 outlier);`min_snr_gamma` 抑制的是低 t 端高 SNR 简单步(rectified flow 下 SNR 与 t 反向)。按旧描述调参的用户实际把方向反了。 - - **`noise_schedule` 组拆 3 组**:原 22 项实际混了 3 个关注点,拆为 `noise_augmentation`(4 项 noise_offset / pyramid)/ `timestep_sampling`(11 项 timestep + InfoNoise)/ `loss`(7 项 huber / min_snr / detail_inv_t)。前端配置页对应改 3 个 group label。 - - **用语清理**:删 dev-note(「kohya 上游同样禁止同开」等内部参考)、删 PR 作者主观经验(「雾蒙蒙画风建议 3」等无 paper 支持的断言)、可调数值参数统一补「越大 / 越小有什么表现」、无上界字段补典型范围警告。 -- **「全局代理」改名「Booru 代理」+ 移到数据集 tab + 删独立网络 tab(#191)** - v0.10.3 加的「全局代理」section 在 UI 上叫「全局代理」,但实际数据流只接 booru 下载链路(ModelScope / HuggingFace 下载都不走它)。原版本里有段试图把代理灌进 hf_hub httpx client 的代码 import 了 hf_hub 里不存在的符号,hotfix #165 删掉后只剩 booru 一条链路在用。 + - Deleting a version no longer loses historical tasks: to look back at "the loss curve / sample images from the last run of this config," you no longer need to keep the corresponding version around. + - Running several tasks back to back under one version no longer cross-contaminates: in the old implementation the second task's monitor state would overwrite the first, so clicking "View" on task1 showed task2 — fixed in this release. + - Staying at the version level: `reg/` (reused across tasks), `output/*.safetensors` (the version's deliverable), `config.yaml`, `train/`, `caption_snapshots/`. + - Old-task compatibility: the DB `monitor_state_path` column keeps its old value untouched, the sample / log readers keep a fallback candidate for the old paths, and pre-PP6.1 (< v0.5.0) historical tasks are still readable. +- **Full review of schema field descriptions + fixed 4 backwards descriptions + `noise_schedule` split into 3 groups (#194)** + - **4 backwards descriptions corrected**: `ppsf_d_coef` / `prodigy_d_coef` used to say "overfitting → 2.0," which is backwards — you raise it when underfitting and lower it when overfitting; `huber_c` had the δ direction backwards (the larger δ is, the closer to MSE; the smaller, the more tolerant of outliers); `min_snr_gamma` suppresses the high-SNR easy steps at low t (under rectified flow, SNR and t run in opposite directions). Anyone tuning by the old descriptions had the direction reversed. + - **`noise_schedule` group split into 3**: the original 22 items actually mixed 3 concerns, split into `noise_augmentation` (4 items, noise_offset / pyramid) / `timestep_sampling` (11 items, timestep + InfoNoise) / `loss` (7 items, huber / min_snr / detail_inv_t). The frontend config page's group labels change to match, 3 groups. + - **Wording cleanup**: removed dev-notes (internal references like "kohya upstream forbids this too"), removed PR-author subjective experience (paper-unsupported assertions like "for hazy art styles, try 3"), tunable numeric parameters consistently note "what larger / smaller does," and unbounded fields get a typical-range warning. +- **"Global proxy" renamed "Booru proxy" + moved to the Dataset tab + removed the standalone Network tab (#191)** + The "Global proxy" section added in v0.10.3 was called "Global proxy" in the UI, but the actual data flow only feeds the booru download path (neither ModelScope nor HuggingFace downloads go through it). The original version had a chunk of code trying to push the proxy into the hf_hub httpx client that imported a symbol that doesn't exist in hf_hub; after hotfix #165 removed it, only the booru path was left using it. - UI 改:section 改名「Booru 代理」、移到数据集 tab 末尾(与 Gelbooru / Danbooru 同 tab)、删独立的「网络」tab、文案明示「仅覆盖 Booru 下载链路,模型下载不走此代理」。不动 `secrets.proxy` schema,用户已存配置不丢,老 localStorage 残留的 `activeTab="network"` 自动降级到默认 tab。模型下载走代理仍需 OS 层 `HTTP_PROXY` / `HTTPS_PROXY` 环境变量或 `HF_ENDPOINT` 镜像。 + UI changes: the section is renamed "Booru proxy," moved to the end of the Dataset tab (same tab as Gelbooru / Danbooru), the standalone "Network" tab is removed, and the copy makes clear "covers only the Booru download path; model downloads don't use this proxy." The `secrets.proxy` schema is untouched, so your saved config isn't lost, and a stale `activeTab="network"` left in old localStorage auto-degrades to the default tab. Routing model downloads through a proxy still needs OS-level `HTTP_PROXY` / `HTTPS_PROXY` environment variables or an `HF_ENDPOINT` mirror. -### 改进 +### Improved -- **监控页 charts / 采样图 / 配置字段顺序 / 打标 blacklist 输入修复(#166, #186, #187, #189)** - 监控页和打标页一组 UI 修复,多轮迭代逐步收敛: +- **Monitor page charts / sample images / config field order / tagging blacklist input fixes (#166, #186, #187, #189)** + A batch of UI fixes on the Monitor and Tagging pages, converging over several iterations: - - **chart 兼顾大小屏**:右列三图改 `useLayoutEffect` + `ResizeObserver` 测真实像素尺寸,SVG 1:1 渲染,文字 / 线宽 / 圆点不再被父容器宽高比扭曲;xy 轴字号 9 → 13、padding 加大让 `0.0796` 这种 6 字宽 y-label 不越界;首 / 末 x-tick 改 `start` / `end` 锚点防边缘字一半溢出;右三卡夹在 `[140, 300]px` 防 4K 比例失衡;1080p 不再多余滚动条。 - - **d 曲线独立成第三张卡**:之前夹在 LR 内部时 50px 高度被压扁,独立成卡后高度跟 loss / LR 对齐。 - - **smooth slider 分开**:loss 默认 0.02、LR / d 默认 off(LR / d 本身已是 EMA 派生量,再叠 EMA 失真)。 - - **采样图点击放大**:监控页采样大图原来点击无反应,现在加 `cursor-zoom-in` + 全屏预览 + ← / → 在采样序列前后切。 - - **采样图卡片不再被原图撑爆**:sample 大图 img 改 `absolute inset-0` 脱 in-flow,避免浏览器把 1024×* 原图 intrinsic 尺寸当作父容器 min-content 把 sample 卡顶到原图高度。 - - **VersionRail 切版本同步 sidebar**:项目概览页 VersionRail 切版本时漏调 `ctx.onSelectVersion`,sidebar 的 phase nav 不联动;补一行调用即修。 - - **打标 blacklist 输入打不进逗号 / 空格**:受控输入每次按键都把文本 split → trim → filter 回数组,逗号产生的空段被 filter 掉,尾随空格被 trim 掉,连 `blue eyes` 都打不出。抽 `TagsInput` 组件:编辑态(focus)纯文本,blur 时再渲染成 chip。Tagging 页 + Settings 全局两处同步。 + - **Charts handle both large and small screens**: the three charts in the right column now use `useLayoutEffect` + `ResizeObserver` to measure real pixel dimensions and render the SVG 1:1, so text / line width / dots aren't distorted by the parent's aspect ratio; the x/y axis font goes 9 → 13 and padding increases so a 6-character-wide y-label like `0.0796` doesn't overflow; the first / last x-tick switch to `start` / `end` anchors so edge labels don't spill halfway off; the three right cards are clamped to `[140, 300]px` to avoid 4K proportion imbalance; and 1080p no longer gets a stray scrollbar. + - **The d curve becomes its own third card**: previously crammed inside LR, its 50px height was squashed; as its own card the height aligns with loss / LR. + - **Smooth sliders separated**: loss defaults to 0.02, LR / d default to off (LR / d are already EMA-derived, so stacking another EMA distorts them). + - **Click to enlarge sample images**: the large sample images on the Monitor page didn't respond to clicks before; now they get `cursor-zoom-in` + fullscreen preview + ← / → to move through the sample sequence. + - **Sample image cards no longer blown out by the source image**: the large sample `img` switches to `absolute inset-0` to leave the in-flow, so the browser doesn't treat the 1024×* source image's intrinsic size as the parent's min-content and push the sample card up to the source's height. + - **VersionRail syncs the sidebar when switching versions**: switching versions in the project overview's VersionRail missed calling `ctx.onSelectVersion`, so the sidebar's phase nav didn't follow along; one added call fixes it. + - **Tagging blacklist input couldn't type commas / spaces**: the controlled input split → trim → filter the text back into an array on every keystroke, so the empty segment a comma produces got filtered out and a trailing space got trimmed, so you couldn't even type `blue eyes`. Extracted a `TagsInput` component: plain text while editing (focused), rendered into chips on blur. Synced across both the Tagging page and the global Settings. -### 修复 +### Fixed -- **测试页推理与 ComfyUI 出图对齐 + LoRA DoRA / rs_lora 不再静默失效(#169)** - 用户反馈两个独立现象:测试页出图「LoRA 完全没生效」、同样配置下 Studio 出图与 ComfyUI 差别明显。逐项排查 ComfyUI 原生 Anima 实现后确认是 4 个不同层面的对齐 bug。 +- **Generate-page inference aligned with ComfyUI output + LoRA DoRA / rs_lora no longer silently fails (#169)** + Users reported two separate symptoms: on the Generate page "the LoRA had no effect at all," and the same config produced clearly different output in Studio vs. ComfyUI. Going through the native ComfyUI Anima implementation item by item confirmed 4 alignment bugs at different levels. - - **LoRA DoRA / rs_lora 透传**:`read_lora_meta` 漏读 `ss_network_args` 里的 `weight_decompose`(DoRA)和 `rs_lora` 两个字段,`apply_loras` 构造网络结构与训练时不一致 — DoRA 的 280 个 `dora_scale` 张量全部 unexpected 跳过,rs_lora 的 α 缩放被砍到训练时的 1/8。两者叠加后用户感知就是「LoRA 完全没生效」。 - - **T5 token 权重生效**:`(tag:1.3)` 这类 T5 token 权重语法之前算出来后被 `preprocess_text_embeds` 丢弃,权重无效;现在按 ComfyUI 在 LLMAdapter 输出后乘 t5xxl_weights。 - - **初始噪声跨硬件复现**:`torch.randn(device='cuda')` 走 cuRAND(跨硬件不一致),改成 CPU generator + `.to(device)`,同 seed 跨平台可复现。 - - **默认负面 prompt**:用户填空时不再偷塞 `"worst quality, low quality, score_1, ..."` 一长串,CFG 锚点跟 ComfyUI 一致。 + - **LoRA DoRA / rs_lora pass-through**: `read_lora_meta` missed reading the `weight_decompose` (DoRA) and `rs_lora` fields from `ss_network_args`, so `apply_loras` built a network structure inconsistent with training — all 280 of DoRA's `dora_scale` tensors were skipped as unexpected, and rs_lora's α scaling was cut to 1/8 of what training used. The two together are what you perceive as "the LoRA had no effect at all." + - **T5 token weights take effect**: T5 token weight syntax like `(tag:1.3)` was computed but then discarded by `preprocess_text_embeds`, so the weights had no effect; now, following ComfyUI, the t5xxl_weights are multiplied in after the LLMAdapter output. + - **Initial noise reproducible across hardware**: `torch.randn(device='cuda')` goes through cuRAND (inconsistent across hardware); switched to a CPU generator + `.to(device)`, so the same seed is reproducible across platforms. + - **Default negative prompt**: when you leave it blank, it no longer sneaks in a long `"worst quality, low quality, score_1, ..."` string, so the CFG anchor matches ComfyUI. - 平行用多个 agent 逐文件对比 ComfyUI 原生 Anima 实现(VAE / DiT 685 key / RoPE 3D / Sampler / Scheduler / CFG)后确认其余路径完全等价,本次只改这 4 处。 -- **cache_latents 与 flip_augment 同开导致 50% 数据被永久镜像污染(#176)** - `CachedLatentDataset` cache 阶段调 `ImageDataset.__getitem__` 拿 pixel,但 base dataset 里的 `if flip_augment and random.random() > 0.5: img.transpose(...)` 在 cache 阶段就掷骰子 — 每张图永久编码成翻转 / 不翻转其中一个状态,之后训练只读 npz 不再 invoke base flip。后果:flip_augment 静默失效(用户以为有数据增强,实际没有),并且 50% 数据被永久镜像污染(每次启训随机一半图被翻成镜像)。 + After comparing the native ComfyUI Anima implementation file by file in parallel with several agents (VAE / DiT 685 keys / RoPE 3D / Sampler / Scheduler / CFG) and confirming the rest of the path is fully equivalent, this release changes only these 4 spots. +- **cache_latents and flip_augment on together permanently mirror-pollutes 50% of the data (#176)** + During its cache stage, `CachedLatentDataset` calls `ImageDataset.__getitem__` to get pixels, but the base dataset's `if flip_augment and random.random() > 0.5: img.transpose(...)` rolls the dice during the cache stage — every image is permanently encoded as either flipped or not, and training afterward only reads the npz and never invokes the base flip. The consequence: flip_augment silently does nothing (you think you have data augmentation, but you don't), and 50% of the data is permanently mirror-polluted (a random half of the images get mirrored at each training launch). - 按 kohya-ss/sd-scripts 的 `ImageInfo.latents / latents_flipped` 双份 latent 方案修复:cache 阶段按 `flip_augment` 决定单 / 双份编码(双份存 npz 的 `latent` + `latent_flipped` 两键),训练时 `__getitem__` 按 `random.random() > 0.5` 选哪个键。代价:`flip_augment=True` 时编码时间和 cache 大小都 ×2,但训练里 flip 真生效。 + Fixed following kohya-ss/sd-scripts' `ImageInfo.latents / latents_flipped` dual-latent approach: the cache stage encodes single / double copies depending on `flip_augment` (the double stores both `latent` + `latent_flipped` keys in the npz), and at training time `__getitem__` picks which key by `random.random() > 0.5`. The cost: with `flip_augment=True`, both the encode time and cache size are ×2, but flip really takes effect in training. - 老 cache 自动修复:`_is_cache_valid` 检测 `flip_augment=True` 但 npz 缺 `latent_flipped` 键时失效重 encode;反向 `flip_augment=False` 时双份仍有效(超集),切开关不反复重编码。 -- **测试页 XY 图混入未选过的 LoRA(孤儿 anchor 沉积,自 v0.8.0 起)(#167)** - XY 模式下用户反复看到「混进没选过的 LoRA」:XY 轴选了 chenbin V3.4,结果每个 cell 都被钉上没选过的 chen-bin v3.2 或跨 mode 的 hoshi。清 localStorage / 换匿名浏览器都复现,与前端缓存无关。 + Old caches self-heal: `_is_cache_valid` invalidates and re-encodes when `flip_augment=True` but the npz is missing the `latent_flipped` key; conversely, with `flip_augment=False` the double copy is still valid (a superset), so toggling the switch doesn't re-encode back and forth. +- **Generate-page XY grid mixed in LoRAs that were never selected (orphan anchor buildup, since v0.8.0) (#167)** + In XY mode users kept seeing "LoRAs they never selected" mixed in: the XY axis had chenbin V3.4 selected, yet every cell got pinned with an unselected chen-bin v3.2 or a cross-mode hoshi. It reproduced even after clearing localStorage / switching to an anonymous browser, unrelated to the frontend cache. - 根因(自 v0.8.0 XY 功能起就在):XY 模式往 LoRA 桶加 anchor 的唯一入口是 `lora_ckpt` 轴 picker,但它「只 push 不 prune」 — 切项目 / 切轴类型 / 删 Y 轴只清轴绑定,桶里的 anchor 不动。XY 侧栏没有 base-LoRA 列表 UI,用户看不到也删不掉。提交时旧逻辑把整桶当 base LoRA 发,后端把它们叠到每个 cell,孤儿 LoRA 就恒定出现在所有图里。历次修复(包括最近的双桶 split)只解了跨 mode 串味,从没碰桶内孤儿沉积 + 整桶当 base 发这一层。 + Root cause (present since the v0.8.0 XY feature): the only entry point that adds an anchor to the LoRA bucket in XY mode is the `lora_ckpt` axis picker, but it "only pushes, never prunes" — switching project / switching axis type / deleting the Y axis only clears the axis binding, leaving the bucket's anchors untouched. The XY sidebar has no base-LoRA list UI, so you can't see or delete them. At submit, the old logic sent the whole bucket as base LoRAs, and the backend stacked them onto every cell, so the orphan LoRAs appeared constantly in every image. Past fixes (including the recent dual-bucket split) only addressed cross-mode bleed and never touched the in-bucket orphan buildup + sending the whole bucket as base. - 修复:抽出纯函数 `buildXYMatrix`,提交时只保留被 X / Y 轴 `loraIndex` 引用的 anchor,按出现序重映射索引,孤儿丢弃;非 lora_ckpt 轴时 `lora_configs` 为空。 -- **测试页生成入队竞态 (`config not found`) + 跨 single / xy 模式 LoRA 串味(#159)** - - **入队竞态**:`enqueue_generate` 先把任务落成 `pending+generate` 即可被派发,随后才构建 config(含耗时的 `detect_attention_backend`)并写 `config_path`。supervisor 每 1s tick,若在「`task_type` 已是 generate、`config_path` 还是 NULL」的窗口派发,daemon 读到空路径直接 failed。改为 `_dispatch_generate` 跳过 `config_path` 未落库的任务(下个 tick 再派),config 构建失败时把任务标 failed 避免永远 pending。 - - **single / xy 模式 LoRA 串味**:`loras` 持久化为单份共享数组,single 加 A 切到 xy 加 B,xy 生成吃 A+B,切回 single 又显示 A+B。拆成 `singleLoras` / `xyLoras` 双桶按 mode 路由(compare 共用 xy 桶),老配置自动迁移到两桶各一份不丢已选。 -- **XY lora_ckpt 轴按 ckpt 展示序排列,不再随点击顺序乱跳(#164)** - 测试页 XY 图的 `lora_ckpt` 轴(扫同一 LoRA 不同 epoch / step ckpt 找过拟合拐点)选中多个 ckpt 时,轴值按用户点击 chip 的先后顺序排,不是 epoch / step 顺序。后果:选 ep80 / ep60 / ep40 如果点击顺序乱(80 → 40 → 60),网格列就排成 `[80, 40, 60]`,ep60 落到末尾,肉眼对不上「训练越往后越过拟合」的趋势,拐点读不出来。 + Fix: extracted a pure function `buildXYMatrix` that, at submit, keeps only the anchors referenced by the X / Y axis `loraIndex`, remaps the indices in order of appearance, and discards orphans; `lora_configs` is empty for non-lora_ckpt axes. +- **Generate-page enqueue race (`config not found`) + LoRA bleed across single / xy modes (#159)** + - **Enqueue race**: `enqueue_generate` first writes the task as `pending+generate` (which makes it dispatchable), then builds the config (including the time-consuming `detect_attention_backend`) and writes `config_path`. The supervisor ticks every 1s; if it dispatches in the window where "`task_type` is already generate but `config_path` is still NULL," the daemon reads an empty path and goes straight to failed. Changed so `_dispatch_generate` skips tasks whose `config_path` isn't in the db yet (dispatched on the next tick), and marks a task failed when config build fails to avoid it staying pending forever. + - **LoRA bleed across single / xy modes**: `loras` was persisted as a single shared array, so adding A in single, switching to xy and adding B, made xy generate with A+B, and switching back to single showed A+B too. Split into `singleLoras` / `xyLoras` dual buckets routed by mode (compare shares the xy bucket), with old config auto-migrated into one copy in each bucket so selections aren't lost. +- **XY lora_ckpt axis ordered by the ckpt display order, no longer jumping around by click order (#164)** + On the Generate page's XY grid, the `lora_ckpt` axis (sweeping the same LoRA's different epoch / step checkpoints to find the overfitting inflection point), when multiple ckpts are selected, ordered the axis values by the order you clicked the chips, not by epoch / step. The consequence: picking ep80 / ep60 / ep40 in a jumbled click order (80 → 40 → 60) laid out the grid columns as `[80, 40, 60]`, dropping ep60 at the end so the eye couldn't follow the "more overfitting the later you train" trend and the inflection point couldn't be read. - 根因:`InlineLoraPicker` 多选用 `Set` 存,commit 时 `Array.from(set)` 取的是 Set 插入顺序 = 点击顺序。改为按后端 `list_lora_ckpts` 已经算好的 canonical sort(`final → step↓ → epoch↓ → other 自然序`,picker 本来就按这个顺序渲染 chip)。 -- **`sample_seed=0` 训练开始时 resolve,跨 epoch 同 prompt 同噪声(#193)** - `sample_seed=0` 的语义是「随机种子」(schema description 明示),但旧实现 `if s_seed:` 直接跳过 set seed,全程靠 global torch RNG。global RNG 在每个训练 step 都被 dropout / timestep / noise / dataloader 消耗,不同 epoch 落到采样点时 state 完全不同 — 同 prompt 不同 epoch 出图不一样。跨 epoch 采样图本来是看「收敛过程 / 过拟合趋势」的,前提是噪声固定只看模型变化,旧行为把噪声也一起变了对比就废了。 + Root cause: `InlineLoraPicker` stores the multi-select in a `Set`, and at commit `Array.from(set)` takes the Set insertion order = click order. Changed to the canonical sort already computed by the backend's `list_lora_ckpts` (`final → step↓ → epoch↓ → other natural order`, which the picker already renders the chips in). +- **`sample_seed=0` resolved at training start, same prompt and noise across epochs (#193)** + The semantics of `sample_seed=0` is "random seed" (stated in the schema description), but the old implementation's `if s_seed:` skipped the set-seed entirely and relied on the global torch RNG throughout. The global RNG is consumed by dropout / timestep / noise / dataloader at every training step, so its state is completely different by the time each epoch reaches the sampling point — the same prompt produces a different image at different epochs. Sample images across epochs are meant to show "the convergence process / overfitting trend," which assumes the noise is fixed so only the model changes; the old behavior changed the noise too, ruining the comparison. - 参考 kohya-ss/sd-scripts 标准做法:训练 bootstrap 阶段若 `sample_seed` 是 0 / 缺省,抽一次随机种子写回 `args.sample_seed` 并 log 出来,整轮训练同 prompt 同 seed。pause / resume 经 snapshot freeze,跨暂停仍用同一 seed;用户重起 task 时若 yaml 仍为 0 会重抽新随机。 -- **Windows 启动 npm 报 WinError 193(npm 解析优先 `.cmd`)(#140)** - Windows 启动 `python -m studio` 报 `OSError: [WinError 193] %1 不是有效的 Win32 应用程序`,栈打到 `subprocess.Popen([npm, ...])`。根因:`find_npm` 候选顺序让 `shutil.which("npm")` 优先匹配到 `C:\Program Files\nodejs\npm` — 这是 Node.js 官方装包给 Git Bash / MSYS 用的 bash 脚本(无扩展名),`CreateProcess` 没法直接拉起。 + Following kohya-ss/sd-scripts' standard approach: during the training bootstrap, if `sample_seed` is 0 / unset, draw a random seed once, write it back to `args.sample_seed`, and log it, so the whole run uses the same prompt and same seed. pause / resume freezes through the snapshot, so it uses the same seed across pauses; if you restart a task while the yaml is still 0, it draws a new random one. +- **Windows launch reports npm WinError 193 (npm resolution prefers `.cmd`) (#140)** + Launching `python -m studio` on Windows reported `OSError: [WinError 193] %1 is not a valid Win32 application`, with the stack hitting `subprocess.Popen([npm, ...])`. Root cause: `find_npm`'s candidate order let `shutil.which("npm")` match `C:\Program Files\nodejs\npm` first — that's the bash script (no extension) the official Node.js installer ships for Git Bash / MSYS, which `CreateProcess` can't launch directly. - 修法:Windows 候选改为 `.cmd` 优先 → `.ps1` → 裸名兜底;命中 `.ps1` 时自动包 `powershell.exe -NoProfile -ExecutionPolicy Bypass -File`。Linux / Mac 按 `os.name` 收窄到 `("npm",)`,行为完全不变。 -- **`studio.sh` 启动错误时也 pause 不闪退(双击启动用户能看到错误)(#174)** - `studio.sh` 在没装 Node.js 时只打一行 `Exit code 2` 就 exit,从文件管理器双击或非持久终端启动的用户看不到错误就闪退。`studio.bat` 早就有 `pause >nul` 兜底,本次给 `studio.sh` 补对应处理 — 非零 rc + TTY 时提示按 Enter 后退出;正常退出 / Ctrl+C / 非 TTY(CI / 管道)跳过避免误卡。 -- **SOCKS5 代理依赖缺失致主模型下载报 `socksio not installed`(#190)** - 用户配 `socks5://...` 代理后下载主模型报 `Using SOCKS proxy, but the 'socksio' package is not installed`。根因:`requirements.txt` 没声明 SOCKS5 所需 extras。两条下载链路用不同 HTTP 客户端,各自需要不同 SOCKS backend — HuggingFace(httpx)需 `socksio`、booru / ModelScope(requests)需 `PySocks`。`requirements.txt` 改为 `requests[socks]>=2.31.0` + 显式 `httpx[socks]>=0.27`,两个 extras 各自服务于对应链路,无 C 扩展跨平台 OK。 + Fix: the Windows candidates are reordered to prefer `.cmd` → `.ps1` → bare name as fallback; on a `.ps1` hit it auto-wraps with `powershell.exe -NoProfile -ExecutionPolicy Bypass -File`. Linux / Mac are narrowed to `("npm",)` by `os.name`, so their behavior is completely unchanged. +- **`studio.sh` now pauses on a launch error instead of flashing closed (so double-click users can see the error) (#174)** + When Node.js isn't installed, `studio.sh` only printed a single `Exit code 2` line and exited, so users launching from the file manager or a non-persistent terminal saw the error flash by and disappear. `studio.bat` has long had a `pause >nul` backstop; this release gives `studio.sh` the matching handling — a nonzero rc + a TTY prompts to press Enter before exiting; a normal exit / Ctrl+C / non-TTY (CI / pipe) is skipped to avoid a false hang. +- **Missing SOCKS5 proxy dependency caused base-model download to report `socksio not installed` (#190)** + After configuring a `socks5://...` proxy, downloading the base model reported `Using SOCKS proxy, but the 'socksio' package is not installed`. Root cause: `requirements.txt` didn't declare the extras SOCKS5 needs. The two download paths use different HTTP clients, each needing a different SOCKS backend — HuggingFace (httpx) needs `socksio`, booru / ModelScope (requests) needs `PySocks`. `requirements.txt` is changed to `requests[socks]>=2.31.0` + an explicit `httpx[socks]>=0.27`, with each extra serving its own path, and no C extensions so it's cross-platform fine. --- ## v0.10.3 — 2026-05-29 -新增全局 HTTP/HTTPS 代理配置(覆盖 booru 下载链路) +Added a global HTTP/HTTPS proxy setting (covering the booru download path) -### 新增 +### Added -- **设置页支持全局 HTTP/HTTPS/SOCKS5 代理,覆盖 booru 下载请求(#162)** - Settings → 工具页新增「全局代理」区块。开启后,填写的 HTTP/HTTPS/SOCKS5 地址会注入 booru 图源(Gelbooru / Danbooru)的下载请求,让无法直连这两站的网络环境也能完成抓图。额外字段 `no_proxy` 接收逗号分隔的例外地址(如 `localhost,127.0.0.1`),匹配的请求绕过代理。当前覆盖范围限于 booru 下载链路;模型仓库(HuggingFace / ModelScope)走代理仍需通过 `HF_ENDPOINT` 镜像或 OS 层 `HTTP_PROXY` / `HTTPS_PROXY` 环境变量。 +- **Settings supports a global HTTP/HTTPS/SOCKS5 proxy, covering booru download requests (#162)** + Settings → Tools adds a "Global proxy" section. Once enabled, the HTTP/HTTPS/SOCKS5 address you enter is injected into download requests for the booru image sources (Gelbooru / Danbooru), so networks that can't reach these two sites directly can still scrape images. An extra `no_proxy` field takes comma-separated exception addresses (e.g. `localhost,127.0.0.1`), and matching requests bypass the proxy. Coverage is currently limited to the booru download path; routing model repositories (HuggingFace / ModelScope) through a proxy still requires the `HF_ENDPOINT` mirror or OS-level `HTTP_PROXY` / `HTTPS_PROXY` environment variables. --- ## v0.10.2 — 2026-05-26 -Hotfix — 测试页从项目跳转的 LoRA 缓存污染 + 跨机器 bundle 模型路径 +Hotfix — LoRA cache pollution when jumping to the Generate page from a project + cross-machine bundle model paths -### 修复 +### Fixed -- **从项目页「在测试中加载」跳转到测试页时残留旧 LoRA、submit 抛 axisLoraMissing(#137)** - 项目详情完成态 banner 的「在测试中加载」按钮跳到测试页时,会把跳转的 LoRA 追加到本地缓存的 LoRA 列表末尾,而不是替换。结果:单图模式同时显示新 LoRA + 旧/已删 LoRA;左侧 LoRA picker 下拉和 chip 列表仍卡在缓存的项目;如果缓存里的 XY 轴选了「LoRA 路径」并绑了一个不存在的 LoRA 索引,点「开始生成」会抛错「LoRA 绑定的 LoRA #N 不存在」。修复后跳转视为明确的「测这条」意图,覆盖缓存列表为 URL 指定的那条,picker 重新挂载用新项目和版本初始化下拉,越界的 XY 轴 LoRA 索引收敛到第 1 条。 -- **跨机器 bundle 导入:源机器绝对模型路径被错拼成本机仓库下的子路径(#137)** - 本地(Windows)导出 bundle 含训练配置时,4 个全局模型路径(transformer / vae / text_encoder / t5_tokenizer)会原样写进 bundle;云端(Linux)导入时这些 Windows 盘符路径被识别为相对路径,拼到本机 repo 根目录下变成 `/G:/models/...`,训练启动时找不到 ckpt。修复后对齐预设 fork 行为:设置里「自动同步模型路径」开(默认)时用本机全局值覆盖 4 字段;关时尊重 bundle 内容,不再静默改写跨平台路径。 +- **Jumping to the Generate page via "Load in Generate" from a project left stale LoRAs behind, and submit threw axisLoraMissing (#137)** + The "Load in Generate" button in the completed-state banner on a project's detail view appended the jumped-in LoRA to the end of the locally cached LoRA list instead of replacing it. The result: single-image mode showed the new LoRA alongside old/deleted ones; the left-hand LoRA picker dropdown and chip list stayed stuck on the cached project; and if the cached XY axis was set to "LoRA path" and bound to a LoRA index that no longer existed, clicking "Generate" threw "the LoRA #N bound to the axis doesn't exist." Now a jump is treated as a clear "test this one" intent: it overwrites the cached list with the LoRA named in the URL, the picker remounts and initializes its dropdown with the new project and version, and an out-of-range XY-axis LoRA index collapses to the first entry. +- **Cross-machine bundle import: the source machine's absolute model paths got mangled into a subpath under the local repo (#137)** + When a bundle exported locally (Windows) included the training config, the 4 global model paths (transformer / vae / text_encoder / t5_tokenizer) were written into the bundle verbatim. On import to the cloud (Linux), those Windows drive-letter paths were read as relative paths and joined onto the local repo root, becoming `/G:/models/...`, so training couldn't find the checkpoints at launch. The fix aligns with preset-fork behavior: when "auto-sync model paths" is on in Settings (the default), the 4 fields are overwritten with the local global values; when off, the bundle's contents are respected, and cross-platform paths are no longer silently rewritten. --- ## v0.10.1 — 2026-05-26 -Hotfix — 修 v0.10.0 引入的三处 silent regression +Hotfix — fixes three silent regressions introduced in v0.10.0 -### 修复 +### Fixed -- **JSON caption 模式静默退回 TXT,分类 shuffle / 触发词处理失效(#135)** - v0.10.0 引入的路径错算让训练在 `prefer_json=True` 时永远找不到 JSON caption 工具文件,静默回退到 TXT 模式。受影响:用 tag worker 给图片打 `.json` 标签的训练流程;JSON 路径下的分类 shuffle(按 character / appearance / action 分组洗牌)、`meta.trigger` 触发词首位且不参与 shuffle 等特性全部失效。loss 曲线照常下降,无报错,但模型实际学到的 caption 分布跟设置不一致。 -- **测试页生成按钮在训练 / reg-ai 等 GPU 任务运行期间未禁用(#135)** - 测试页(Studio → 测试)与训练共享 GPU,并发会触发 VRAM 竞争,渲染卡顿,严重时训练进程 OOM。原先 sidebar 只有"与训练共享 GPU"提示,按钮仍可点。现在检测到队列有非生成任务在跑时,「开始生成」按钮置灰 + 鼠标悬停显示"队列任务 #N 正在占用 GPU,等它完成或取消",任务结束后自动恢复可点。 -- **任务详情「关联配置」tab 训练运行期间每 2 秒自动重拉(#135)** - 任务详情页训练运行期间,「关联配置」tab 持续闪烁并每 2 秒重发请求拉取 snapshot config,浏览器卡顿。关联配置是任务启动时冻结的不可变数据,本不应反复拉取;修复后只在切换任务 / 任务从 pending 进入 running 那一刻各拉一次。 +- **JSON caption mode silently fell back to TXT, disabling category shuffle and trigger-word handling (#135)** + A path miscalculation introduced in v0.10.0 meant that with `prefer_json=True`, training could never find the JSON caption tool files and silently fell back to TXT mode. Affected: training flows that use the tag worker to write `.json` tags for images; under the JSON path, category shuffle (shuffling within character / appearance / action groups), keeping the `meta.trigger` trigger word first and out of the shuffle, and similar features all stopped working. The loss curve kept descending with no errors, but the caption distribution the model actually learned didn't match your settings. +- **The Generate page's generate button wasn't disabled while a GPU task (training, reg-ai, etc.) was running (#135)** + The Generate page (Studio → Generate) shares the GPU with training, and running both at once causes VRAM contention, stuttering renders, and in bad cases an OOM in the training process. The sidebar previously only carried a "shares the GPU with training" note while the button stayed clickable. Now, when a non-generation task is detected running in the queue, the "Start generating" button is grayed out and hovering shows "queue task #N is using the GPU — wait for it to finish or cancel it"; the button becomes clickable again automatically once the task ends. +- **The task detail "Linked config" tab refetched automatically every 2 seconds during training (#135)** + On the task detail page, while training was running the "Linked config" tab flickered continuously and re-requested the snapshot config every 2 seconds, stuttering the browser. The linked config is immutable data frozen when the task starts and shouldn't be refetched repeatedly; the fix fetches it just once each time you switch tasks or a task moves from pending to running. --- ## v0.10.0 — 2026-05-25 -项目 / 版本 / 任务生命周期重构(ADR 0007)+ 预处理多工具化 + 训练前去重审核 - -### 新增 - -- **任务详情新增「关联配置」tab + 一键套用此配置(#117, #119)** - Task 启动时把当时的训练 config 冻结到 `studio_data/tasks/{tid}/snapshot/config.yaml`,与 version 当前 config 心智分离。 - - - 任务详情页新 tab「关联配置」显示该任务跑时的完整 yaml(只读)。 - - 提供「套用此配置」按钮,把 snapshot 写回新建 / 现有 version,方便复刻历史训练参数。 - - 用户后续改 version / preset / yaml 不影响历史 task 的 snapshot,跨设备 / 跨平台都能复读出当时跑的参数。 -- **训练前去重审核工具:按组人工审核保留 / 移除(#111, #126)** - 预处理工具栏新增「去重审核」(路由 `/preprocess?tool=dedupe`)。扫描当前工作集,相似 / 差分图按组展示,逐张审核后只写入 manifest 的 `duplicate_removed` 跳过状态,不移动 / 不删除 `download/` 原图。 - - - 参数区独立顶部 OperationPanel:常用项(匹配范围 / 差分分数 / 线程)外露;高级算法参数(hash 边长 / 结构阈值 / 宽高比容差 / 分块网格)默认折叠。 - - 工作区按组展示具体缩略图 + 建议保留 badge + 每图 match type / score / 尺寸 / 大小。 - - 二态自由选择(绿 = 保留 / 黄 = 去除),每张图独立切换,不限制组内最少留几张。 - - 「选中建议项」按钮一键应用系统建议。 - - 确认弹窗要求用户已审核具体分组图片。 - - 新增依赖 `ImageHash >= 4.3.0`。 -- **一图多框裁剪派生独立图 + 聚类裁剪自动对齐训练分辨率(#112)** - - **一图多框 fan-out**:裁剪页一张图可拉多个矩形框,确认后拆出独立派生 `X_c0.png` / `X_c1.png`;筛选 / `copy_to_train` / 缩略图全链路按派生名寻址,原图不再单独占位。 - - **聚类裁剪对齐训练桶**:新增 `lib/trainBuckets.ts`,1:1 mirror `runtime/training/dataset.py:BucketManager`(37 桶严格 sync 断言);聚类内部 snap 到训练桶 AR,裁出的图刚好命中训练桶,trainer 不二次 resize。UI label 仍用易读 AR(4:3 / 16:9)。 -- **训练集 bundle 导出 / 导入:本地下载或服务器路径选择(#108)** - 训练集打包导出新流程: - - - 导出可下载到本地或导出到项目目录。 - - 导入对话框支持浏览器上传与服务器路径选择。 - - 旧 `data_exports` 单独导入入口移除(服务器路径已覆盖该场景)。 - - 训练集页旧「下载训练集」按钮移除(功能并入 bundle 导出)。 -- **分层 rank `lora_rank_rules`:按层名正则指定不同 rank(#108)** - 训练 config 新增可选字典字段 `lora_rank_rules`,键为层名正则、值为 rank,例: +Project / version / task lifecycle overhaul (ADR 0007) + a multi-tool preprocessing flow + pre-training dedup review + +### Added + +- **Task detail gains an "Associated config" tab + one-click reuse of that config (#117, #119)** + When a task starts, its training config is frozen to `studio_data/tasks/{tid}/snapshot/config.yaml`, kept mentally separate from the version's current config. + + - The new "Associated config" tab on the task detail page shows the full yaml the task ran with (read-only). + - An "Apply this config" button writes the snapshot back to a new or existing version, making it easy to reproduce past training parameters. + - Later edits to the version / preset / yaml don't affect a historical task's snapshot, so you can read back the exact parameters it ran with across devices and platforms. +- **Pre-training dedup review tool: review groups by hand to keep or remove (#111, #126)** + The preprocessing toolbar adds "Dedup review" (route `/preprocess?tool=dedupe`). It scans the current working set, shows similar / near-duplicate images grouped together, and after you review each one it only writes a `duplicate_removed` skip status to the manifest — it never moves or deletes the originals in `download/`. + + - A dedicated OperationPanel at the top: common options (match scope / difference score / threads) are surfaced; advanced algorithm parameters (hash side length / structural threshold / aspect-ratio tolerance / tiling grid) are collapsed by default. + - The workspace shows each group's thumbnails with a suggested-keep badge and per-image match type / score / dimensions / file size. + - A free two-state choice (green = keep / yellow = remove), toggled independently per image, with no minimum on how many you must keep in a group. + - A "Select suggested" button applies the system's suggestions in one click. + - The confirmation dialog requires that you've reviewed the actual group images. + - Adds the dependency `ImageHash >= 4.3.0`. +- **One image, multiple crop boxes fan out into separate derived images + cluster cropping auto-aligns to training resolution (#112)** + - **One image, multiple boxes fan-out**: on the crop page a single image can have multiple rectangles drawn; on confirm they split into separate derivatives `X_c0.png` / `X_c1.png`. Filtering / `copy_to_train` / thumbnails all address the derivative names end to end, and the original no longer takes its own slot. + - **Cluster cropping aligned to training buckets**: a new `lib/trainBuckets.ts` 1:1 mirrors `runtime/training/dataset.py:BucketManager` (37 buckets with a strict sync assertion); clustering snaps internally to a training bucket's aspect ratio so crops land exactly on a training bucket and the trainer doesn't resize a second time. UI labels still use readable aspect ratios (4:3 / 16:9). +- **Dataset bundle export / import: download locally or pick a server path (#108)** + A new flow for packaging and exporting a dataset: + + - Export can download locally or export to the project directory. + - The import dialog supports browser upload and server path selection. + - The old standalone `data_exports` import entry is removed (server paths cover that case). + - The dataset page's old "Download dataset" button is removed (folded into bundle export). +- **Per-layer rank via `lora_rank_rules`: assign different ranks by layer-name regex (#108)** + The training config adds an optional dictionary field `lora_rank_rules`, keyed by a layer-name regex with a rank value, e.g.: ```yaml lora_rank_rules: @@ -976,677 +975,677 @@ Hotfix — 修 v0.10.0 引入的三处 silent regression "lora_unet_.*single.*": 8 ``` - 未命中任何规则的层走全局 `lora_rank`。便于按模块重要性差异化分配参数预算。 -- **标签编辑新增多文件夹切换 tab(#108, #113)** - 标签编辑页顶部按 folder 切分 tab,多 folder 项目可快速切到任一 folder 视图,每个 tab 独立计数。 -- **预处理总览三 tab:所有图片 / 经过处理 / 已删除(#126)** - 预处理总览改成三 tab 视图: - - - **所有图片**:当前工作集(不含 removed),只读浏览 + 点击放大。 - - **经过处理**:preprocess 派生产物,沿用原有选中恢复 + 全部撤销。 - - **已删除**:被去重标记为软删的图,可见缩略图(按 download/source 取),选中可一键恢复。 -- **「经过处理」tab 点击放大改左右双图对比(原图 / 处理后)(#126)** - 预览 modal 新增 `compareSrc` 模式,传入时左右 split 双图对比(左 = download 原图、右 = preprocess 派生),便于看 multi-crop 派生与原图的对应关系。窄屏(< md)自动改垂直堆叠。不传 `compareSrc` 时行为完全不变。 -- **测试页新增 daemon 实时日志抽屉(#128)** - Generate 测试页加「日志」按钮(挂在「清理显存」右边),从底部向上 40vh 滑入抽屉显示推理 daemon 实时日志。 - - - terminal 默认不再打印 daemon 输出,统一走 UI 抽屉。 - - 后端 `InferenceDaemon` 加 ring buffer(deque maxlen=2000)+ log listener,supervisor 转 SSE `daemon_log_line` 增量推送。 - - 新接口 `GET /api/generate/daemon/logs?since_seq=N` 拉历史日志。 - - 抽屉隐藏时 `translateY(100%)` + `pointerEvents:none`,完全不占 layout。 - -### 变更 - -- **项目 / 版本 / 任务状态模型升级 + 侧栏改 cursor 推进(#115, #117, #119)** - 把项目—版本—任务的状态模型从单一 `stage` 字段升级为 `status` 与 `phase` 双字段。`status` 5 态:preparing / training / completed / failed / canceled;`phase` 5 态 cursor:curating / tagging / editing / regularizing / ready。 - - - 侧栏成为 cursor 推进主通道:项目展开后 phase 列表显示进度,cursor+1 整行可点直接 advance / skip,独立 PhaseHeaderNav 删除。 - - 完成的 phase 数字变绿;当前 phase 用红色编号高亮;cursor 之后的 phase disabled。 - - DB 升级 v9 destructive 删除 `projects.stage` / `versions.stage` 列(recreate-table 模式兼容 SQLite < 3.35)。 - - 设计 / 决策 / 迁移策略全文见 [ADR 0007](docs/adr/0007-project-version-task-lifecycle.md)。 -- **项目详情页 v2 重做(StatusBanner / VersionRail / 数据集缩略图卡)(#121)** - 按 design v2 重做项目详情页: - - - Identity strip + VersionRail(横向版本切换)+ 5 态 StatusBanner(preparing / training / completed / failed / canceled 各自 CTA)+ 2+3 不对称 detail grid。 - - 训练集卡片用 ImageGrid + ImagePreviewModal + folder chips;与「训练集筛选」面板同款。 - - 标签分布卡内嵌进度条,触发词用 ★ 标记并 accent 高亮。 - - 像素分布卡复用 BarHistogram,与放大页 sidebar 同源数据。 - - 完成态项目的 LoRA 文件 tab 复用队列详情页 OutputsTab,含下载 / 打包 / 打开文件夹。 - - 所有 CTA 接现有 API:fork version → 走 NewVersionDialog 预填;加载 LoRA → 跳测试页 prefill;查看日志 → 跳队列详情 #log。 -- **预处理改多工具结构:总览 / 放大 / 裁剪 / 涂抹(#112)** - 预处理从单一放大页演进成多工具结构,路由 `/projects/:pid/preprocess?tool=overview|upscale|crop|inpaint`。 - - - 工具栏:总览 / 去重审核 / 放大 / 裁剪 / 涂抹,共享同一外框。 - - 总览页:所有 preprocess workspace 图 grid + ctrl/shift 多选 + 单图预览 modal + 撤销选中 / 撤销全部。 - - 放大页:filter chips 改像素 bin(< 512² / 512²–768² / 768²–1024² / 1024²–1536² / 1536²–2048² / > 2048²),与 sidebar 像素分布同源;JobStrip 无 live job 时整块隐藏。 - - 裁剪页:filmstrip 从底部横排改左侧竖排 3-col 正方 cover thumbs;FreeCropEditor 用 ResizeObserver 自适应。 -- **裁剪后再放大 / 放大后再裁剪打通,链路顺序自由(#114, #124)** - 按 [ADR 0004 Addendum 1「Stage 不强制时序」](docs/adr/0004-preprocess-manifest.md) 对齐:preprocess worker 不再因 manifest 已有 entry 跳过,是否跑模型由 `upscaler.SKIP_MODEL_RATIO` 按源像素判断。 - - - 用户明确选中 cropped 派生再点放大 → 放行(之前被 mode='all' skip)。 - - 用户再次放大已放大产物 → 放行。 - - 「放大全部 N」按钮 N 现在是当前 grid 全部图数(包含 cropped 派生),不再只算 download 未派生。 -- **标签编辑批量操作面板重做行式 + 表头排序 + 行内确认(#113)** - 按设计稿 V2 重排批量操作面板:四个操作(添加 / 删除 / 替换 / 去重)各占一行,按钮对齐到同一条竖线。 - - - 添加 / 删除 各有独立 input,不再共享一个输入框抢归属。 - - 「首部 / 尾部」toggle 只挂在「添加」行内,归属明确。 - - 只有「添加」是 filled primary;删除 / 替换 / 去重 均为 outline;删除走 err 着色(红边 + 红字,不再 filled)。 - - 标签分布面板 sort dropdown 换成可点击表头,同列点击切换方向,跨列默认 desc,活跃列旁带 ↑ / ↓ 箭头。 - - 行内 `×` / `✎` 删 / 改单 tag 与批量操作对齐,统一过 confirm modal 显示精确影响张数。 - - 进入单图编辑时自动收起 BulkActionBar 让侧栏让位给 TagEditor。 -- **推理热换同结构 LoRA + 取消生成保留 daemon + 生成参数持久化(#114)** - 测试页推理 daemon 三处行为改进: - - - **热换同结构 LoRA**:切换同 base model / 同结构的 LoRA 不再重启 daemon,秒切。 - - **取消生成保留 daemon**:单次生成任务取消后 daemon 仍存活可复用,不再被误杀。 - - **生成参数持久化**:刷新页面后 prompt / 采样参数 / 选中 LoRA 等不再重置。 -- **输出文件页「下载」「导出」合并为单按钮 + 弹窗二选(#128)** - 输出文件页删独立「导出」按钮,「下载」点击后弹 `OutputsDownloadDialog`:radio 二选(本地 zip / 导出到 data_exports 目录)。风格对齐侧栏 `ExportBundleDialog`,但不带训练集 / 正则集 / caption / config 等无关选项。 -- **训练 save 字段加单位后缀:`save_every_epochs` / `save_state_every_steps`(#128)** - 重命名两个易混字段: - - - `save_every` → `save_every_epochs`(保存 LoRA checkpoint 的 epoch 间隔)。 - - `save_state_every` → `save_state_every_steps`(保存训练 state 的 step 间隔)。 - - `migrate_legacy_save_keys`(pydantic before-validator + `bootstrap.apply_yaml_config` 双层)自动迁移旧 yaml / 旧 preset,无需用户手改文件。同时修正 `save_state_every_epochs` 描述里「与 X 取先到者」错误说明(实际两个 state save 独立判断,文件名各带 `step{N}` / `epoch{N}` 不冲突)。 -- **`grad_clip_max_norm` 默认 0 → 1.0,防 LoRA mode collapse(#125)** - 训练默认梯度裁剪从「禁用」改为 `1.0`。 - - 近期一次 Qwen-image LoRA 训练在 step ~1220 mode collapse(loss 从 0.1 飙到 0.9 ~ 1.0、prediction latent std 从 0.99 收缩到 0.68)。5 个算法 expert 跨方向 audit 后确认 `grad_clip = 0` 在 Prodigy + ScheduleFree / DoRA + LoKr / bf16 + flow matching 多个 regime 下放大风险,主流 trainer(SimpleTuner / OneTrainer / ai-toolkit)默认均为 1.0。schema description 同步重写说明参数行为和场景化推荐值。 - -### 改进 - -- **训练集图像网格虚拟滚动 + 主导色占位淡入(#113)** - ImageGrid 改虚拟滚动,大数据集(数百~数千张)上下滑动不再卡。缩略图未到位前用图像主导色块占位,到位后淡入。 -- **推理 daemon 启动预热避免文本编码器加载卡数分钟(#128)** - 用户报告推理 daemon 启动时卡 6+ 分钟在 `loading text encoders`。py-spy 抓到调用链 `transformers.generation.candidate_generator → sklearn → scipy.special.__init__` —— scipy cold import 在 Windows + Python 3.13 + 已加载 GB 级模型(system RAM 紧张)时要几分钟。 - - 改成 daemon 启动时提前 import `transformers.generation`,等加载 text encoder 时秒过。 -- **标签编辑未保存改动切页时弹确认拦截(#128)** - 之前只有 `beforeunload` 拦关闭浏览器 / 刷新,应用内 React-Router 导航静默丢失改动。改造: - - - App.tsx 从经典 `BrowserRouter` 迁到 `createBrowserRouter + RouterProvider`(DataRouter),解锁 v6 `useBlocker`。 - - TagEdit 用 `useBlocker(dirty)` + useDialog 红色 confirm(放弃 / 留下)拦截。 - - Sidebar + Topbar 拆出 RootLayout 包业务路由进 ``,结构等价。 - -### 修复 - -- **修复 4 处 UI 滚动溢出(队列 / 监控 / 队列详情 / 项目卡片)(#110)** - 4 处父层 `overflow-hidden` 锁视窗高度但子层缺 `flex-1 min-h-0`,子内容超出后被父层裁掉、`overflow-y-auto` 不触发: - - - `/queue` 任务多到撑爆视窗后无法下拉。 - - `/tools/monitor` running 任务时面板内容超视窗被裁。 - - `/queue/:id#monitor` 同上(共用 MonitorDashboard)。 - - `/queue/:id#overview` 长 error_msg / 路径无法滚动。 - - 以及项目主页卡片 grid 缺 `auto-rows-fr`,带 note 的卡片把同行其它卡片撑成不齐。 -- **队列挂起 / 释放 / 重排接口偶发 422 错误(#114)** - FastAPI 路由顺序问题:`/api/queue/hold` / `/api/queue/release` / `/api/queue/reorder` 等静态路径被 `/api/queue/{task_id}` 参数路由抢匹配,返回 422 unprocessable entity。重排路由顺序修复。 -- **训练日志中内部协议行不再混进显示 + 自动备份日志独立分类(#114)** - - `__EVENT__:` 开头的进程间协议行不再被当作训练日志显示。 - - 自动 epoch 备份与手动 step / epoch 保存的日志提示分开,不再共用同一行(之前看不清是哪一种 save 触发)。 -- **正则化预生成隐藏队列 + 日志移到 config tab + 自动选 attention backend(#114)** - - 正则集预生成任务从队列页隐藏(之前混在训练任务列表里)。 - - 预生成日志移到 version config tab 内(之前散落在主队列日志里)。 - - attention backend 不再要求用户手动选择,会根据安装的库自动检测。 -- **队列页只显示当前任务训练状态 + 嵌套训练输出正确渲染(#114)** - 队列页主行不再混入历史任务的训练 metric;任务下方的嵌套输出(每 epoch 采样图 / 中间状态)按所属任务正确分组渲染,不再串行错位。 -- **测试页数据集 prompt 选择器关闭时未清空已选 tag(#128)** - Generate 测试页数据集 prompt picker 之前 `×` 关闭只关 UI 不动 `datasetPick` 状态,已选 caption.tags 仍被 `handleGenerate` 拼到 prompt,用户以为没选还在生效。改成 `onClose` 同时 `setDatasetPick(null)`。 + Layers that don't match any rule fall back to the global `lora_rank`. This makes it easy to allocate the parameter budget differently by module importance. +- **Tag editing gains tabs to switch between folders (#108, #113)** + The tag editing page splits into per-folder tabs at the top, so a multi-folder project can jump to any folder's view quickly, with each tab counting independently. +- **Preprocessing overview gets three tabs: All images / Processed / Removed (#126)** + The preprocessing overview becomes a three-tab view: + + - **All images**: the current working set (excluding removed), read-only browsing with click-to-zoom. + - **Processed**: preprocess derivatives, with the existing select-to-restore and undo-all. + - **Removed**: images soft-deleted by dedup, with visible thumbnails (sourced from download/source) that you can restore with one click after selecting. +- **Click-to-zoom on the "Processed" tab becomes a side-by-side before/after comparison (#126)** + The preview modal adds a `compareSrc` mode: when provided, it splits left/right into a two-image comparison (left = the download original, right = the preprocess derivative), making it easy to see how a multi-crop derivative maps to its original. On narrow screens (< md) it stacks vertically. Without `compareSrc` the behavior is unchanged. +- **The Generate page gains a live daemon log drawer (#128)** + The Generate page adds a "Logs" button (next to "Clear VRAM"); it slides a drawer up from the bottom to 40vh showing the inference daemon's live logs. + + - The terminal no longer prints daemon output by default — it all goes through the UI drawer. + - The backend `InferenceDaemon` adds a ring buffer (deque maxlen=2000) + a log listener, and the supervisor relays incremental `daemon_log_line` SSE pushes. + - A new endpoint `GET /api/generate/daemon/logs?since_seq=N` pulls historical logs. + - When hidden the drawer uses `translateY(100%)` + `pointerEvents:none`, so it takes no layout space at all. + +### Changed + +- **Project / version / task state model upgrade + sidebar drives a cursor forward (#115, #117, #119)** + The project–version–task state model goes from a single `stage` field to two fields, `status` and `phase`. `status` has 5 states: preparing / training / completed / failed / canceled; `phase` is a 5-state cursor: curating / tagging / editing / regularizing / ready. + + - The sidebar becomes the main channel for advancing the cursor: expanding a project shows the phase list with progress, and the cursor+1 row is clickable to advance / skip directly. The standalone PhaseHeaderNav is removed. + - Completed phase numbers turn green; the current phase is highlighted with a red number; phases past the cursor are disabled. + - DB upgrade v9 destructively drops the `projects.stage` / `versions.stage` columns (recreate-table mode for SQLite < 3.35 compatibility). + - Full design / decisions / migration strategy in [ADR 0007](docs/adr/0007-project-version-task-lifecycle.md). +- **Project detail page v2 redesign (StatusBanner / VersionRail / dataset thumbnail cards) (#121)** + The project detail page is rebuilt to design v2: + + - An identity strip + VersionRail (horizontal version switching) + a 5-state StatusBanner (preparing / training / completed / failed / canceled, each with its own CTA) + an asymmetric 2+3 detail grid. + - The dataset card uses ImageGrid + ImagePreviewModal + folder chips, matching the "Dataset filter" panel. + - The tag distribution card embeds a progress bar, with trigger words marked by ★ and accent-highlighted. + - The pixel distribution card reuses BarHistogram, sharing the same data as the zoom page's sidebar. + - For completed projects, the LoRA files tab reuses the queue detail page's OutputsTab, with download / package / open folder. + - All CTAs hook into existing APIs: fork version → opens NewVersionDialog prefilled; load LoRA → jumps to the Generate page prefilled; view logs → jumps to queue detail #log. +- **Preprocessing restructured into multiple tools: overview / upscale / crop / inpaint (#112)** + Preprocessing evolves from a single zoom page into a multi-tool structure at route `/projects/:pid/preprocess?tool=overview|upscale|crop|inpaint`. + + - Toolbar: overview / dedup review / upscale / crop / inpaint, sharing one outer frame. + - Overview page: a grid of all preprocess workspace images + ctrl/shift multi-select + single-image preview modal + undo selected / undo all. + - Upscale page: filter chips become pixel bins (< 512² / 512²–768² / 768²–1024² / 1024²–1536² / 1536²–2048² / > 2048²), sharing the sidebar's pixel distribution; the JobStrip hides entirely when there's no live job. + - Crop page: the filmstrip moves from a bottom horizontal row to a left vertical 3-col square cover-thumb strip; FreeCropEditor adapts with a ResizeObserver. +- **Crop-then-upscale and upscale-then-crop now work in any order (#114, #124)** + Aligned with [ADR 0004 Addendum 1 "Stages aren't strictly ordered"](docs/adr/0004-preprocess-manifest.md): the preprocess worker no longer skips just because the manifest already has an entry; whether to run the model is decided by `upscaler.SKIP_MODEL_RATIO` based on source pixels. + + - Explicitly selecting a cropped derivative and clicking upscale → goes through (previously skipped by mode='all'). + - Upscaling an already-upscaled output again → goes through. + - The "Upscale all N" button's N is now the full count of images in the current grid (including cropped derivatives), not just un-derived downloads. +- **Tag editing's batch action panel reworked into rows + sortable headers + inline confirmation (#113)** + The batch action panel is rearranged per design V2: the four operations (add / remove / replace / dedup) each take a row, with buttons aligned to one vertical line. + + - Add / remove each have their own input, no longer sharing one input box that fights over ownership. + - The "prefix / suffix" toggle lives only inside the "add" row, so its scope is clear. + - Only "add" is a filled primary; remove / replace / dedup are all outline; remove uses error coloring (red border + red text, no longer filled). + - The tag distribution panel's sort dropdown becomes clickable column headers — clicking the same column toggles direction, switching columns defaults to desc, and the active column shows a ↑ / ↓ arrow. + - The inline `×` / `✎` to delete / edit a single tag is aligned with the batch operations, all going through the confirm modal that shows the exact number of images affected. + - Entering single-image editing auto-collapses the BulkActionBar so the sidebar makes room for the TagEditor. +- **Inference hot-swaps same-structure LoRAs + canceling a generation keeps the daemon + generation parameters persist (#114)** + Three behavior improvements for the Generate page's inference daemon: + + - **Hot-swap same-structure LoRAs**: switching to a LoRA with the same base model / same structure no longer restarts the daemon — it switches instantly. + - **Canceling a generation keeps the daemon**: after a single generation is canceled the daemon stays alive and reusable, no longer killed by mistake. + - **Generation parameters persist**: after a page refresh the prompt / sampling parameters / selected LoRA and more no longer reset. +- **The outputs page merges "Download" and "Export" into a single button + a dialog with two choices (#128)** + The outputs page drops the standalone "Export" button; clicking "Download" opens `OutputsDownloadDialog` with a radio choice (local zip / export to the data_exports directory). It matches the sidebar's `ExportBundleDialog` in style, but without the unrelated dataset / regularization set / caption / config options. +- **Training save fields get unit suffixes: `save_every_epochs` / `save_state_every_steps` (#128)** + Two easily-confused fields are renamed: + + - `save_every` → `save_every_epochs` (the epoch interval for saving a LoRA checkpoint). + - `save_state_every` → `save_state_every_steps` (the step interval for saving training state). + + `migrate_legacy_save_keys` (a pydantic before-validator + `bootstrap.apply_yaml_config`, two layers) migrates old yaml / old presets automatically, with no manual file edits. It also corrects the `save_state_every_epochs` description's wrong note about "whichever of X comes first" (the two state saves are actually judged independently, and their filenames carry `step{N}` / `epoch{N}` so they don't collide). +- **`grad_clip_max_norm` default 0 → 1.0, to prevent LoRA mode collapse (#125)** + The training default for gradient clipping changes from "disabled" to `1.0`. + + A recent Qwen-image LoRA training mode-collapsed around step ~1220 (loss spiked from 0.1 to 0.9 ~ 1.0, prediction latent std shrank from 0.99 to 0.68). After a cross-direction audit by 5 algorithm experts, `grad_clip = 0` was confirmed to amplify risk across several regimes — Prodigy + ScheduleFree / DoRA + LoKr / bf16 + flow matching — and mainstream trainers (SimpleTuner / OneTrainer / ai-toolkit) all default to 1.0. The schema description was rewritten to explain the parameter's behavior and scenario-based recommended values. + +### Improved + +- **Dataset image grid gets virtual scrolling + a dominant-color placeholder that fades in (#113)** + ImageGrid switches to virtual scrolling, so large datasets (hundreds to thousands of images) no longer stutter when you scroll. Before a thumbnail loads, a block of the image's dominant color holds its place and fades in once it arrives. +- **Inference daemon prewarms on startup to avoid a multi-minute stall loading text encoders (#128)** + Users reported the inference daemon stalling for 6+ minutes at `loading text encoders`. py-spy traced the call chain `transformers.generation.candidate_generator → sklearn → scipy.special.__init__` — a scipy cold import takes minutes on Windows + Python 3.13 with GB-scale models already loaded (system RAM under pressure). + + The fix imports `transformers.generation` early when the daemon starts, so loading the text encoder is instant. +- **Tag editing intercepts navigation with a confirmation when there are unsaved changes (#128)** + Previously only `beforeunload` caught closing the browser / refreshing, and in-app React-Router navigation silently lost changes. The rework: + + - App.tsx migrates from classic `BrowserRouter` to `createBrowserRouter + RouterProvider` (DataRouter), unlocking v6 `useBlocker`. + - TagEdit uses `useBlocker(dirty)` + useDialog with a red confirm (discard / stay) to intercept. + - The Sidebar + Topbar are pulled into a RootLayout wrapping the business routes in ``, structurally equivalent. + +### Fixed + +- **Fixed 4 UI scroll overflows (queue / monitor / queue detail / project card) (#110)** + In 4 places the parent's `overflow-hidden` locked the viewport height but the child lacked `flex-1 min-h-0`, so overflowing child content was clipped by the parent and `overflow-y-auto` never triggered: + + - `/queue` couldn't scroll down once tasks overflowed the viewport. + - `/tools/monitor` clipped panel content beyond the viewport while a task was running. + - `/queue/:id#monitor` had the same issue (it shares MonitorDashboard). + - `/queue/:id#overview` couldn't scroll a long error_msg / path. + + Plus the project home card grid lacked `auto-rows-fr`, so a card with a note stretched the other cards in its row out of alignment. +- **Queue hold / release / reorder endpoints occasionally returned a 422 error (#114)** + A FastAPI route ordering problem: static paths like `/api/queue/hold` / `/api/queue/release` / `/api/queue/reorder` were captured by the `/api/queue/{task_id}` parameter route, returning 422 unprocessable entity. Fixed by reordering the routes. +- **Internal protocol lines no longer leak into the training log display + auto-backup logs are categorized separately (#114)** + - Lines starting with `__EVENT__:` (the inter-process protocol) are no longer shown as training logs. + - The log messages for automatic epoch backups and manual step / epoch saves are separated, no longer sharing one line (it used to be unclear which kind of save triggered). +- **Regularization pre-generation hidden from the queue + logs moved to the config tab + attention backend auto-selected (#114)** + - The regularization-set pre-generation task is hidden from the Queue page (it used to mix in with the training task list). + - Pre-generation logs moved into the version config tab (they used to scatter into the main queue logs). + - The attention backend no longer requires manual selection — it's auto-detected based on the installed libraries. +- **Queue page shows only the current task's training status + nested training output renders correctly (#114)** + The Queue page's main row no longer mixes in training metrics from historical tasks; the nested output under a task (per-epoch sample images / intermediate state) renders grouped correctly to its owning task, no longer interleaved out of place. +- **Generate page dataset prompt picker didn't clear selected tags when closed (#128)** + The Generate page's dataset prompt picker previously only closed the UI on `×` without touching the `datasetPick` state, so the selected caption.tags were still stitched into the prompt by `handleGenerate` — you thought nothing was selected, but it was still in effect. Fixed so `onClose` also calls `setDatasetPick(null)`. --- ## v0.9.1 — 2026-05-20 -依赖修正 + 发布流水线 version 一致性校验 +Dependency fixes + version-consistency checks in the release pipeline -### 改进 +### Improved -- **bump_version.py 同步 package-lock.json,新增 verify-versions 跨文件一致性校验与 CI gate** - bump 子命令扩展为同步四处 version 字段(studio/__init__.py、studio/web/package.json、studio/web/package-lock.json 顶层与 `packages[""]`)。新增 verify-versions 子命令做一致性校验,bump 完成后自检;同时新增 .github/workflows/version-check.yml,在 pull_request 与 master / dev push 时执行 validate 与 verify-versions,阻断 version drift 流入历史。 +- **bump_version.py now syncs package-lock.json, and a new verify-versions cross-file consistency check gates CI** + The bump subcommand is extended to sync the version field in four places (studio/__init__.py, studio/web/package.json, and both the top level and `packages[""]` of studio/web/package-lock.json). A new verify-versions subcommand performs the consistency check and self-verifies after a bump; a new .github/workflows/version-check.yml runs validate and verify-versions on pull requests and on master / dev pushes, blocking version drift from entering history. -### 修复 +### Fixed -- **modelscope 提为必装依赖,ModelScope 下载源不再需要运行时手动安装** - services/model_downloader.py 的 ModelScope 路径原先在缺包时返回 False 并提示手动 `pip install modelscope`,requirements.txt 不列。本次将 modelscope 提为必装依赖,安装或自更新完成后 ModelScope 下载源开箱可用。 +- **modelscope is now a required dependency, so the ModelScope download source no longer needs a manual install at runtime** + The ModelScope path in services/model_downloader.py previously returned False when the package was missing and told you to run `pip install modelscope` by hand, since it wasn't listed in requirements.txt. This release promotes modelscope to a required dependency, so the ModelScope download source works out of the box after an install or self-update. --- ## v0.9.0 — 2026-05-20 -训练新增暂停 / 恢复 + huber loss / mixed_uniform timestep + 触发词自动写 caption + 全前端中英双语 - -### 新增 - -- **UI 暂停 / 恢复训练 + 队列挂起调度(ADR 0006)(#100, #101, #105)** - Queue / QueueDetail 页加 4 个新交互(详见 [ADR 0006](docs/adr/0006-queue-pause-resume.md) 全文 + Addendum 1 + design doc 三轮 review): - - - **任务暂停**:running task 点暂停 → 先弹 PauseConfirmModal 提醒语义(按 Addendum 1:暂停 = cancel + 立即释放 GPU,恢复点从最近一次 epoch 末 auto-backup 起),确认后子进程收 `CTRL_BREAK_EVENT` / `SIGINT`,handle_interrupt 保 LoRA "interrupted" + 写 pause snapshot freeze 当前 args → 子进程 exit 0 → task 标 paused。UI 全程锁 PauseProgressModal 引导(保存中 / 30s 超时三选一 / 失败兜底)。 - - **任务恢复**:paused 行点恢复 → status 回 pending → supervisor 下次 dispatch 用 `--resume-state ` 拉起,bootstrap_phase 读 sibling config snapshot 覆盖 args(snapshot 是 ADR §5.7 frozen 真相,跟用户后续改 version / preset / yaml 完全解耦) → load_training_state 成功 emit `resume_state_loaded` → supervisor 清 pause 文件对。 - - **队列挂起 / 恢复调度**:banner sticky 顶部;挂起 confirmation modal 检测 running task 多问一句 "让它跑完" / "同时暂停",主按钮文案随 radio 联动。 - - **取消 paused task**:paused 行有 "彻底取消",删 pause 文件 + 清 db 字段。 - - 信号链路(#96 spike 端到端验证):supervisor `CTRL_BREAK_EVENT` / POSIX `SIGINT` → 子进程 SIGBREAK / SIGINT handler → handle_interrupt 保 state + emit `__EVENT__:pause_state` → supervisor `_finish_slot` 三元分流(paused / canceled / done / failed),pause_pending 但 state_path 空 → 兜底 canceled。db migration v6 加 4 个 NULLABLE 列 + `queue_settings` kv 表持久化 hold 开关(老库零 backfill)。 -- **打标页加触发词输入,启动后自动写进每张 caption 和采样图 prompt(#103)** - 终结「忘记加触发词导致白训」footgun。Step 4 (Tagging) 顶部加一个紧凑输入框,启动打标时把 trigger 落库到 `versions.trigger_word`(migration v7),tag worker 把 trigger 作为第一个 tag 注入每张 caption: - - - `.txt` / 简单 list `.json`:trigger 作为 `tags[0]`(case-insensitive 去重) - - LLM `documented_full` `.json`:顶层 `meta.trigger` 注入(不污染 `ai_output` / `fixed` / `character` 等业务字段) - - caption_text 模式:`f"{trigger}, {text}"` prepend - - 训练 runtime `bootstrap.py` 自动把 trigger 注入 sample_prompt,采样图天然带触发词(token 级 case-insensitive 匹配,"art" 不会误判 "artist") - - TagEdit 页 actions 区显示 trigger badge(read-only)缩短"忘记什么是 trigger"的 cognitive distance - - 其他主流 trainer 对比:Civitai 在线 trainer 项目创建必填 → 强制;AI-Toolkit `[trigger]` runtime 替换 → 不可移植;本 PR 走 "作者写时规范化"路线,caption 文件即真相,切 trainer 也带得走。 -- **Studio 全前端中英双语 + 首次启动弹语言选择(#76, #89)** - - **#76**:Studio 前端 Regularization / Settings / Generate / Testing / Presets / Project Overview / LLM Tagger 全页本地化;schema 暴露字段改为 frontend-owned i18n key(后端 enum / 字段名稳定,UI 渲染本地化 label / description / disable hint);Settings 模型 / upscaler 目录描述 + LLM Tagger builtin preset / message editor copy 一并本地化;恢复 + 扩展 schema-driven takeover(PPSF 禁 scheduler / InfoNoise 禁 timestep / Prodigy / PPSF 锁 LR=1)。 - - **#89**:首启语言选择从 `studio.bat/sh` CLI prompt(cmd.exe 默认 codepage 显示不了汉字)改成前端 modal。`localStorage['studio.lang']` 为 null 时自动弹出,"English / 中文" 两张卡 + 各自语言副标题,键盘焦点初始落到 `navigator.language` 命中那张但视觉不引导。fresh install 不再有阻塞 prompt,CI / 无人值守部署直通。 -- **训练新增 huber loss 选项,对极端样本更鲁棒(#75, #86, #87)** - - Schema 新增 `loss_type: Literal["mse", "huber"]` + `huber_c` 专属字段;默认 `mse`,旧 yaml / preset 行为 bit-for-bit 一致(MseLoss.compute 跟 `F.mse_loss(reduction='none')` codify 严格相等)。 - - 按 ADR 0003 plugin registry 模式落子包:BUILDERS + build_loss + validate_schema_consistency 三件套 + `LossProtocol` (runtime_checkable Protocol)。 - - **HuberLoss** 简化为单 constant δ(#86 删 PR #75 引入的 snr/sigma schedule —— 数学退化为 MSE,且无 paper / 主流 trainer 出处)。 - - InfoNoise 的 `_raw_mse` 跟训练 loss 解耦,始终单独算一次 `F.mse_loss`(保证 huber 启用时 InfoNoise paper I-MMSE 假设不被破坏),有 `test_infonoise_raw_mse_decoupled_from_loss_type` 守这条边界。 -- **新增 mixed_uniform timestep 采样模式 + 后置 schedule shift(#73, #85)** - - `timestep_sampling` Literal 加 `mixed_uniform_low` / `mixed_uniform_logit` 两个 mode,在 uniform 全覆盖基础上按 `timestep_mix_low_prob` 比例混入 `logit_normal_low` / `logit_normal` 的偏置端分布。 - - 新字段 `timestep_schedule_shift: float = 1.0`:跟 `timestep_shift`(logit-normal 内部)不同,是对采样**完成后**的 t 做一次额外 `t' = (t·s) / (1 + (s-1)·t)` 偏移。1536 训练用 1.12 比单独调 `timestep_shift` 更稳。 - - 默认值 `timestep_mix_low_prob=0.0` / `timestep_schedule_shift=1.0` 下输出 bit-for-bit 等价历史 sample_t(s==1.0 short-circuit)。InfoNoise CDF 正式阶段不读 baseline shift(codify `test_infonoise_cdf_path_ignores_baseline_timestep_schedule_shift`)。 - - 字段命名 PR #85 把 `schedule_shift` → `timestep_schedule_shift` 对齐 `timestep_*` 一族(合并 24h 内 zero-cost 重命名)。 -- **loss_weighting=detail_inv_t 的权重上下限现在可在 UI 调(#72, #87)** - `loss_weighting=detail_inv_t` 原 clamp 范围 hardcode `[1, 5]`,现在改 schema 两个参数 `detail_inv_t_min=1.0` / `detail_inv_t_max=5.0`(show_when + advanced),默认值等价历史 hardcode(旧 yaml 行为零变化)。 - - - 雾蒙蒙 / 低饱和画风:max 降到 3,缓解"低 t 反复学"的偏置 - - 高对比 / 硬朗画风:维持默认 [1, 5] - - 激进细节:max 升到 8(Prodigy d 估计注意单样本主导风险) - - Schema validator 守 `min > max` 启动期 fail-fast;下限 ≥ 1.0(描述补"<1.0 因 1/t>1 恒成立故无效"防配置死区)。 -- **Settings 加「统一模型路径」toggle,关后预设可携带绝对路径(#93)** - 默认 ON(沿用旧行为,多数用户自动同步全局模型路径);OFF 后: - - | | toggle ON(默认) | toggle OFF | |---|---|---| | 预设页 / 项目页 4 字段 UI | disabled + 跳转 Settings hint | 可编辑 + picker + ↺ reset | | fork preset → version | 用全局值覆盖 | 拷预设值不覆盖 | | 保存为预设 | 4 字段清成 Settings 全局值 | 原样保留绝对路径 | | yaml 落盘 | 一律绝对 POSIX 路径 | 同上 | - - 老相对路径 yaml read/write 兜底转绝对(基于 REPO_ROOT,反斜杠 → POSIX `/`)。独立 PUT 端点不进全局 dirty 流程。 - -### 变更 - -- **暂停后从最近 epoch 末接续训练,不会丢半个 epoch 进度(ADR 0006 Addendum 1)(#105)** - 合 PR-1~PR-4 后三方算法专家深度 audit dev 训练栈,翻盘初版 ADR 的 mid-epoch save 路径——grad_accum 周期未守 / dataloader 5% double-train / `current_epoch` 二义性 / cosine restart `T_cur` 漂移都是 mid-epoch 无法 freeze 的根因。改为: - - - **Pause = Cancel + 立即释放 GPU**:handle_interrupt 不再尝试 freeze mid-epoch 训练状态,直接 LoRA "interrupted" 落盘 + 子进程 exit。 - - **Epoch 末 auto-backup**:每个 epoch 末覆盖式写一份 `state/task_/auto_epoch_state.pt`,下次 resume 就从这个最近完整 epoch 边界起,零中段误差。 - - **UI is_pausable 升级**:首 epoch 内 / 还没有 auto-backup 时暂停按钮完全隐藏(避免点了 "暂停" 实际没有可恢复 state 的 footgun)。 - - 详细 audit 流程 / 三方专家评审 / 拒绝方案 A/B/C 的理由见 [docs/adr/0006-queue-pause-resume.md](docs/adr/0006-queue-pause-resume.md) Addendum 1。 -- **同一 version 下连跑多个训练任务不再互相覆盖进度文件(#97)** - `save_state_every` / `save_state_every_epochs` / `handle_interrupt` 全部改写到 `output_dir/state/task_/` 下,不再写 output_dir 顶层。同一 version 下连跑多个 task 时再也不会互相覆盖 state 文件(latent bug 直接修)。 - - - supervisor 启动 training 时通过 env `LORA_TASK_ID` 注入 task id - - CLI 直接跑(无 env)fallback 到 `state/task_unknown/` 子目录 - - `list_state_ckpts`(ResumeFieldPicker 后端)同时扫旧顶层 + 新子目录,**老用户的现存 .pt 不丢** - - 顺手修了 `list_state_ckpts` 之前只扫 step 不扫 epoch 的小 bug -- **导出训练集 / 队列改用浏览器原生下载,大文件不再卡内存(#104)** - outputs.zip 那套 `` 直链 + 后端 `*_ready/_failed` SSE 标杆推广到剩下的下载入口(train.zip / queue 导出),浏览器原生下载条 + app-side 小 spinner 收尾。SSH 隧道 / 大文件场景下不再吃 JS heap、看得到进度、切 tab 不中断。 - - - 删 `downloadBlob` (fetch→blob,静默吃内存) - - 新事件:`version_train_zip_ready/_failed` / `queue_export_ready/_failed` - - 预设 yaml 导入语义改对齐:"上传即入池 + 自动选中",同名冲突弹三选一 dialog(覆盖 / 另存为 `{name}-2` / 取消),不再走"切新建模式预填表单等用户改名"的二步式 -- **zip 安装包用户「启用自动更新」后不再被本地修改卡住(#84)** - v0.8.1 zip 用户实测:解压后跑过 `studio.bat run`,npm install 改 `studio/web/package-lock.json` 几行依赖元数据 → `git reset --mixed` 路径 init 完就 dirty → 版本面板 pre-flight 卡更新。改 `--hard` 强制覆盖 working tree 对齐 anchor tag。zip 用户场景下"启用自动更新"潜台词就是"对齐上游稳定版",强制覆盖比保留随机改动符合预期。Settings banner 文案显式改为「本地源码会同步到上游 v{X.Y.Z},zip 目录里的本地修改会被覆盖」。 - -### 修复 - -- **Prodigy+ScheduleFree / InfoNoise 训练恢复后不再崩溃 / 丢进度(#105)** - - `prodigy_plus_schedulefree`(PPSF)依赖 `optimizer.train()` 切训练模式,`load_training_state` 后没调,resume 立即抛 `Not in train mode!`。修:`load_training_state` 末尾 `if hasattr(optimizer, 'train'): optimizer.train()`。 - - InfoNoise 9 个内部 state(EMA / K / B / FIFO / cdf / hist / step counters)之前不进 state_dict,resume 后冷启动重走 N_warm(默认 5000 步)。修:加 `state_dict` / `load_state_dict` 序列化 + K/B mismatch 退冷启动 warning 不抛。 - - 14 个新单测(baseline no-op / InfoNoise roundtrip / sample bit-exact / K/B mismatch / FIFO maxlen / ckpt 损坏 warning)codify。 -- **路径选择器 3 处修:外部路径浏览 / 文件起点 / Windows 路径分隔符(#93)** - - 端点放开外部绝对路径浏览(之前 `list_dir` 默认拒外部,PathPicker 设计本就是给选外部模型路径用的) - - 文件路径作起点时自动回退到父目录并高亮该文件(之前直接 404) - - 后端统一 `as_posix()` 输出,前端 `childPath` 拼接不再混用 `\` / `/` - - 模型根目录输入框默认值预填实际绝对路径;之前 placeholder 文案错写 `REPO_ROOT/anima/`(实际默认 `REPO_ROOT/models/`) -- **LLM 打标兼容更多服务商响应格式 + 加并发节流(#88)** - - 解析 SSE 兼容响应(部分 OpenAI 兼容服务商即使 stream=false 也回 `text/event-stream`,需要按行 + `[DONE]` 终止符解析) - - 每分钟并发请求节流(serial mode 也生效) - - 非流式响应路径单独 retry -- **UI 一组小修:保存按钮配色 / 浏览器自动填充误触 / XY picker 选了立即生效(#94)** - - Settings 自定义放大器下载按钮漏 `common.download` zh/en i18n - - 设置页保存按钮 dirty 着色改 `btn-secondary` 灰 / `btn-primary` 橙 切换(之前一直橙色靠 disabled 区分弱) - - `SensitiveInput`(api_key / user_id 等)+ 配对 `username` 加 `autoComplete=new-password` + `data-lpignore` / `data-1p-ignore` / `data-form-type=other`,阻止 Chrome / 1Password / LastPass 自动填充触发 dirty - - 项目 step header 删 Download / Preprocess 的重复"设置"入口 - - Generate XY 轴 LoRA picker:`InlineLoraPicker` 加 `live` 模式,chip toggle / 权重变 / pid-vid 切换都即时 `onPick`,axis card 里常驻 picker 不再渲染"添加 N 个"commit footer +Training adds pause / resume + huber loss + a mixed_uniform timestep mode + automatic trigger-word injection into captions + a localized front end + +### Added + +- **Pause / resume training from the UI + queue hold scheduling (ADR 0006) (#100, #101, #105)** + Four new interactions on the Queue / QueueDetail pages (see the full [ADR 0006](docs/adr/0006-queue-pause-resume.md) plus Addendum 1 and the design doc, after three rounds of review): + + - **Pause a task**: pausing a running task first opens the PauseConfirmModal to explain the semantics (per Addendum 1, pause = cancel + immediate GPU release, and the resume point is the most recent end-of-epoch auto-backup). After you confirm, the child process receives `CTRL_BREAK_EVENT` / `SIGINT`; handle_interrupt saves the LoRA as "interrupted" and writes a pause snapshot that freezes the current args → the child exits 0 → the task is marked paused. Throughout, the UI guides you with the PauseProgressModal (saving / a three-way choice on a 30s timeout / failure fallback). + - **Resume a task**: resuming a paused row sets its status back to pending → on the next dispatch the supervisor launches it with `--resume-state `, and bootstrap_phase reads the sibling config snapshot to override the args (the snapshot is the frozen source of truth from ADR §5.7, fully decoupled from any later edits you make to the version / preset / yaml) → a successful load_training_state emits `resume_state_loaded` → the supervisor clears the pause file pair. + - **Hold / resume queue scheduling**: a sticky banner at the top; when a running task is present, the hold confirmation modal adds a "let it finish" / "pause it too" choice, and the primary button label updates with the radio selection. + - **Cancel a paused task**: paused rows offer "cancel permanently," which deletes the pause files and clears the db fields. + + Signal path (verified end to end by the #96 spike): supervisor `CTRL_BREAK_EVENT` / POSIX `SIGINT` → child SIGBREAK / SIGINT handler → handle_interrupt saves state and emits `__EVENT__:pause_state` → the supervisor's `_finish_slot` branches across paused / canceled / done / failed, falling back to canceled when pause_pending is set but state_path is empty. db migration v6 adds four NULLABLE columns plus a `queue_settings` kv table to persist the hold switch (no backfill needed for old databases). +- **The Tagging page gains a trigger-word input that is written into every caption and sample-image prompt once tagging starts (#103)** + Ends the "forgot the trigger word and wasted a run" footgun. A compact input is added at the top of Step 4 (Tagging); when tagging starts, the trigger is saved to `versions.trigger_word` (migration v7), and the tag worker injects the trigger as the first tag in every caption: + + - `.txt` / simple list `.json`: trigger becomes `tags[0]` (deduplicated case-insensitively) + - LLM `documented_full` `.json`: injected as a top-level `meta.trigger` (without polluting the `ai_output` / `fixed` / `character` business fields) + - caption_text mode: prepends `f"{trigger}, {text}"` + - The training runtime's `bootstrap.py` injects the trigger into sample_prompt automatically, so sample images naturally include the trigger word (token-level case-insensitive matching, so "art" won't be mistaken for "artist") + - The TagEdit page shows a read-only trigger badge in its actions area to shorten the cognitive distance of "what was the trigger again?" + + Compared with other mainstream trainers: Civitai's online trainer makes it mandatory at project creation → forced; AI-Toolkit does a `[trigger]` runtime substitution → not portable; this PR takes the "normalize at authoring time" route, where the caption files are the source of truth and travel with you if you switch trainers. +- **The entire Studio front end is now localized, with a language picker on first launch (#76, #89)** + - **#76**: the Regularization / Settings / Generate / Testing / Presets / Project Overview / LLM Tagger pages are fully localized; schema-exposed fields now use frontend-owned i18n keys (backend enums / field names stay stable while the UI renders localized labels, descriptions, and disable hints); the Settings model / upscaler directory descriptions and the LLM Tagger builtin presets / message-editor copy are localized too; schema-driven takeover is restored and extended (PPSF disables the scheduler / InfoNoise disables timestep / Prodigy and PPSF lock LR=1). + - **#89**: the first-launch language choice moves from a `studio.bat/sh` CLI prompt (cmd.exe's default codepage can't display non-ASCII text) to a front-end modal. It pops up automatically when `localStorage['studio.lang']` is null, showing one card per language, each with a subtitle in its own language; keyboard focus initially lands on the card matching `navigator.language` without steering you visually. Fresh installs no longer have a blocking prompt, so CI and unattended deployments pass straight through. +- **Training adds a huber loss option that is more robust to extreme samples (#75, #86, #87)** + - The schema adds `loss_type: Literal["mse", "huber"]` plus a dedicated `huber_c` field; the default is `mse`, and old yaml / presets behave bit-for-bit identically (a test codifies that MseLoss.compute is strictly equal to `F.mse_loss(reduction='none')`). + - Implemented as a sub-package following the ADR 0003 plugin registry pattern: the BUILDERS + build_loss + validate_schema_consistency trio plus a `LossProtocol` (a runtime_checkable Protocol). + - **HuberLoss** is simplified to a single constant δ (#86 removes the snr/sigma schedule introduced in PR #75 — mathematically it degenerated into MSE and had no paper or mainstream-trainer basis). + - InfoNoise's `_raw_mse` is decoupled from the training loss and always computes a separate `F.mse_loss` (so that enabling huber doesn't break the I-MMSE assumption from the InfoNoise paper), with `test_infonoise_raw_mse_decoupled_from_loss_type` guarding this boundary. +- **New mixed_uniform timestep sampling mode + a post-sampling schedule shift (#73, #85)** + - The `timestep_sampling` Literal gains two modes, `mixed_uniform_low` / `mixed_uniform_logit`, which start from full uniform coverage and mix in the biased-end distribution of `logit_normal_low` / `logit_normal` at the `timestep_mix_low_prob` ratio. + - New field `timestep_schedule_shift: float = 1.0`: unlike `timestep_shift` (internal to logit-normal), this applies one extra shift to t **after** sampling, `t' = (t·s) / (1 + (s-1)·t)`. For 1536 training, 1.12 is steadier than tuning `timestep_shift` alone. + - At the defaults `timestep_mix_low_prob=0.0` / `timestep_schedule_shift=1.0`, output is bit-for-bit equivalent to the historical sample_t (s==1.0 short-circuits). The InfoNoise CDF phase doesn't read the baseline shift (codified by `test_infonoise_cdf_path_ignores_baseline_timestep_schedule_shift`). + - PR #85 renamed the field `schedule_shift` → `timestep_schedule_shift` to align with the `timestep_*` family (a zero-cost rename within 24h of merging). +- **The weight bounds for loss_weighting=detail_inv_t can now be tuned in the UI (#72, #87)** + `loss_weighting=detail_inv_t` previously hardcoded its clamp range to `[1, 5]`; it's now two schema parameters, `detail_inv_t_min=1.0` / `detail_inv_t_max=5.0` (show_when + advanced), whose defaults match the old hardcoded values (no change to existing yaml behavior). + + - Hazy / low-saturation styles: lower max to 3 to ease the "over-learning low t" bias + - High-contrast / crisp styles: keep the default [1, 5] + - Aggressive detail: raise max to 8 (with Prodigy, watch the d estimate for single-sample dominance) + + A schema validator fails fast at startup if `min > max`; the lower bound is ≥ 1.0 (the description notes that "<1.0 is ineffective because 1/t>1 always holds" to prevent a dead config zone). +- **Settings adds a "unified model paths" toggle; turning it off lets presets carry absolute paths (#93)** + On by default (the old behavior, where most users' global model paths sync automatically); when off: + + | | toggle ON (default) | toggle OFF | |---|---|---| | Preset / project page, 4-field UI | disabled + hint linking to Settings | editable + picker + ↺ reset | | fork preset → version | overridden with global values | copies preset values, no override | | save as preset | 4 fields reset to Settings global values | absolute paths kept as-is | | yaml on disk | always absolute POSIX paths | same as above | + + Legacy relative-path yaml is converted to absolute on read/write as a fallback (based on REPO_ROOT, backslashes → POSIX `/`). A dedicated PUT endpoint stays out of the global dirty flow. + +### Changed + +- **Resuming after a pause continues from the most recent end of epoch, so you don't lose half an epoch of progress (ADR 0006 Addendum 1) (#105)** + After PR-1 through PR-4 landed, a deep audit of the dev training stack by outside algorithm experts overturned the original ADR's mid-epoch save path — an unguarded grad_accum cycle, a 5% dataloader double-train, the ambiguity of `current_epoch`, and cosine-restart `T_cur` drift were all reasons mid-epoch state can't be frozen. The new approach: + + - **Pause = cancel + immediate GPU release**: handle_interrupt no longer tries to freeze mid-epoch training state; it saves the LoRA as "interrupted" and the child process exits. + - **End-of-epoch auto-backup**: at the end of each epoch, `state/task_/auto_epoch_state.pt` is overwritten, so the next resume starts from this most recent complete epoch boundary with no mid-epoch error. + - **is_pausable in the UI upgraded**: the pause button is hidden entirely during the first epoch / before any auto-backup exists (avoiding the footgun of pausing when there's no resumable state). + + For the full audit process, the outside expert review, and why options A/B/C were rejected, see [docs/adr/0006-queue-pause-resume.md](docs/adr/0006-queue-pause-resume.md) Addendum 1. +- **Running multiple training tasks back to back under the same version no longer overwrites each other's progress files (#97)** + `save_state_every` / `save_state_every_epochs` / `handle_interrupt` now all write under `output_dir/state/task_/` instead of the top level of output_dir. Running multiple tasks back to back under the same version no longer overwrites their state files (a latent bug, fixed directly). + + - the supervisor injects the task id via the `LORA_TASK_ID` env var when it starts training + - running the CLI directly (no env) falls back to a `state/task_unknown/` subdirectory + - `list_state_ckpts` (the backend for ResumeFieldPicker) scans both the old top level and the new subdirectory, so **existing .pt files from older users aren't lost** + - Also fixed a small bug where `list_state_ckpts` previously scanned steps but not epochs +- **Exporting the training set / queue now uses native browser downloads, so large files no longer choke on memory (#104)** + The `` direct-link + backend `*_ready/_failed` SSE pattern from outputs.zip is rolled out to the remaining download entry points (train.zip / queue export), with the browser's native download bar plus a small app-side spinner to finish. In SSH-tunnel / large-file scenarios it no longer eats JS heap, shows progress, and isn't interrupted by switching tabs. + + - Removed `downloadBlob` (fetch→blob, which silently ate memory) + - New events: `version_train_zip_ready/_failed` / `queue_export_ready/_failed` + - Preset yaml import semantics are realigned to "upload adds it to the pool and selects it"; a name conflict opens a three-way dialog (overwrite / save as `{name}-2` / cancel) instead of the old two-step flow that switched to create mode, prefilled the form, and waited for you to rename +- **For zip-install users, "enable auto-update" is no longer blocked by local modifications (#84)** + Observed with v0.8.1 zip users: after unzipping and running `studio.bat run`, npm install changed a few lines of dependency metadata in `studio/web/package-lock.json` → the `git reset --mixed` path left the tree dirty right after init → the pre-flight in the version panel blocked the update. This now uses `--hard` to force the working tree to match the anchor tag. For zip users, "enable auto-update" implicitly means "align with the upstream stable version," so force-overwriting matches expectations better than keeping random changes. The Settings banner copy is changed explicitly to "your local source will sync to upstream v{X.Y.Z}; local modifications in the zip directory will be overwritten." + +### Fixed + +- **Resuming Prodigy+ScheduleFree / InfoNoise training no longer crashes or loses progress (#105)** + - `prodigy_plus_schedulefree` (PPSF) relies on `optimizer.train()` to switch into training mode, which wasn't called after `load_training_state`, so resume immediately raised `Not in train mode!`. Fix: `if hasattr(optimizer, 'train'): optimizer.train()` at the end of `load_training_state`. + - Nine InfoNoise internal states (EMA / K / B / FIFO / cdf / hist / step counters) weren't in the state_dict, so resume cold-started and re-ran N_warm (5000 steps by default). Fix: added `state_dict` / `load_state_dict` serialization, and a K/B mismatch now warns and falls back to a cold start instead of raising. + - 14 new unit tests codify this (baseline no-op / InfoNoise roundtrip / sample bit-exact / K/B mismatch / FIFO maxlen / corrupt-ckpt warning). +- **Three path-picker fixes: browsing external paths / file start points / Windows path separators (#93)** + - the endpoint now allows browsing external absolute paths (previously `list_dir` rejected external paths by default, even though PathPicker is designed precisely for choosing external model paths) + - when a file path is used as the start point, it now falls back to the parent directory and highlights the file (previously a plain 404) + - the backend outputs `as_posix()` consistently, so the front-end `childPath` concatenation no longer mixes `\` / `/` + - the model-root input now prefills the actual absolute path as its default; previously the placeholder text wrongly said `REPO_ROOT/anima/` (the actual default is `REPO_ROOT/models/`) +- **LLM tagging now handles more provider response formats and adds concurrency throttling (#88)** + - parses SSE-style responses (some OpenAI-compatible providers return `text/event-stream` even when stream=false, requiring line-by-line parsing with a `[DONE]` terminator) + - per-minute concurrent-request throttling (also applies in serial mode) + - a separate retry for the non-streaming response path +- **A batch of small UI fixes: save-button colors / accidental browser autofill / XY picker applying immediately on selection (#94)** + - the custom-upscaler download button in Settings was missing the `common.download` i18n key + - the Settings save button's dirty coloring now toggles between gray `btn-secondary` and orange `btn-primary` (previously it was always orange, with only a weak disabled state to distinguish it) + - `SensitiveInput` (api_key / user_id, etc.) and the paired `username` now set `autoComplete=new-password` plus `data-lpignore` / `data-1p-ignore` / `data-form-type=other`, preventing Chrome / 1Password / LastPass autofill from triggering a dirty state + - removed the duplicate "settings" entry for Download / Preprocess from the project step header + - the Generate XY-axis LoRA picker: `InlineLoraPicker` gains a `live` mode, so chip toggles, weight changes, and pid-vid switches all `onPick` immediately, and the always-present picker in the axis card no longer renders an "add N" commit footer --- ## v0.8.3 — 2026-05-19 -hotfix:ARB 桶不再丢小桶图 + 每 epoch step 数按桶算 +Hotfix: ARB bucketing no longer drops images from small buckets + the per-epoch step count is computed per bucket -### 修复 +### Fixed -- **ARB 桶尾不足 batch_size 张图时整桶被丢的 bug 修复(短 batch 保留,对齐 kohya / ai-toolkit)** - `BucketBatchSampler` 默认 `drop_last=True`:每个 ARB 桶里 `len(bucket) % batch_size` 张图被丢。最严重的是 **桶 size < batch_size 时整桶被丢**,例如 batch_size=2 时所有单图奇形比例的桶都永远训不到。 +- **Fixed a bug where an ARB bucket's tail was dropped whenever it held fewer than batch_size images (short batches are now kept, matching kohya / ai-toolkit)** + `BucketBatchSampler` defaulted to `drop_last=True`, so `len(bucket) % batch_size` images were dropped from every ARB bucket. Worst of all, **a bucket smaller than batch_size was dropped entirely** — at batch_size=2, for example, every bucket holding a single odd-aspect-ratio image would never be trained on. - 排查 git history:`drop_last=True` 是 ARB 初版(commit `6774079` "feat: add arb",2026-02-08)默认写下的,没有 commit message / ADR 解释设计意图,应是沿用 PyTorch `DataLoader` 的卫生默认 —— 但 ARB 场景下 trade-off 已经翻转:普通 DataLoader 一个 epoch 丢 1 个零头,ARB 是 **每个桶各丢一个零头**,丢失量随桶数线性放大。 + Tracing the git history: `drop_last=True` was written into the very first ARB version (commit `6774079`, "feat: add arb", 2026-02-08) with no commit message or ADR explaining the intent — it was likely carried over from PyTorch `DataLoader`'s sanitary default. But in the ARB case the trade-off has flipped: a plain DataLoader drops one remainder per epoch, whereas ARB drops one remainder per bucket, so the loss scales linearly with the number of buckets. - - `phases/dataset.py`:`drop_last=False`,对齐 kohya `sd-scripts`(`math.ceil(len/bs)`)与 ostris `ai-toolkit` (`min(end_idx, len(bucket))`)的「短 batch 不丢」语义 - - diffusion DiT 用 LayerNorm/GroupNorm,对动态 batch 不敏感;`loop.py` 已按 `latents.shape[0]` 动态读 bs,短 batch 不会引入数值问题 -- **每 epoch 步数现在按 ARB 桶精确算(之前偏小导致 LR scheduler 末尾不对齐)** - `__len__` 之前用全局 `n // batch_size`(或 `ceil(n/bs)`),但 ARB 实际 batch 数 = `Σ_b f(n_b, bs)`,每桶各自有零头。全局公式低估 batch 数 → `steps_per_epoch` 偏 → scheduler `total_steps` 偏 → 末尾几步 LR 不对齐。 + - `phases/dataset.py`: `drop_last=False`, matching the "keep short batches" semantics of kohya `sd-scripts` (`math.ceil(len/bs)`) and ostris `ai-toolkit` (`min(end_idx, len(bucket))`) + - Diffusion DiTs use LayerNorm/GroupNorm and are insensitive to dynamic batch sizes; `loop.py` already reads the batch size dynamically from `latents.shape[0]`, so a short batch introduces no numerical issues +- **The per-epoch step count is now computed exactly per ARB bucket (it was previously too low, leaving the LR scheduler misaligned near the end)** + `__len__` previously used the global `n // batch_size` (or `ceil(n/bs)`), but the actual ARB batch count is `Σ_b f(n_b, bs)`, since each bucket has its own remainder. The global formula underestimated the batch count → `steps_per_epoch` was off → the scheduler's `total_steps` was off → the learning rate was misaligned over the final few steps. - 例:数据集 129 张 + batch_size=2,日志报「每 epoch 步数: 64」(=129//2),但实际跑出来按桶 floor 求和会更小(每桶各掉一个零头)。修后按桶遍历 `bucket_for_index` 分别 floor/ceil 求和。 + For example: a dataset of 129 images with batch_size=2 logged "steps per epoch: 64" (= 129 // 2), but the actual per-bucket floor sum comes out smaller (each bucket loses one remainder). The fix iterates over `bucket_for_index` and sums the per-bucket floor/ceil counts. - 加 `tests/test_bucket_batch_sampler.py` 覆盖:小桶不丢 / 历史 drop_last=True 行为 / `__len__` 按桶算 / `__len__` == 实际 yield 的 batch 数 / 无桶信息时退回全局公式。 + Added `tests/test_bucket_batch_sampler.py` covering: small buckets aren't dropped, the historical drop_last=True behavior, `__len__` computed per bucket, `__len__` equaling the number of batches actually yielded, and falling back to the global formula when no bucket info is available. --- ## v0.8.2 — 2026-05-17 -hotfix:预设系统脱钩 redesign + hf-mirror 暂不可用 + 新建预设预览补齐项目路径 +Hotfix: decouple the preset system from the redesign + hf-mirror temporarily unavailable + new-preset preview now includes project paths -### 变更 +### Changed -- **预设跟 version 改为完全脱钩,version yaml 直接作为「项目专属配置」** - 发现整个训练页预设系统的 mental model 是坏的: - - picker 顶部「预设 X · 已自定义」标签永远显示「已自定义」,即使用户没改任何字段 —— 因为 fork 时 default_paths_for_new_version 把 4 个全局模型路径注入成绝对路径,跟全局预设 yaml 里的相对路径 diff 永远存在。`stripProjectFields` 只剥了 6 个项目特定字段,漏了 4 个模型字段 - - fork 之后 version yaml 跟全局预设其实是脱钩的,但 UI 假装一直绑定,导致用户对「换预设」「保存」语义混乱 - - fork 后 `onForkPreset` / `saveNewPreset` 没调父级 `reload()` 刷新 `activeVersion`,picker 顶部标签停在旧预设名上,用户以为 fork 没生效(实际后端已写) +- **Presets are now fully decoupled from versions, with a version's yaml acting directly as its "project-specific config"** + We found the whole training page's preset mental model was broken: + - The "Preset X · Customized" label at the top of the picker always showed "Customized", even when you hadn't changed any field — because on fork, default_paths_for_new_version injects the 4 global model paths as absolute paths, which always differ from the relative paths in the global preset yaml. `stripProjectFields` only stripped 6 project-specific fields and missed the 4 model fields. + - After a fork, the version yaml is actually decoupled from the global preset, but the UI pretended it was still bound, which confused the meaning of "switch preset" and "save". + - After a fork, `onForkPreset` / `saveNewPreset` didn't call the parent's `reload()` to refresh `activeVersion`, so the label at the top of the picker stayed on the old preset name and you thought the fork hadn't taken effect (the backend had in fact written it). - 改成「version yaml = 项目专属配置」语义: + Changed to a "version yaml = project-specific config" model: - - **picker 顶部 button**:has_config=True 显示「项目专属配置」, has_config=False 显示「未配置 — 选预设作为起点」。不再展示来源预设名 + 「· 已自定义」标签 - - **picker 列表卡片**:去掉 active 高亮(没有「当前绑定」概念了),全部卡片视觉一致;副标题改为「重置为此预设」/「以此预设为起点」 - - **confirm 文案**:从「换预设会覆盖当前 version 的配置」改成「重置为预设 X 的配置?当前 version 的所有自定义内容会丢失」, okText 改「重置」 - - **删整套 baseline diff 逻辑**:`presetBaselineRef` / `refreshPresetBaseline` / `stripProjectFields` / `customized` 全部移除,连带相关 useEffect - - **fork / saveNewPreset 之后调 reload()**:刷父级 activeVersion,否则 picker 顶部跟主表单内容跟实际 yaml 脱节,需要 F5 才能看到 - - `versions.config_name` db 字段保留作为 audit trail(informational only),不再有 UI 用途 + - **Picker top button**: when has_config=True it shows "Project-specific config"; when has_config=False it shows "Not configured — pick a preset as a starting point". It no longer shows the source preset name + "· Customized" label. + - **Picker list cards**: the active highlight is gone (there's no "currently bound" concept anymore), so all cards look the same; the subtitle becomes "Reset to this preset" / "Start from this preset". + - **Confirm copy**: changed from "Switching presets will overwrite the current version's config" to "Reset to preset X's config? All of the current version's customizations will be lost", with okText changed to "Reset". + - **Removed the whole baseline-diff logic**: `presetBaselineRef` / `refreshPresetBaseline` / `stripProjectFields` / `customized` are all removed, along with their related useEffect. + - **Call reload() after fork / saveNewPreset**: to refresh the parent's activeVersion, otherwise the picker top and main form contents fall out of sync with the actual yaml and you'd need an F5 to see it. + - The `versions.config_name` db field is kept as an audit trail (informational only) and no longer has any UI use. -### 修复 +### Fixed -- **新建预设预览表单补齐项目路径(reg_data_dir / data_dir / 模型路径等)** - 训练页「+ 新建预设」预览表单之前一律展示 schema 默认值(`reg_data_dir=None`、`data_dir=./dataset`、相对模型路径),即使项目已跑过 reg build、Settings 已配好模型,用户在预览里仍看到一片相对路径 / 空白,误以为「fork 后不会自动带项目目录」。实际上 `fork_preset_for_version` 后端**早已**注入这些值到 version config(实测验证),只是预览看不到,UX 误导。 +- **New-preset preview form now includes project paths (reg_data_dir / data_dir / model paths, etc.)** + The training page's "+ New preset" preview form previously always showed schema defaults (`reg_data_dir=None`, `data_dir=./dataset`, relative model paths), even when the project had already run a reg build and Settings already had models configured. You'd still see a wall of relative paths / blanks in the preview and mistakenly think "forking won't bring the project directories along". In fact `fork_preset_for_version` on the backend **already** injected these values into the version config (verified in practice) — they just weren't visible in the preview, which was misleading UX. - - **后端**:`GET /api/projects/{pid}/versions/{vid}/config` 在 `has_config=False` 分支新增 `project_specific_defaults` —— 合并 `project_specific_overrides()` + `default_paths_for_new_version()`,含 reg/meta.json 检测结果 - - **前端 startCreatePreset**:用 `project_specific_defaults` 覆盖 schema 默认,预览表单初始化即显示「fork 后会得到的值」(含 reg 目录 / 训练目录 / 输出目录 / 绝对模型路径) - - **前端预览表单**:与主表单一致挂 `disabledFields`(全局模型路径灰显「自动 · 全局设置」)+ `autoHints`(项目特定字段标「自动 · 项目设置」)+ `advancedMode` - - **前端 saveNewPreset**:savePreset 前过滤项目特定字段 + 全局模型路径清回 schema 默认,全局预设池不带项目 / 机器数据(fork 时后端会再注入,version config 仍正确)。与 `services/presets.py:save_version_config_as_preset` 的清理逻辑对齐 -- **HF 模型下载默认源从 hf-mirror.com 切回 huggingface.co 官方** - 实测发现 `hf-mirror.com` 在所有已测 `huggingface_hub` 版本(0.25 / 0.30 / 0.34 / 1.14)下下载均失败 —— hub 客户端从 HEAD 响应里读不到 `commit_hash`,抛 `FileMetadataError: Distant resource does not seem to be on huggingface.co`。 curl 跟 redirect 拿 bytes 没问题,说明 mirror 服务本身活着,但 hub 期望的元数据 header 在 redirect 链里丢了 —— 怀疑是 mirror 服务端近期改动。这不是上游回归(0.25 requests 时代也挂),且锁旧版 hub 会破 transformers 5.x。 + - **Backend**: `GET /api/projects/{pid}/versions/{vid}/config` adds `project_specific_defaults` in the `has_config=False` branch — merging `project_specific_overrides()` + `default_paths_for_new_version()`, including reg/meta.json detection results. + - **Frontend startCreatePreset**: uses `project_specific_defaults` to override the schema defaults, so the preview form initializes showing "the values you'll get after a fork" (including reg dir / training dir / output dir / absolute model paths). + - **Frontend preview form**: matches the main form by attaching `disabledFields` (global model paths grayed out as "Automatic · global settings") + `autoHints` (project-specific fields marked "Automatic · project settings") + `advancedMode`. + - **Frontend saveNewPreset**: before savePreset it filters out project-specific fields + resets global model paths back to schema defaults, so the global preset pool carries no project / machine data (the backend re-injects on fork, so the version config is still correct). This matches the cleanup logic in `services/presets.py:save_version_config_as_preset`. +- **HF model download default source switched back from hf-mirror.com to the official huggingface.co** + We found in practice that `hf-mirror.com` fails to download under every tested `huggingface_hub` version (0.25 / 0.30 / 0.34 / 1.14) — the hub client can't read `commit_hash` from the HEAD response and raises `FileMetadataError: Distant resource does not seem to be on huggingface.co`. curl follows the redirect and gets the bytes fine, so the mirror service itself is alive, but the metadata header the hub expects is lost somewhere in the redirect chain — likely a recent change on the mirror's side. This isn't an upstream regression (it was already broken back in the 0.25 requests era), and pinning an old hub would break transformers 5.x. - - **默认 endpoint 改空串**(= huggingface_hub 默认 = 直连 `huggingface.co`):`studio/secrets.py`、Settings.tsx、 Settings.test.tsx mock 同步 - - **Settings UI 隐藏 `hf-mirror.com` preset**:endpoint 字段本身仍接受任意 URL,用户可通过「自定义 URL」粘贴 hf-mirror 继续试,或切到 ModelScope - - **复查 doc**:`docs/todo/hf-mirror-recheck.md` 记录现象、根因、复测命令、上游 PR #4071 跟踪点、复活后逆向回滚清单。建议 2 周一次复查 - - 默认 endpoint 字段类型 / 接受范围未变,已配置过自定义 endpoint 的用户不受影响 + - **Default endpoint changed to an empty string** (= the huggingface_hub default = a direct connection to `huggingface.co`): `studio/secrets.py`, Settings.tsx, and the Settings.test.tsx mock are all updated. + - **Settings UI hides the `hf-mirror.com` preset**: the endpoint field itself still accepts any URL, so you can paste hf-mirror via "Custom URL" to keep trying, or switch to ModelScope. + - **Recheck doc**: `docs/todo/hf-mirror-recheck.md` records the symptom, root cause, recheck commands, the upstream PR #4071 tracking point, and a reverse-rollback checklist for when it recovers. A recheck every 2 weeks is recommended. + - The default endpoint field's type / accepted range is unchanged, so users who already configured a custom endpoint are unaffected. --- ## v0.8.1 — 2026-05-16 -版本面板单视图 + 通道偏好与 git 解耦(ADR 0005)+ zip 用户一键启用自动更新 +Single-view version panel + update channel decoupled from git (ADR 0005) + one-click self-update for zip users -### 新增 +### Added -- **zip 解压安装用户一键初始化 git 仓库(启用自动更新)** - ADR 0002 当时只支持 git clone 部署;zip 解压用户因为没有 `.git/`,版本面板全员 unknown、自更新功能完全用不了。这版加自动 normalize: +- **One-click git-repo initialization for zip-install users (enables self-update)** + ADR 0002 only supported git-clone deployments; because zip-extract users have no `.git/`, the version panel showed everything as unknown and self-update didn't work at all. This release adds automatic normalization: - - **版本面板顶部 banner**:检测到 zip 模式时显示「启用自动更新」按钮 + 文案;没装 git 时改文案为「先安装 git」+ 官网链接 - - **bootstrap 流程**:`git init` → `git remote add origin ` → `git fetch origin master --tags` → 优先 anchor 在 `v{__version__}` tag(让 working tree 看上去干净);reset `--mixed` 不动 working tree,原 zip 文件原样保留 - - **可配置上游 URL**:fork 维护者可通过 env var `ANIMA_STUDIO_ORIGIN_URL` 覆盖默认值 - - 不支持「dev 分支 zip」场景(端用户走的是 master zip,这是开发者自测场景) + - **Banner at the top of the version panel**: when zip mode is detected, it shows an "Enable self-update" button with explanatory text; if git isn't installed, the text changes to "Install git first" with a link to the official site + - **Bootstrap flow**: `git init` → `git remote add origin ` → `git fetch origin master --tags`, anchoring on the `v{__version__}` tag when possible (so the working tree looks clean); the reset is `--mixed`, leaving the working tree untouched and your original zip files in place + - **Configurable upstream URL**: fork maintainers can override the default via the `ANIMA_STUDIO_ORIGIN_URL` env var + - The "dev-branch zip" case isn't supported (end users run the master zip; dev zips are a developer self-testing scenario) -### 变更 +### Changed -- **Settings 版本面板改单视图 + 升级通道做成用户偏好而非 git 状态(ADR 0005)(#81)** - ADR 0005:之前「通道」绑死在 git 工作树状态上(branch 名 + `git reset --hard`),release 直后会出现「装的 v0.8.0」+「↑落后 2 commits」+ 「切到 dev 没反应」的矛盾解读。新模型把通道理解为**用户视图偏好**: +- **Version panel switched to a single view + the upgrade channel is now a user preference rather than git state (ADR 0005) (#81)** + ADR 0005: the "channel" used to be tied to git working-tree state (branch name + `git reset --hard`), so right after a release you'd get the contradictory reading of "v0.8.0 installed" + "↑2 commits behind" + "switching to dev does nothing". The new model treats the channel as a **user view preference**: - - **同屏只显示当前通道**(不再 master + dev 并排),避免"两个通道都活着"的视觉混乱 - - **切 toggle 不动 git**,纯写 `system.update_channel` 到 `secrets.json`;真正"切到 dev HEAD" / "更新到 vX.Y.Z" 是独立按钮 - - **后端新「装了什么」分类** `installed_kind`(stable / dev / custom),按 commit hash + tree 比对推断,取代前端读 `branch` 做判断 - - **文案脱离 git 词汇**:「↑落后 N commits」→「有新稳定版 vX.Y.Z」/ 「已是最新」;不再出现 commits / sha / branch - - 旧 `show_dev_channel` 一次性迁移到 `update_channel`,写时双写保持向下兼容 + - **Only the current channel is shown on screen** (no more master and dev side by side), avoiding the visual confusion of "both channels are live" + - **Toggling doesn't touch git** — it just writes `system.update_channel` to `secrets.json`; the real "switch to dev HEAD" / "update to vX.Y.Z" are separate buttons + - **A new backend "what's installed" classification**, `installed_kind` (stable / dev / custom), inferred by comparing commit hash + tree, replacing the frontend's reliance on reading `branch` + - **Wording free of git jargon**: "↑N commits behind" → "New stable version vX.Y.Z available" / "Up to date"; commits / sha / branch no longer appear + - The old `show_dev_channel` is migrated once to `update_channel`, dual-writing on save for backward compatibility -### 修复 +### Fixed -- **版本面板 3 项修:切到 dev 按钮状态错 / 同版本号伪箭头 / 双按钮重复(#82)** - ADR 0005 重做后续: +- **Three version-panel fixes: wrong "switch to dev" button state / a phantom arrow for the same version / a duplicated button (#82)** + Follow-ups to the ADR 0005 rework: - - rollback 文案在 stable / dev 两种 installed_kind 下分流,不再一律显示「回滚到 vX.Y.Z」 - - preflight panel 在通道偏好与 git 状态不一致时不再展示矛盾的切换目标 - - dev 卡 fetch race:`devCommits` 和 `devCheck` 拆独立 useEffect,避免一个先 resolve 触发 re-render 跳过另一个的 fetch(症状: "切到 dev HEAD"按钮看上去 enabled 但点了 no-op) + - Rollback wording now branches on the stable / dev `installed_kind` instead of always showing "Roll back to vX.Y.Z" + - The preflight panel no longer shows a contradictory switch target when the channel preference and git state disagree + - dev-card fetch race: `devCommits` and `devCheck` are split into separate useEffects, so one resolving first can't trigger a re-render that skips the other's fetch (symptom: the "Switch to dev HEAD" button looked enabled but was a no-op when clicked) --- ## v0.8.0 — 2026-05-16 -预处理流水线 + InfoNoise 训练 + Generate/Preset UX 大改(ADR 0004) - -### 新增 - -- **新增「预处理」步骤(图片放大)+ 实时进度(#69)** - 流水线 ① 下载 → ② 筛选之间插入「② 预处理」step(旧 step 顺延),用户可在进入筛选前对下载图统一放大;现有项目可选跳过(sidebar 标「(可选)」)。 - - - **多放大器预设**:Settings → 预处理 tab 内置 ESRGAN / Real-ESRGAN 等预设 + 自定义 repo 下载,HuggingFace / ModelScope 双源 - - **智能流水**:目标面积阈值,大图直接 resize 跳过放大模型,省时 - - **GPU fp16 自动**:worker 启动期 device 诊断日志(看得到走 cuda 还是 cpu,是否 fp16) - - **进度反馈走 SSE**:每图完成事件实时推前端(不再轮询);job 跑动时 3s 轮询 files 刷新 grid / 进度 / 盘占 - - **阶段缩略图预生成**:256/768 两档供 grid 用,切 filter 不卡 -- **新增 InfoNoise 自适应 timestep 采样器(#63, #66)** - 基于 I-MMSE 等价(`dH/dσ = mmse/σ³`):跟踪 per-bin 去噪 MSE FIFO + EMA,构造反 CDF 采样器把抽样集中在信息量大的噪声窗口;warmup 期回退到 logit-normal baseline。 - - - **零默认侵入**:7 个 `infonoise_*` 字段,默认全关;存量训练行为不变 - - **N_warm 自动**:`infonoise_N_warm = 0` 自动算 `max(200, total_steps × 1/5)` —— 短 run 不再被硬编码 5000 步卡住 - - **plugin registry**:新 `runtime/training/timestep_samplers/` 子包, baseline 与 infonoise 走同一份 protocol;后续加 sampler 不动 phases / loop(沿用 ADR 0003 模式) - - **EMA 公式按论文 Algorithm 1**:`mse ← (1-β)·mse + β·ℓ̄`,docstring 顶部警告"不要按主观直觉翻转"(PR #65 误翻 → PR #66 恢复 + 16 个 regression test 把公式 codify) - - **冷启动 trip wire**:CDF 长期 not ready 时 warn 一次 + 提供 `status()` 给外部探测 -- **Train / Presets 页加 Simple / Advanced 模式切换(#63, #66)** - Train 页和 Presets 页右上角加 Simple / Advanced toggle。Simple 模式默认隐藏 35 个高级字段(dropout / scheduler 微调 / PPSF 旋钮 / 噪声 schedule 细节等),新用户不被淹没。 - - - 状态走 `useAdvancedMode` hook + `localStorage`,同 tab 实时更新; storage event 跨 tab 同步(开两个 tab 不会"漂") - - schema 重新分组:新 `noise_schedule` 组(noise offset / pyramid noise / timestep sampling / InfoNoise / loss weighting);`kv_trim` / `mixed_precision` / `attention_backend` / `num_workers` 移到 `system` 组 - - 字段描述按上下文调整:InfoNoise 启用时 `timestep_sampling` disabled + 提示语;`timestep_shift` 显示 warm-up 上下文 -- **studio CLI 加 --torch / --fe-port flag + 默认子命令修复(#63)** - - **`--torch `** (`cu128` / `cu126` / `cu124` / `cu118` / `cpu`):强制指定 CUDA torch wheel,CPU-only 租赁机 GPU torch 无法自动探测的场景。Ctrl+C 可跳过;同时支持 `--skip-pending` 跳过 restart 时的 pending install - - **`--fe-port`**(仅 `dev` 子命令):Vite dev server 端口,默认 5173 - - **默认子命令修复**:`studio.sh --port 6006` 之前会失败 `invalid choice: '6006'`,现在未知 positional 正确 fallback 到 `run` - - pip 安装期流式输出 + 优雅 Ctrl+C 中断 pending torch install - -### 变更 - -- **预处理界面整合到单 grid,不再让用户区分「下载图 / 预处理图」(ADR 0004)(#74)** - ADR 0004:「双 grid(待处理 / 已处理)」摊平成单 grid + 状态徽章;用户视角图只有一份,处理状态是图的属性。 - - - **状态唯一真理**:`projects/{id}/preprocess/manifest.json` 取代 per-image `*.preprocess.json` sidecar - - **单一 ImagesPanel**:摘要头「共 N 张 · 未处理 X · 已处理 Y」+ filter chips(全部 / 未处理 / 已处理)+ 图卡状态徽章(✓ upscale / ⊘ 未处理) - - **「⟲ 还原」按钮**替代「🗑 删除」:还原 = 删 preprocess 副本回未处理 - - **接缝坍缩**:删 `left_source` 字段 + 双 URL 分支;前端永远走 `projectThumbUrl`,不感知 download / preprocess 差异 - - **0 删除迁移**:第一次访问每个 project 时检测,无 manifest 但有老 sidecar → 聚合写出 manifest.json,老 sidecar **保留不删**(防御性回滚) -- **LoRA picker 重写 + 三处图片网格点击交互统一(#77)** - PR #70 因为 squash-merge 在 GitHub 上显示 Merged 但实际未进 dev(cherry-pick 经 #77 复活,详见 PR description)。 - - - **LoRA picker 重写**:扁平搜索 → 项目→版本→ckpt 三段下拉 + chip 列表; single / multi 模式分离,权重 slider 按需显示 - - **Download / Preprocess / Reg 三处图片网格点击交互统一**:点击 = 大图预览(lightbox);checkbox / Shift / Ctrl / ⌘ + 点击 = 多选(对齐 Curation / TagEdit 已有约定) - - **PromptFromDatasetPicker**:受控单选 + 常驻 + 只读 tags 区 + `datasetSuffix` 拼到 prompt 末尾 - - **XY 矩阵**:lora_ckpt 轴改多选 chip 「添加 N 个」;`lora_scale` 改为全局轴(不再绑特定 LoRA) -- **预设导入导出改用 yaml 文件(取代 JSON)(#79)** - 后端落盘本就是 yaml,但前端导出却是 JSON + 一个 4 行手写 mini YAML parser(只支持单行 scalar,遇到 list / nested / quoted 字段静默丢字段,例如 `sample_prompts` / `timestep_samplers`)。本 PR 把传输模型换成端到端 yaml 文件。 - - - 新 `GET /api/presets/{name}/download`:FileResponse 直接透传磁盘 yaml,导出文件跟 `studio_data/presets/{name}.yaml` 字节级一致 - - 新 `POST /api/presets/import`:multipart `UploadFile`,后端 `yaml.safe_load` + pydantic 校验,返回 `{config, suggested_name}` 给前端 draftSeed flow(不写盘,等用户改名 + 确认) - - **旧 `.json` 导出仍能导入回来**:yaml.safe_load 是 JSON 的 superset,零兼容包袱 - - 工作流闭环:用户可直接编辑 `studio_data/presets/{name}.yaml` 然后导回,schema 校验失败时错误信息从后端 `PresetError` 单一来源出 - - 按钮文案「导出 JSON」→「导出 YAML」 - -### 改进 - -- **标签编辑批量范围加「当前筛选 / 当前列表」选项(#71)** - 过去过滤后批量范围只有「当前选中」和「全部图片」:前者要先手动全选、后者会改到筛选外的隐藏图。现在把"我正在看的这批"做成明确范围。 - - - 三个范围都带计数:「当前选中(K)」/「当前筛选(N)」/「全部图片(M)」 - - 批量加 / 删 / replace / dedupe 都支持范围切换 - - 过滤结果为空时批量按钮 disabled,避免对空结果执行 -- **Generate / Queue / XY 全屏交互一组改进(#64, #68)** - - **XY 全屏 cell 导航**:方向键 / PgUp / PgDn 切换;边缘 hint 动态显示可走方向(不再撒谎承诺 4 方向);input / textarea 内不抢焦点 - - **PreviewCompare 双图对比**:全屏后 ←/→ 切 A↔B(之前只能 ESC) - - **InlineLoraPicker header**:拆 2 行(搜索 / 操作 + project / version dropdowns);projects ≤ 1 时隐藏 dropdown 行 - - **PromptFromDatasetPicker 持久化**:选过的数据集 prompt 跨刷新记忆 - - **`useLocalStorageState` hook**:通用 JSON 序列化 + storage event 跨 tab 同步 + SSR 安全;旧 `anima.` localStorage key 自动迁移到 `studio:` - - **ConfigSkeleton 公共组件**:Train + Presets 加载态视觉对齐 - - Generate sample 响应 `Cache-Control: no-store`(强制每次重抓最新) -- **wandb 训练日志:epoch 度量重整 + log_samples 开关恢复(#63, #65, #66)** - - 删 per-step `train/epoch`(step 轴本就够),加 `train/loss_epoch` (epoch 均值)+ `train/epoch` 只在 epoch 边界发一次 - - **`log_samples` 开关恢复**(PR #63 误删 → PR #65 修):默认 True (启用 wandb 即上传采样图,保持便利性);NSFW / 私有数据集可在 Settings UI 关掉,只上传 metrics - - `wandb` 依赖从 optional 升级为 `requirements.txt` 硬依赖 - -### 删除 - -- **删假「回收站」+ 队列「清理已完成」按钮(#78)** - 两个按钮共同问题:UI 暗示「可恢复 / 释放空间」,实际不兑现。 - - - **回收站**:之前删项目 / 版本是搬到 `studio_data/_trash/`,但 **没有恢复 UI** + 不自动清理 → UI 视角等同硬删,却 silently 累积孤儿目录占磁盘。改为直接 `rmtree`;删除 confirm 文案强调「此操作不可恢复」+ red danger tone - - **队列「清理已完成」按钮**:只删 `tasks` 表的 done 行(KB 级),不动 `logs/{id}.log`(MB 级),不动 output(per-version,跟 task row 无关),还漏 `failed` / `canceled` 状态。删按钮(真要清理 log / output 后续单独 feature 做) - - 顺手把 Sidebar / Stepper 的「② 预处理」「⑥ 正则集」标上「(可选)」 +Preprocessing pipeline + InfoNoise training + a major Generate/Preset UX overhaul (ADR 0004) + +### Added + +- **New "Preprocess" step (image upscaling) with live progress (#69)** + A new "② Preprocess" step is inserted between ① Download and ② Filter in the pipeline (the later steps shift down), letting you upscale all of your downloaded images before moving on to filtering. Existing projects can skip it (marked "(optional)" in the sidebar). + + - **Multiple upscaler presets**: the Settings → Preprocess tab ships with ESRGAN / Real-ESRGAN presets plus custom-repo downloads, from either HuggingFace or ModelScope + - **Smart pipeline**: a target-area threshold lets large images skip the upscaler model and resize directly, saving time + - **Automatic GPU fp16**: the worker logs device diagnostics at startup, so you can see whether it's running on CUDA or CPU and whether fp16 is active + - **Progress over SSE**: per-image completion events stream to the frontend in real time (no more polling); while a job runs, files are polled every 3s to refresh the grid, progress, and disk usage + - **Pre-generated stage thumbnails**: 256 and 768 sizes are generated ahead of time for the grid, so switching filters stays smooth +- **New InfoNoise adaptive timestep sampler (#63, #66)** + Built on the I-MMSE identity (`dH/dσ = mmse/σ³`): it tracks per-bin denoising MSE with a FIFO and EMA, then builds an inverse-CDF sampler that concentrates sampling in the most informative noise windows; during warmup it falls back to the logit-normal baseline. + + - **No impact by default**: seven `infonoise_*` fields, all off by default; existing training behavior is unchanged + - **Automatic N_warm**: `infonoise_N_warm = 0` computes `max(200, total_steps × 1/5)` automatically — short runs are no longer stuck on a hardcoded 5000 steps + - **Plugin registry**: a new `runtime/training/timestep_samplers/` subpackage where the baseline and infonoise samplers share one protocol; adding future samplers won't touch phases or the loop (following the ADR 0003 pattern) + - **EMA formula follows the paper's Algorithm 1**: `mse ← (1-β)·mse + β·ℓ̄`, with a docstring warning up top not to flip it on intuition (PR #65 flipped it by mistake → PR #66 restored it and added 16 regression tests to codify the formula) + - **Cold-start trip wire**: warns once if the CDF stays not-ready for too long, and exposes `status()` for external probing +- **Simple / Advanced mode toggle on the Train and Presets pages (#63, #66)** + A Simple / Advanced toggle now sits in the top-right of the Train and Presets pages. Simple mode hides 35 advanced fields by default (dropout, scheduler fine-tuning, PPSF knobs, noise-schedule details, and more) so new users aren't overwhelmed. + + - The state lives in a `useAdvancedMode` hook backed by `localStorage`, updating live within a tab; storage events sync it across tabs, so two open tabs won't drift + - The schema is regrouped: a new `noise_schedule` group (noise offset, pyramid noise, timestep sampling, InfoNoise, loss weighting); `kv_trim`, `mixed_precision`, `attention_backend`, and `num_workers` move into the `system` group + - Field descriptions adapt to context: when InfoNoise is on, `timestep_sampling` is disabled with an explanatory note, and `timestep_shift` shows warm-up context +- **studio CLI gains --torch / --fe-port flags and a default-subcommand fix (#63)** + - **`--torch `** (`cu128` / `cu126` / `cu124` / `cu118` / `cpu`): forces a specific CUDA torch wheel, for rented CPU-only machines where the GPU torch build can't be auto-detected. Ctrl+C skips it; `--skip-pending` also skips the pending install on restart + - **`--fe-port`** (the `dev` subcommand only): the Vite dev server port, default 5173 + - **Default-subcommand fix**: `studio.sh --port 6006` used to fail with `invalid choice: '6006'`; an unknown positional now correctly falls back to `run` + - pip installs stream their output, and Ctrl+C gracefully interrupts a pending torch install + +### Changed + +- **The preprocess UI is consolidated into a single grid, so you no longer have to distinguish "downloaded" from "preprocessed" images (ADR 0004) (#74)** + ADR 0004: the two-grid layout (pending / processed) is flattened into a single grid with status badges. From your point of view there's only one copy of each image, and processing state is just a property of it. + + - **Single source of truth for state**: `projects/{id}/preprocess/manifest.json` replaces the per-image `*.preprocess.json` sidecars + - **One ImagesPanel**: a summary header ("N total · X unprocessed · Y processed") plus filter chips (All / Unprocessed / Processed) and per-card status badges (✓ upscaled / ⊘ unprocessed) + - **A "⟲ Revert" button** replaces "🗑 Delete": reverting deletes the preprocessed copy and returns the image to unprocessed + - **Seam collapsed**: the `left_source` field and the dual-URL branch are gone; the frontend always uses `projectThumbUrl` and is unaware of any download / preprocess distinction + - **Zero-deletion migration**: the first time you open each project, if there's no manifest but old sidecars exist, they're aggregated into a new manifest.json — the old sidecars are kept, not deleted (a defensive fallback) +- **LoRA picker rewrite + unified click behavior across three image grids (#77)** + PR #70 showed as Merged on GitHub because of the squash-merge but never actually landed on dev (revived via cherry-pick in #77 — see the PR description). + + - **LoRA picker rewrite**: the flat search becomes a three-stage project → version → ckpt set of dropdowns plus a chip list; single and multi modes are separated, and the weight slider shows only when needed + - **Unified click behavior across the Download, Preprocess, and Reg image grids**: a click opens the lightbox preview; checkbox / Shift / Ctrl / ⌘ + click selects multiple (matching the existing Curation / TagEdit convention) + - **PromptFromDatasetPicker**: a controlled single-select that stays visible, with a read-only tags area and a `datasetSuffix` appended to the end of the prompt + - **XY matrix**: the lora_ckpt axis becomes a multi-select chip ("Add N") and `lora_scale` becomes a global axis (no longer tied to a specific LoRA) +- **Preset import/export now uses yaml files (replacing JSON) (#79)** + The backend already stored presets as yaml, but the frontend exported JSON plus a hand-written 4-line mini-YAML parser (single-line scalars only, silently dropping list / nested / quoted fields such as `sample_prompts` / `timestep_samplers`). This PR switches the transport model to end-to-end yaml files. + + - New `GET /api/presets/{name}/download`: a FileResponse streams the on-disk yaml straight through, so the exported file is byte-for-byte identical to `studio_data/presets/{name}.yaml` + - New `POST /api/presets/import`: a multipart `UploadFile` that the backend runs through `yaml.safe_load` and pydantic validation, returning `{config, suggested_name}` to the frontend draftSeed flow (nothing is written to disk until you rename and confirm) + - **Old `.json` exports still import**: yaml.safe_load is a superset of JSON, so there's no compatibility burden + - A closed-loop workflow: you can edit `studio_data/presets/{name}.yaml` directly and import it back, and on schema-validation failure the error message comes from a single source — the backend `PresetError` + - Button label "Export JSON" → "Export YAML" + +### Improved + +- **Tag editing adds "current filter / current list" options to its bulk scope (#71)** + After filtering, the bulk scope used to offer only "current selection" and "all images": the former meant manually selecting everything, and the latter would change hidden images outside the filter. "The batch I'm currently looking at" is now an explicit scope. + + - All three scopes carry a count: "Selected (K)" / "Current filter (N)" / "All images (M)" + - Bulk add / remove / replace / dedupe all support switching scope + - When the filter returns no results, the bulk buttons are disabled to prevent acting on an empty set +- **A set of fullscreen interaction improvements across Generate / Queue / XY (#64, #68)** + - **XY fullscreen cell navigation**: arrow keys / PgUp / PgDn move between cells; edge hints dynamically show which directions are available (no longer falsely promising all four); focus isn't stolen while you're in an input or textarea + - **PreviewCompare side-by-side**: in fullscreen, ← / → switch between A and B (previously only ESC worked) + - **InlineLoraPicker header**: split into two rows (search / actions, plus project / version dropdowns); the dropdown row is hidden when there's one project or fewer + - **PromptFromDatasetPicker persistence**: the dataset prompt you picked is remembered across refreshes + - **`useLocalStorageState` hook**: generic JSON serialization, cross-tab sync via storage events, and SSR safety; old `anima.` localStorage keys migrate automatically to `studio:` + - **Shared ConfigSkeleton component**: aligns the loading state of Train and Presets visually + - Generate sample responses now send `Cache-Control: no-store`, forcing a fresh fetch every time +- **wandb training logs: epoch metrics reorganized + log_samples toggle restored (#63, #65, #66)** + - Dropped the per-step `train/epoch` (the step axis already covers it), added `train/loss_epoch` (the epoch mean), and `train/epoch` now logs only once at each epoch boundary + - **`log_samples` toggle restored** (PR #63 removed it by mistake → PR #65 fixed it): defaults to True (enabling wandb uploads sample images, keeping things convenient); for NSFW or private datasets you can turn it off in Settings and upload metrics only + - The `wandb` dependency is promoted from optional to a hard `requirements.txt` dependency + +### Removed + +- **Removed the fake "Recycle Bin" and the queue's "Clear completed" button (#78)** + Both buttons shared one problem: the UI implied "recoverable / frees space" but didn't deliver. + + - **Recycle Bin**: deleting a project or version used to move it to `studio_data/_trash/`, but there was no restore UI and no automatic cleanup — so from the UI it behaved like a hard delete while silently accumulating orphaned directories on disk. It now calls `rmtree` directly; the delete confirmation stresses "this can't be undone" with a red danger tone + - **The queue's "Clear completed" button**: it only deleted the done rows from the `tasks` table (kilobytes), leaving `logs/{id}.log` (megabytes) and output (per-version, unrelated to the task row) untouched, and it missed `failed` / `canceled` states. The button is gone (proper log / output cleanup will come as its own feature) + - Also marked "② Preprocess" and "⑥ Regularization set" as "(optional)" in the Sidebar / Stepper --- ## v0.7.0 — 2026-05-14 -webui 一键自更新 + 训练栈解构(ADR 0002 / 0003) - -### 新增 - -- **支持 Anima 1.0 主模型(latest 默认指向 1.0)(#61)** - 上游 `circlestone-labs/Anima` 发布正式 1.0 版本 (`split_files/diffusion_models/anima-base-v1.0.safetensors`)。 - - - `ANIMA_VARIANTS` 加 `"1.0"` 条目;`LATEST_ANIMA` 切到 `"1.0"` - - dict 顺序调整为「latest 在前」——保证 `find_anima_main` fallback + `build_catalog` 给 UI 的 variants 列表顺序符合「最新优先」直觉(之前 [LATEST] + [dict 插入序] 在 LATEST 不在磁盘时会先命中最老变体) - - `schema` / `secrets` / `server` / `runtime/training/cli` / 前端 Settings 的默认值同步切到 1.0 —— **只影响新装用户 + 未写过 secrets.json 的**;已存 version 的 yaml 里 `transformer_path` 是绝对路径不动,保证训练重现性 -- **webui 一键自更新:双通道升级面板 + 回滚 + dev 通道(ADR 0002)(#51, #52, #53)** - 实现 ADR 0002:Settings → 系统 → 版本卡片里完成 git pull / 重启 / 回滚全流程,不用再回命令行。 - - - **流派 A**(flag + shell wrapper loop,学 A1111 / SwarmUI): `tmp/restart` flag + `cli.py` inner loop + `studio.sh / studio.bat` wrapper loop + `POST /api/system/restart`。强制约束:running task 时拒绝 update / restart / rollback - - **主路径**:`studio/services/updater.py` + 4 个端点(`/version` `/update_check` `/update` `/preflight`)+ 启动期 `apply_pending()` git reset + 增量 pip / npm install + Topbar update badge - - **双通道升级面板**(#52 重设计):master / dev 并排双卡 + container query 响应式 + 通道徽章(稳定/绿 · 开发版/橙)+ inline preview 面板取代所有 dialog 模态;CHANGELOG.md 解析嵌入卡片;dev 卡 commit timeline 可点击任意 commit 切换 - - **Pre-flight 4 项检查**:dirty working tree / running tasks / requirements.txt diff / `.last_version` 预览;任一 err → 确认按钮 disabled - - **回滚 + 失败 banner**:`.update_status` / `.last_version` / `.update_log` + `/api/system/rollback` + master 卡内红色失败 banner + 查看完整日志 modal - - **dev 通道 toggle**:`SystemConfig.show_dev_channel` 持久化;自动检查只看 master(避免开发者被 dev 高频 commit 持续骚扰 badge),dev 必须手动触发 - - **installer 自检(exit 42 协议)**:`cli.py` / `studio.sh` / `studio.bat` sha256 比对,任一变化 → cli.py 保留 flag + exit 42, wrapper 看到 exit 42 + tmp/restart 走 exec self - - **历史版本显示 tag**(#54 内):`updater.exact_tag_for(sha)` 用 `git describe --tags --exact-match`,命中 → 回滚按钮显示 `v0.6.0`,未命中 fallback 到 sha[:8] -- **训练栈内部重构(不影响用户行为,方便后续加自定义 adapter / scheduler)(ADR 0003)(#56, #57, #58)** - 实现 ADR 0003 全套:`runtime/anima_train.py` 从 2901 行 mega-script 拆到 `runtime/training/` 子包(25 个文件,128 行 thin entry)+ 4 个 plugin registry + `AdapterProtocol` hook。**训练行为字节级等价**——LyCORIS 路径所有 hook 都是 no-op;optimizer / scheduler 走同一份 kwargs 经 build wrapper。 - - - **PR-A(#56)模块拆分**:bootstrap / observability / model_loading / models / text_encoding / state / dataset / sampling / cli / timestep_sampling / noise / loss_weighting 12 个模块。sister script (anima_daemon / anima_generate / anima_reg_ai) 通过 re-export 契约 0 改动继续工作 - - **PR-B(#57)main() 拆 phase**:793 行 main() → 6 个 phase function (bootstrap / models / dataset / optimizer / resume / finalize) + 1 个 train_loop,靠 `TrainingContext` dataclass(43 字段 + 3 方法)串引用。消掉 main() 里 3 处近 75 行重复的 sample 块到 `run_sample` helper - - **PR-C(#58)plugin registry + AdapterProtocol**:4 个 plugin 子包 (adapters / optimizers / schedulers / inference_samplers) + 显式 BUILDERS 字典;3 处 if-elif dispatch 替换成 registry 调用。 AdapterProtocol 含 3 个可选 hook(on_step_begin / regularization_loss / excludes_weight_decay)给未来 T-LoRA / OFT / AdaLoRA 留位 - - **schema↔registry 一致性自动校验**:3 个 plugin 子包暴露 `validate_schema_consistency()`,bootstrap_phase 启动期跑一次;漏注册 / 漏 schema 早 fail - - **加新变体步骤**(详见 [`runtime/training/README.md`](runtime/training/README.md) + ADR 0003 Case 3-6):写 `training/{plugin}/{variant}.py` 含 build 函数 + BUILDERS 字典加一行 + schema Literal 加值,phases / loop / main 0 改动 -- **训练稳定性扩展:NaN skip + 噪声 / loss / timestep 采样新选项 + cross-attn KV trim(#55)** - Cherry-pick 自 PR #49(saltysalrua),三方 review 后保留 5 个低风险高价值 commit + 4 项我们的加固。**T-LoRA / Ortho-Hydra adapter / 手动 OrthoGrad 不进主仓**,放 `experimental/pr49-adapters` 长期 parking lot。 - - - **ProdigyPlus 上游 version-compat filter** + `eps=None` 支持 + StableAdamW(修上游 API 飘移 TypeError) - - **NaN detection**:loss / grad 非有限时跳过 step;bf16 + Prodigy 偶发 spike 不再炸整训练 - - **时间步相关 loss weighting**:`min_snr` / `detail_inv_t` / `cosmap` + `weight_cap_ratio` - - **可配置 timestep sampling**:`logit_normal` / `uniform` / `logit_normal_low` / `mode` + shift - - **noise_offset + pyramid_noise** 噪声增强 - - **cross-attn KV trim**:手术拆 `c5e81c2`,只挑 kv_trim 部分(丢弃 T-LoRA 改动);附带修 `_bucket = 512` 兜底(原代码 `_actual > 512` 时 NameError) - - **死 T-LoRA dispatch 清理**:原 commit 顺带泄漏的隐性 ImportError 雷 - - **9 个新字段 description 加簇前缀**:【噪声增强】/【时间步采样】/ 【损失加权】/【性能】tooltip 看得出归属 - - **`_filter_kwargs_by_signature` 加白名单**:schema 暴露的 ppsf_* 字段被上游 drop 时显式 raise 而非 silent log(避免 8 小时训练后才发现用户勾选悄悄失效) -- **新增 Prodigy+ScheduleFree 优化器(解 Prodigy 训练中偶发「风格突变 epoch」问题)(#46)** - Prodigy 内部 `d` 估计在 Flow Matching timestep 随机性 + 小数据集 + LoRA 低参数量三重噪声下会"跳档"——`d` 是不下降的累积量,一旦异常 batch 推上档,后续整段训练就用更大有效步长。社区调研结论:Flux / Qwen-Image / HiDream / 视频 DiT LoRA 已把 PPSF 作为事实默认(ai-toolkit / SimpleTuner / sd-scripts 三家都接入)。 - - - **命名**:`prodigy_plus_schedulefree`(snake_case 全名,避免和未来 vanilla Prodigy 撞名) - - **eval/train 切换**:context manager `optimizer_eval_mode()`,比 helper pair 更难漏掉一边;所有 `injector.save` 都在 ctx 内 → 保存的 .safetensors 是 averaged weights x,直接可用 - - **scheduler 互斥三层防御**:(a) 前端 disable + 自动 reset → (b) pydantic model_validator → (c) anima_train CLI 启动期 SystemExit - - **依赖 pin >=2.0.0**:PPSF v1.9.2 ↔ v2.0.0 state_dict 不兼容 - - **新 schema 元数据 `disable_when`**:复用 `show_when` 表达式语法; SchemaForm 实现 disabled + hint + force value to default - -### 变更 - -- **Settings 减法:长 help text 折进 ⓘ tooltip + 历史版本卡显示 tag 而非 commit sha(#54)** - - **`InfoButton` 组件**:click-toggle ⓘ 弹层,外部 click / Esc 关; button stopPropagation 防止放在 `` 里触发外层 toggle;新 `styles/info-button.css` 中性 `.info-btn-*` 前缀 - - **应用 tooltip 化**:ServiceSection 重启说明 / WD14 / CLTagger / anima_main 模型卡 description / xformers 互斥说明 / Layer 1 长 desc 7 项(hf endpoint / wandb 节流等)全部从 inline `

` / `desc=` 移到 label / title 旁的 ⓘ - - **`SettingsField` / `ModelGroupCard` 加 `helpTooltip` slot**; `SettingsSection` 已有 `headerExtras` slot(PR #53) - - **删冗余**:wandb「需要训练环境已安装 wandb 包」提示删除(错误位置,该提示应在 wandb 实际报错时显示) - -### 改进 - -- **训练页:内联新建预设 + tag chip 拖拽排序 + 「导出训练集」按钮改名(#47)** - 4 个独立小 polish 合一 PR: - - - **内联新建预设**:训练页 picker grid 加「+ 新建预设」虚线卡片,点击切到 SchemaForm 内联表单(名字默认 `_

` doesn't trigger the outer toggle; a new `styles/info-button.css` with a neutral `.info-btn-*` prefix + - **Applied the tooltip treatment**: the ServiceSection restart note / WD14 / CLTagger / anima_main model-card descriptions / the xformers mutual-exclusion note / 7 long Layer 1 descriptions (hf endpoint / wandb throttling, etc.) all move from inline `

` / `desc=` to a ⓘ next to the label / title + - **`SettingsField` / `ModelGroupCard` gain a `helpTooltip` slot**; `SettingsSection` already had a `headerExtras` slot (PR #53) + - **Removed redundancy**: the wandb "the training environment needs the wandb package installed" note is removed (wrong place — it should appear when wandb actually errors) + +### Improved + +- **Training page: inline preset creation + drag-to-reorder tag chips + a renamed "export training set" button (#47)** + Four independent small polish items in one PR: + + - **Inline preset creation**: the training page's picker grid gains a dashed "+ New preset" card that switches to an inline SchemaForm (the name defaults to `_