
Dataset-first LoRA training studio — because a great LoRA starts with a great dataset.
v0.8.0-beta.3 · PyTorch 2.12.1 local / 2.11.0 container · CUDA 13.0 local / 12.8 container (+cu126 fallback) · Angular 22 · Node 24 · FastAPI
Author: master-merlin · Repository: github.com/master-merlin/mrln-arcane-tuner
Every LoRA trainer will tell you: the dataset is 90 % of the result. Yet most training tools treat dataset management as an afterthought — a folder of images you dump somewhere and hope for the best.
MRLN Arcane Tuner started as a personal experiment to fix that. The goal was simple: build a workflow where dataset curation is the heart and center of the process, not a chore you skip through to get to training. From smart cropping and image adjustments to duplicate detection and stacked LUT color grading — the dataset pipeline is where most of the R&D effort lives.
The training engine, job management, and LoRA tools grew organically around that core — because once your data is good, training should be straightforward.
What you are looking at is one person's interpretation of that workflow, distilled from the hundreds of LoRAs I have trained. The tool exists to remove the parts that only cost time and let me concentrate on the quality of the data, without jumping between five other tools to get there. It is developed at the pace I can afford — it is neither my only project nor my main one — so feedback, suggestions and contributions are very welcome.
What it is not: a replacement for ai-toolkit — kudos to Ostris, a legend — and by no means feature-complete. Where a feature is missing or a family is unsupported, that is the honest state of the project, not a promise.
This project wouldn't exist without the incredible open-source community that pioneered LoRA training for diffusion models. A sincere thank you to:
ss_* keys) is the de-facto standard for LoRA checkpoint interoperability, and MRLN Arcane Tuner writes these keys for full compatibility with ComfyUI, A1111, and other inference tools.get_qwen3_vl_features (MIT License), and the diffusion_model. LoRA key convention this project saves in is ai-toolkit's. Credited in ideogram4/driver.py; full licence text in NOTICE.lora_conversion_utils.py. Credited in flux2/saver.py.lora_tools.py.Note: MRLN Arcane Tuner is a personal experiment and is not intended to compete with any of these projects. They are community pillars. This tool simply explores a different angle — dataset quality first.
| Requirement | Version | Notes |
|---|---|---|
| Python | 3.12+ | With venv support |
| NVIDIA GPU | Ampere+ (RTX 30xx) | CUDA 13.0 (driver R580+); see container note below for cloud hosts |
| Node.js | 24+ (LTS) | Required by Angular 22 / TS 6 |
| npm | 10+ | Comes with Node.js |
The install scripts create a virtual environment, install PyTorch with CUDA support, and install all Python dependencies.
Windows:
cd backend
install.bat
Linux / macOS:
cd backend
chmod +x install.sh
./install.sh
Then install the frontend:
cd frontend
npm install
# 1. Create and activate a virtual environment
cd backend
python -m venv venv
# Windows: venv\Scripts\activate
# Linux: source venv/bin/activate
# 2. Install PyTorch with CUDA 13.0 (local dev; needs an R580+ driver).
# The published container ships CUDA 12.8 (cu128, Blackwell-capable) with a
# cu126 fallback for older host drivers — see "Run as a container" below.
pip install torch==2.12.1 torchvision==0.27.1 \
--index-url https://download.pytorch.org/whl/cu130
# torchaudio has no 2.12-series wheel yet (maintenance mode) and its own
# metadata pins torch==2.11.0, so it must be installed --no-deps or pip would
# downgrade torch back to 2.11.0.
pip install torchaudio==2.11.0 --no-deps \
--index-url https://download.pytorch.org/whl/cu130
# 3. Install remaining Python dependencies. torch/torchvision/torchaudio
# (installed above) and three packages whose declared metadata is wrong must
# be excluded from this bulk install and added back with --no-deps:
# scenedetect (its GUI opencv-python dep would clobber the pinned
# opencv-python-headless), sam3 (a stale huggingface-hub<1.0 ceiling), and
# hpsv2 (its pytest dev deps leaked into its install requirements and would
# abort the resolve against our own pytest pin). grep -v filters them out
# the same way install.sh does.
# Windows users: skip this bash step and run backend\install.ps1 or
# backend\install.bat instead, which do the equivalent filtering natively.
grep -ivE '^[[:space:]]*(scenedetect|sam3|hpsv2|torch|torchvision|torchaudio)([[:space:]=<>!~#]|$)' \
requirements.txt > /tmp/requirements.filtered.txt
pip install -r /tmp/requirements.filtered.txt
for pkg in scenedetect sam3 hpsv2; do
pip install --no-deps "$(grep -iE "^[[:space:]]*${pkg}[[:space:]]*==" requirements.txt | sed -E 's/#.*$//' | tr -d '[:space:]')"
done
# 4. Install frontend dependencies
cd ../frontend
npm install
Backend:
cd backend
start_backend.bat # Windows
./start_backend.sh # Linux / macOS
The backend creates required directories (datasets/, models/, models/upscale/, outputs/) and a default settings.json on first launch.
Frontend:
cd frontend
npm run start
Or enable auto-start in Server Settings — the backend will launch the frontend and open your browser automatically on first start.
The frontend runs on http://localhost:4200 by default. Both ports are configurable in Server Settings.
update.bat :: Windows
.\update.ps1 :: Windows (PowerShell)
./update.sh # Linux / macOS
Pulls the latest main, then brings your installed dependencies back in line
with it — reinstalling backend packages if requirements.txt moved, running
npm ci if the frontend lockfile moved, and rebuilding the SPA if this install
serves a built one.
It compares what is installed against what the checkout declares, rather than looking at what a given pull happened to change, so it also repairs drift from any other cause — a branch switch, an interrupted install, or simply having skipped a few releases.
--check | report what is out of date and change nothing (exit 1 if anything is) |
--no-pull | skip git; only bring dependencies in line with the checkout you have |
--build / --no-build | force or skip the production frontend build |
It will stop rather than touch a working tree it does not own: it refuses if you
have uncommitted changes, and only ever fast-forwards — it will not merge,
reset or stash. If your branch has diverged from origin, that is left to you.
Inside the container, use the in-app updater instead (Server Settings → Update). The container owns its own checkout and restarts itself once running tasks have drained; the script detects the container and stops rather than having two updaters write to one checkout.
The app ships as a single Docker image that serves the API, the WebSocket log stream, media, and the Angular UI from one port over HTTPS. RunPod is the example provider here, but the same image works on any host with an HTTPS ingress proxy.
uvicorn process serves everything at one origin: / (UI), /api,
/api/ws, /media. The frontend uses same-origin URLs, so it works
behind RunPod's per-port proxy with zero config.0.0.0.0 — no certificates to
manage.Two public RunPod templates track this image, one per CUDA variant. They pre-fill the image, the exposed port, the volume mount path and the environment variables, so deploying is choosing a GPU and changing one value.
| Template | Use it when | Deploy |
|---|---|---|
mastermerlin-mrln-arcane-tuner (cu128) | Default. Any Ampere-or-newer GPU including Blackwell; host driver R570+ | Deploy on RunPod |
mastermerlin-mrln-arcane-tuner-cu126 | Only a legacy host on driver R560–R565. No Blackwell support | Deploy on RunPod |
Two things to change before you use the pod for anything real:
Get your access token from the pod log. The templates ship the
placeholder MRLN_AUTH_TOKEN=123 so the pod boots out of the box, and the
container refuses to serve behind it: on first boot it generates a real token,
saves it to the volume so it survives restarts, and prints it. Open the pod's
log and look for:
[entrypoint] ===================================================================
[entrypoint] ACCESS TOKEN: <this is the one you sign in with>
Setting MRLN_AUTH_TOKEN to a value of your own is still honoured and takes
precedence — a token you chose is never replaced and never printed.
Attach a network volume mounted at /workspace. A template cannot bring
storage with it. Without one the pod runs fine and destroys everything when it
stops — including the multi-GB base-model downloads you just paid to transfer.
The token is on that volume too, so a pod without one gets a new token on
every restart.
Then skip to 3. Open the app.
Use this if you want to configure the pod yourself, pin a specific version tag, build the image from source, or deploy somewhere other than RunPod.
The published image is on Docker Hub — you can use it directly, no build required:
mastermerlin/mrln-arcane-tuner:latest # rolling latest (CUDA 12.8 / cu128)
mastermerlin/mrln-arcane-tuner:latest-cu126 # rolling latest, legacy R560–R565 drivers
mastermerlin/mrln-arcane-tuner:0.8.0-beta.3 # pinned version (CUDA 12.8 / cu128)
mastermerlin/mrln-arcane-tuner:0.8.0-beta.3-cu126 # pinned version, legacy R560–R565 drivers
Every variant has both a rolling name and a pinned one. Follow a
:latest* tag to get each release automatically; pin a version tag when you
want a pod to behave the same next month. :latest and :latest-cu128 are the
same image — the unsuffixed names belong to cu128 because it is the default
variant. The rolling cu126 tags are new; before them a version tag was the only
cu126 name, so a cu126 host had to be repointed by hand at every release.
The default image bundles CUDA 12.8 (cu128) · PyTorch 2.11.0 · Python 3.12
(runtime) and a Node 24 / Angular 22 production build of the UI. cu128 ships
Blackwell (sm_120/sm_100) kernels plus Hopper/Ada/Ampere, and needs an
R570+ host driver — which Blackwell cards require anyway, so it covers the
whole modern fleet. The -cu126 tag is for older hosts pinned to R560–R565
drivers (no Blackwell support).
"no kernel image is available for execution on the device" on a Blackwell card (e.g. RTX PRO 6000) means you're on an older cu126 image — pull
latest(cu128).
Building your own (only needed if you've modified the code). The CUDA target is parameterized via build args (default cu128):
GIT_SHA is required — the image is built from a specific commit rather
than from whatever the branch points at, so two builds of the same tag contain
the same code and the image records which. A build without it fails immediately
rather than quietly tracking a moving branch.
Build through docker-build.ps1 rather than calling docker build
directly. GIT_SHA alone is not enough to guarantee what ends up in the image:
the Dockerfile's own git rev-parse HEAD == $GIT_SHA assertion lives inside a
RUN, and a cache hit never re-runs it. A published image once shipped a
different commit than the one it was built with while the build exited 0,
printed writing image and named its tags — so the wrapper builds to a scratch
tag, reads the commit back out of the finished image, and applies the
release tags only if it matches. A mismatch leaves the previous image holding
:latest. It also writes the build log and the exact arguments it used to
.agent/workdir/, and it never pushes.
$SHA = git rev-parse HEAD # the full 40-char commit; a short sha is refused
# Primary (cu128 — Blackwell + modern fleet). Takes the version tag and latest.
.\docker-build.ps1 -GitSha $SHA -Variant cu128 -Version 0.8.0-beta.3 `
-TokenPath <path-to-a-file-holding-a-github-token>
# Fallback (cu126 — legacy R560–R565 drivers).
.\docker-build.ps1 -GitSha $SHA -Variant cu126 -Version 0.8.0-beta.3 `
-TokenPath <path-to-a-file-holding-a-github-token>
Each variant claims its own tags, and the wrapper applies them only after the image has proven its commit — so a failed verification leaves the previous image holding them:
| Variant | Tags applied on success |
|---|---|
cu128 (default) | mastermerlin/mrln-arcane-tuner:0.8.0-beta.3 and mastermerlin/mrln-arcane-tuner:latest |
cu126 (fallback) | mastermerlin/mrln-arcane-tuner:0.8.0-beta.3-cu126 |
Add -NoCache for a build you intend to publish: the clone layer is the one
that went wrong, and rebuilding it unconditionally removes the ambiguity at the
cost of a long build. Pushing stays a separate, deliberate step:
docker push mastermerlin/mrln-arcane-tuner:0.8.0-beta.3
docker push mastermerlin/mrln-arcane-tuner:latest
docker push mastermerlin/mrln-arcane-tuner:0.8.0-beta.3-cu126
The commit must already be pushed to the remote — the build clones it, so a local-only commit fails the build rather than baking in code nobody else can retrieve.
Ollama (the optional caption-refinement sidecar) is installed by piping
ollama.com/install.sh into a root shell, which is an unpinned third-party
script executing at build time. For a build you intend to publish, pin it:
# Get the digest once, then pass both — one without the other is refused.
# The published asset is a zstd tarball; the .tgz form no longer exists upstream.
# Authoritative digests are in each release's sha256sum.txt.
curl -fsSL https://github.com/ollama/ollama/releases/download/<tag>/ollama-linux-amd64.tar.zst | sha256sum
.\docker-build.ps1 -GitSha $SHA -Variant cu128 -Version 0.8.0-beta.3 `
-TokenPath <token-file> -OllamaVersion <tag> -OllamaSha256 <digest>
--build-arg INSTALL_OLLAMA=0 skips it entirely; the app starts fine without
it and simply reports the sidecar as disabled.
The container runs as UID 10001, not root. The entrypoint starts as root
only long enough to take ownership of the mounted data volume, then drops. If
you mount a volume whose contents are owned by a different UID, files created by
an earlier root-run container may need chown -R 10001:10001 once.
GPU: any NVIDIA Ampere+ GPU, including Blackwell (RTX 50xx / RTX PRO
6000 Blackwell). The default image is built for CUDA 12.8 (cu128) and
needs an R570+ host driver — standard on current cloud hosts and mandatory
for Blackwell anyway. On a legacy host stuck on R560–R565, use the
:0.8.0-beta.3-cu126 tag instead (no Blackwell support). Avoid CUDA 13 in the
container: it needs R580+ and its forward-compat layer breaks cuBLAS on older
drivers.
Container image: mastermerlin/mrln-arcane-tuner:latest
Volume (strongly recommended): attach a network volume mounted at
/workspace. The SQLite DB, datasets/, models/, outputs/, and the
Hugging Face cache (hf-cache/) are stored there, so base models /
encoders download only once and your work survives restarts. Without a
volume the container runs but all data — including multi-GB model downloads —
is lost when the pod stops.
Expose HTTP port: add 8000 to the pod's Expose HTTP Ports field.
Environment variables:
| Variable | Purpose | Default |
|---|---|---|
MRLN_AUTH_TOKEN | Require this token to access the app. Leave it unset (or at the templates' 123) and the container generates one and prints it to the log. | generated on first boot |
MRLN_BIND_HOST | Address to serve on. The container needs 0.0.0.0 to be reachable at all; local installs default to loopback. | 0.0.0.0 (container) |
PORT | Internal port (match the exposed HTTP port). | 8000 |
MRLN_DATA_DIR | Persistence root (DB, datasets, models, outputs, HF cache). | /workspace |
HF_TOKEN | Hugging Face token — set it if you train/pull gated models (e.g. some FLUX weights). | unset |
HF_HOME | Hugging Face cache location. Auto-set to $MRLN_DATA_DIR/hf-cache so downloads persist on the volume — only override to relocate the cache. | /workspace/hf-cache |
CUDA_VISIBLE_DEVICES | Pin a specific GPU on multi-GPU pods. | all |
RunPod exposes the port at:
https://[POD_ID]-8000.proxy.runpod.net
Open it; you'll get a sign-in page — enter the token once and a cookie keeps you signed in.
⚠ Breaking change in this release: the container needs
MRLN_AUTH_TOKENEarlier versions started an unauthenticated server on
0.0.0.0whenMRLN_AUTH_TOKENwas unset. On a RunPod pod that proxy URL is public, so anyone who guessed it had full control of your datasets, models and GPU — and nothing said so.The app now refuses to start when it is bound to an address other machines can reach and no token is set:
- set
MRLN_AUTH_TOKENto a long random string, or- set
MRLN_BIND_HOST=127.0.0.1for a private, machine-local run.In the container you no longer have to do either. The entrypoint treats an absent token — and the
123the published RunPod templates ship — as no token at all: it generates a real one, saves it next to your data so restarts keep it, and prints it to the pod log as[entrypoint] ACCESS TOKEN: …. A token you set yourself is never replaced and never printed.A placeholder is worth naming for what it was: it answered this guard while giving none of the protection the guard exists for, since the pod URL is public and
123is the first string anyone tries. A pod that looks configured is worse than one that visibly refuses.Local installs are unaffected in normal use:
start_backendnow binds loopback by default instead of0.0.0.0, so it starts with no token as before. It used to publish an open server onto every network you joined, including untrusted wifi. To reach a local install from another machine, set bothMRLN_AUTH_TOKENandMRLN_BIND_HOST=0.0.0.0.
/workspace volume directly.CUDA_VISIBLE_DEVICES.8000 and (optionally) set
MRLN_AUTH_TOKEN.MRLN Arcane Tuner is a full-stack application with a FastAPI backend and an Angular frontend connected via REST API and WebSocket.
┌──────────────────────────────────────────────────────┐
│ Angular 22 SPA │
│ 43 Standalone Components · Signals · Tailwind │
└──────────────────┬────────────────┬──────────────────┘
│ REST │ WebSocket
┌──────────────────┴────────────────┴──────────────────┐
│ FastAPI Backend │
│ 9 Route Domains · Structured Logging · Middleware │
├─────────────┬────────────┬───────────┬───────────────┤
│ Dataset │ Training │ AI │ System │
│ Manager │ Engine │ Services │ Settings │
├─────────────┴────────────┴───────────┴───────────────┤
│ PyTorch · Diffusers · PEFT · SQLite │
└──────────────────────────────────────────────────────┘
For the full component and API route inventory, see docs/ARCHITECTURE.md.
The dataset pipeline is designed to get your images from raw collection to training-ready with maximum control.
Automatic directory scanning with image–caption pairing. Supports .png, .jpg, .webp images with paired .txt caption files. Incremental and full rescan modes.
The library card and workspace details footer both need one representative image, and by default that's just whatever the scanner enumerates first. A pin control (left of Adjust image in a grid tile's top-right cluster, left of Adjust in the details footer) lets you choose it instead — it's a toggle, so unpinning re-elects the automatic choice immediately. The pin is persisted (datasets.preview_pinned, PUT /datasets/{name}/preview) and survives a rescan — every scan used to overwrite the cover with the next enumerated file, which is what made pinning worth having in the first place. If the pinned file is later deleted, the next scan clears the pin and falls back rather than showing a hole. Library and workspace-grid covers are served from a sized thumbnail rendition, not the full training source.
A non-destructive adjustment pipeline, ten operations deep, that renders into a saved overlay rather than touching the source file until you explicitly Save, Bake in, or apply it to other images ("Mass edit"):
| Stage | Controls |
|---|---|
| White Balance | Temperature / tint |
| Curves | Per-channel Bézier curves (master + individual R/G/B) |
| Color & Tone | Hue rotation, saturation, contrast |
| HSL | Selective hue/saturation/lightness control per color range |
| Sharpen | Unsharp mask / kernel / high-pass, with radius, amount and threshold |
| Vignette | Amount, midpoint, feather, circular or rectangular shape |
| Lens | Barrel distortion and vertical/horizontal keystone correction, auto-crop |
| Stacked LUT | Apply .cube LUT files, tetrahedral interpolation, adjustable strength |
| Color Match | Match tone/color to a reference image (CDF or wavelet method); always applies first, not reorderable |
Three further stages run a model rather than a formula, so results can vary run to run — Denoise, Face Restore and Upscale (restoration/upscale models you supply, tile-based). Every enabled operation is individually toggleable and drag-reorderable (except Color Match). Cropping is a separate, destructive editor — it changes the image's aspect-ratio bucket, so it is not part of this pipeline.
All adjustments include a real-time canvas preview with live histogram visualization.
Resolution-aware aspect-ratio bucketing with visual crop preview. Images are automatically grouped into optimal width×height buckets (divisible by 32) matching your target training resolutions.
Bucketing modes:
Dataset version bumping invalidates latent and text embedding caches, ensuring training always uses current image state. Cache admin UI lets you inspect and purge cached data.
Tiled neural upscaling using ESRGAN and SwinIR models for images that need higher resolution before training.
Group the datasets, templates and job history for one LoRA effort so you stop re-picking the same dataset and re-configuring the same training settings for every run. A project scopes its own branched caption/mask/training/adaptive-targeting templates (independent copies of the global ones), links existing datasets without moving files, and exposes a three-step Quick Train flow for launching a run without leaving the page. Export bundles a project's templates and datasets (embed / reference / exclude, chosen per dataset) into one portable zip; the same import wizard detects whether a zip is a dataset, a template bundle or a full project. See docs/projects-guide.md.
A saved configuration you reuse instead of re-deciding it every time — a caption system prompt, masking parameters, a training config per model family, or a set of adaptive-targeting knobs. Created where you tune it (the Datasets tab's caption/masking settings, the Training screen's Template Selection and Adaptive Layer Targeting cards) and listed together on one /templates screen, Global or scoped to a project, with per-row edit / edit-JSON / branch / delete plus export and import (single template or a filtered bundle, with an import plan that flags name clashes and missing model definitions first). See docs/templates-guide.md.
Integrated AI models for automated dataset annotation, running as GPU-backed batch services:
| Model | Specialty |
|---|---|
| Florence-2 | Fast, reliable descriptions. Multiple detail levels. |
| JoyCaption Beta | 12 caption types (descriptive, prompt-style, tag lists). Extensive control options. |
| Qwen3-VL | Large vision-language model for nuanced descriptions. Configurable variant (4B/8B). |
| Youtu-VL | Tencent's vision-language model with fine-grained parameter control. |
All models support batch processing with real-time progress, custom system prompts, and per-model template management.
| Model | Approach |
|---|---|
| SAM 3 | Text-prompted segmentation (Meta's Segment Anything). Multi-mask output. |
| RemBG | Background removal with 15+ model variants (BiRefNet, ISNet, U2Net, BRIA). Alpha matting support. |
Supports batch mass-apply across entire datasets.
Training is configured through a dynamic JSON Schema-driven UI — the form auto-generates from model-family definitions, so new fields appear automatically without frontend code changes. See docs/training-guide.md for the full storyline: picking a model, attaching datasets, reading the VRAM estimate, Adaptive Layer Targeting, and every field on the form grouped as the screen groups them.
29 families, 54 shipped definitions. A family is an architecture with its own loader, driver, trainer, sampler and saver; a definition is one concrete checkpoint of it, declared in YAML. Archetypes, capability flags and the shared support packages are in ARCHITECTURE.md.
This table is generated from backend/app/engine/models/families/ and pinned by backend/tests/test_readme_family_table.py, so a family added without a row here fails the gate. It listed three of twenty-eight until 2026-08-28 — there is no text-encoder column because populating it accurately for every family means reading every loader, and twenty-five blanks would say less than no column at all.
Image — 20 families
| Family | Definitions shipped |
|---|---|
boogu_image | Boogu-Image 0.1 Base · Boogu-Image 0.1 Edit · Boogu-Image 0.1 Turbo |
chroma | Chroma1 Base · Chroma1 HD |
dreamlite | DreamLite Base · DreamLite Mobile |
ernie_image | ERNIE Image (Base 8B) |
flux1 | FLUX.1 Dev · FLUX.1 Kontext Dev · FLUX.1 Schnell |
flux2 | Flux 2 (Dev) · Flux 2 (Klein Base 4B) · Flux 2 (Klein Base 9B) |
hidream_o1 | HiDream-O1-Image (Full) |
ideogram4 | Ideogram 4 (fp8, 9.3B) |
krea2 | Krea-2 Raw · Krea-2 Turbo |
longcat_image | LongCat-Image |
lumina2 | Lumina-Image 2.0 |
microsoft_lens | Microsoft Lens (Base 3.8B) |
nucleus_image | Nucleus-Image |
omnigen2 | OmniGen2 |
ovis_image | Ovis-Image 7B |
prx | PRX 512 T2I (SFT) |
prx_pixel | PRX Pixel T2I |
qwen_image | Qwen-Image 2512 · Qwen-Image-Edit 2509 · Qwen-Image-Edit 2511 |
sdxl | Illustrious-XL v2.0 · NoobAI-XL v1.1 · Stable Diffusion XL Base |
zimage | Z-Image Base · Z-Image De-Turbo (ostris) |
Video — 8 families
| Family | Definitions shipped |
|---|---|
bernini_r | Bernini-R 14B (Video Edit, MoE) · Bernini-R 1.3B (Video Edit) |
hunyuan_video15 | HunyuanVideo 1.5 480p I2V · HunyuanVideo 1.5 480p T2V |
kandinsky5 | Kandinsky 5.0 I2V Pro SFT 5s · Kandinsky 5.0 T2V Lite SFT 5s |
ltx2 | LTX 2.3 (Lightricks) · LTX 2.5 (Lightricks) |
minimax_h3 | MiniMax H3 (text → video+audio) · MiniMax H3 (first/last frame → video+audio) · MiniMax H3 (reference → video+audio) — not selectable in this release: the family ships as a scaffold (loader, video contract, vendored model classes) and training lands in a later one, so the three definitions are gated out of the model pickers |
wan21 | WAN 2.1 I2V 14B 720P · WAN 2.1 I2V 14B 480P · WAN 2.1 T2V 14B · WAN 2.1 T2V 1.3B |
wan22 | WAN 2.2 I2V A14B (MoE) · WAN 2.2 T2V A14B (MoE) |
wan22_ti2v_5b | WAN 2.2 TI2V 5B (Dense) |
Audio — 1 family
| Family | Definitions shipped |
|---|---|
ace_step15 | ACE-Step 1.5 (Turbo) · ACE-Step 1.5 XL (Base) |
⚠️ Available but experimental. These features ship and pass the test suite, but are still being validated on real end-to-end training runs. Treat results as beta-quality and expect rough edges.
MRLN Arcane Tuner supports a wide range of optimizers, from proven defaults to cutting-edge research:
Standard (Adam-family):
Adaptive Learning Rate:
1.0.Memory-Efficient:
Second-Order:
Advanced:
| Strategy | Best For |
|---|---|
| logit_normal | Default for flow matching (Flux). Mid-range focus. |
| uniform | Equal probability baseline. |
| sigmoid | Simplified logit-normal with fixed parameters. |
| cosmap | Cosine-mapped smooth distribution. |
| mode | Configurable mid-range emphasis. |
| flux_shift | Resolution-dependent shifting. Original Flux recipe. |
| radc | Resolution-Aware Dynamic Curriculum. Progressive coarse-to-fine learning. Best for multi-phase curriculum training. |
RADC is a standout feature — it progressively shifts the noise focus from high-noise (learning structure and composition) to low-noise (refining details and textures) over the course of training, with optional resolution-aware weighting for multi-resolution datasets.
Periodically measures how much each LoRA module's effective weight is still changing (an EMA-smoothed, windowed norm) and freezes the ones that have stopped moving, so late-stage updates are confined to the modules still learning. Off by default.
Selection runs per projection group — every block's to_v is ranked against the other to_vs, never against the feed-forward projections. Raw ‖ΔW‖² is not comparable across matrix shapes: under grouped-query attention a to_v delta has an order of magnitude fewer elements than an ff.gate delta, so a single global ranking would retire the entire text-conditioning pathway on shape alone. Each group also carries its own share of the min_active_pct floor, so no pathway can be eliminated wholesale.
This is a regularizer, not a speedup — it does not reduce step time. Freezing pins a module's learned delta; it does not remove the module. The forward pass is unchanged by design: a frozen module's delta is part of the model, and dropping it would silently revert what that module learned. The backward pass still runs through every block down to the earliest module that is still active, and since the modules that keep learning are usually spread across the full depth, there is rarely a cold prefix for autograd to skip. What you gain is capacity control — fewer modules can absorb new information during the phase where a LoRA is most prone to memorizing training specifics. Note also that freezing stops further drift in a module; it never walks back drift that already happened.
Two action modes: freeze flips requires_grad in place; rebuild additionally checkpoints and relaunches the same job with the optimizer rebuilt over only the kept parameters, reclaiming optimizer-state VRAM you can then spend on batch size or resolution. In both modes every checkpoint and the exported LoRA still contain all modules.
Conservative / Balanced / Aggressive presets are fully editable and save as your own templates. The narrowing is plotted live on the training curve and summarized afterward in Training Stats, which lists the earliest still-active block per event — the number that tells you whether any backward work could have been skipped at all. Watch min_active_pct too: once the active set reaches that floor, later events are no-ops.
alpha = rank / 2.Save, load, and manage training configurations as templates per model family. Auto-save on job creation.
Full checkpoint support including LoRA weights, optimizer state, scheduler, GradScaler, EMA shadow weights, and latent/embedding cache manifests. Resume training with selective cache re-use.
See docs/jobs-guide.md for the full storyline: reading a
running job's live metrics and samples, the two-mode Resume dialog, downloading
a finished LoRA, and the cross-run Training Statistics modal.
All · 1k · 500 · 100 narrows how much of the run the chart shows; it's a view over the data, so the KPI rail (best loss, convergence) stays keyed to the whole run regardless of the window⟳) — re-reads the saved curve, including on a running job. The trainer only rewrites loss_history.json at the end of a run and flushes step_metrics every 50 steps, so a live re-read is accurate to the last flush, not to the instantAdaptive: n/m layers, next to the live chip, for runs using adaptive layer targeting<elapsed> elapsed · started <time>. Elapsed comes from the trainer, not wall-clock-minus-start: it's real run time (paused time excluded, and the offset from earlier sessions of a resumed run carried forward), so a pause doesn't inflate it and a backend restart doesn't lose itSQLite-backed persistent tracking of all training runs:
See docs/tools-guide.md for the full storyline:
Inspect's Speed Training Suggestion and per-layer norm graphs, copying a
targeted layer list, and Resize's SVD rank change with the path roots both
tools are restricted to.
Analyze any .safetensors LoRA file without loading a model:
ss_* keys (optimizer, LR, schedule, resolution, etc.)ss_tag_frequency metadataSVD-based rank change (up or down) with proportional alpha scaling. Reconstructs the effective weight delta W = B @ A, decomposes via truncated SVD, and re-factors to the target rank.
Combine Inspect results with Targeted Layer Training — inspect a reference LoRA to identify which layers learned most, then configure your next training run to target only those layers for more efficient, focused learning.
See docs/server-guide.md for the full storyline:
the health KPI rail, restart and in-app updates, Connection/Models settings,
the LLM Refine Endpoint's Save & Test, and the live, filterable Server Logs
card.
Detailed architecture documentation, including full API route inventory and component listing:
Created and maintained by master-merlin.
Repository: github.com/master-merlin/mrln-arcane-tuner — issues, contributions, and discussion are welcome there.
MRLN Arcane Tuner is licensed under the Apache License 2.0. The full text is
in LICENSE.
Third-party code vendored into this repository keeps its own licence. Those
attributions — Apache-2.0 and MIT — are listed in NOTICE, together
with the upstream each component came from.
MRLN Arcane Tuner ships no model weights. It downloads them, on your instruction, from the upstream repository named in each model definition. Those weights carry their own licences, which are not this project's licence.
Two things vary per model, and they are independent, so neither answers the other:
HF_TOKEN before a download will succeed.black-forest-labs/FLUX.1-schnell shows why the two are separate: it is
Apache-2.0 and still gated.
The application does not check, gate, or enforce any of this — it cannot know your intended use, and the agreement is between you and the model's publisher. The table below is information, not enforcement. Verified against the HuggingFace model API on 2026-08-25; upstream terms can change, so treat the model page as authoritative.
Restricted — read the terms before you train something you intend to sell:
| Family | Upstream weights | Licence | Access |
|---|---|---|---|
ideogram4 | ideogram-ai/ideogram-4-fp8 | ideogram-4-non-commercial — non-commercial | open |
flux1 | black-forest-labs/FLUX.1-dev | flux-1-dev-non-commercial-license — non-commercial | gated — you must accept the agreement on HuggingFace first |
flux2 | black-forest-labs/FLUX.2-dev | flux-non-commercial-license — non-commercial | gated |
Permissive (verified): hidream_o1
(HiDream-ai/HiDream-O1-Image,
MIT) · sdxl
(stabilityai/stable-diffusion-xl-base-1.0,
OpenRAIL++) · boogu_image
(Boogu/Boogu-Image-0.1-Base,
Apache-2.0 as tagged upstream).
Everything else — check the model page. The families here draw on roughly
fifty upstream repositories (Wan, Qwen, Kandinsky, LTX-2, ACE-Step, OmniGen2,
Lumina, Chroma, ERNIE, Z-Image, Krea, LongCat, Nucleus, Ovis, PRX, DreamLite,
HunyuanVideo, Bernini-R, Lens and others), each with its own terms, and some
are gated or regionally restricted. The upstream repository for every
model is named in its definition under
backend/app/engine/models/families/<family>/definitions/.
Two things worth being explicit about:
Some model families vendor a small amount of upstream Python (a transformer
forward, a scheduler, a cache helper) under
backend/app/engine/models/families/<family>/vendor/, because the released
diffusers does not carry that architecture. Each file names its upstream and
revision in its header.
NOTICE is the authoritative list of what is vendored and under
whose terms — it is kept in step with the tree, and duplicating it here would
only give the two copies a chance to drift apart.
Third-party dependencies installed from PyPI and npm keep their own licences; the CI gate publishes an inventory of both on every run.
9 commits
2 commits
Python
67.9%
TypeScript
29.0%
HTML
1.4%
CSS
1.1%

Dataset-first LoRA training studio — because a great LoRA starts with a great dataset.
v0.8.0-beta.3 · PyTorch 2.12.1 local / 2.11.0 container · CUDA 13.0 local / 12.8 container (+cu126 fallback) · Angular 22 · Node 24 · FastAPI
Author: master-merlin · Repository: github.com/master-merlin/mrln-arcane-tuner
Every LoRA trainer will tell you: the dataset is 90 % of the result. Yet most training tools treat dataset management as an afterthought — a folder of images you dump somewhere and hope for the best.
MRLN Arcane Tuner started as a personal experiment to fix that. The goal was simple: build a workflow where dataset curation is the heart and center of the process, not a chore you skip through to get to training. From smart cropping and image adjustments to duplicate detection and stacked LUT color grading — the dataset pipeline is where most of the R&D effort lives.
The training engine, job management, and LoRA tools grew organically around that core — because once your data is good, training should be straightforward.
What you are looking at is one person's interpretation of that workflow, distilled from the hundreds of LoRAs I have trained. The tool exists to remove the parts that only cost time and let me concentrate on the quality of the data, without jumping between five other tools to get there. It is developed at the pace I can afford — it is neither my only project nor my main one — so feedback, suggestions and contributions are very welcome.
What it is not: a replacement for ai-toolkit — kudos to Ostris, a legend — and by no means feature-complete. Where a feature is missing or a family is unsupported, that is the honest state of the project, not a promise.
This project wouldn't exist without the incredible open-source community that pioneered LoRA training for diffusion models. A sincere thank you to:
ss_* keys) is the de-facto standard for LoRA checkpoint interoperability, and MRLN Arcane Tuner writes these keys for full compatibility with ComfyUI, A1111, and other inference tools.get_qwen3_vl_features (MIT License), and the diffusion_model. LoRA key convention this project saves in is ai-toolkit's. Credited in ideogram4/driver.py; full licence text in NOTICE.lora_conversion_utils.py. Credited in flux2/saver.py.lora_tools.py.Note: MRLN Arcane Tuner is a personal experiment and is not intended to compete with any of these projects. They are community pillars. This tool simply explores a different angle — dataset quality first.
| Requirement | Version | Notes |
|---|---|---|
| Python | 3.12+ | With venv support |
| NVIDIA GPU | Ampere+ (RTX 30xx) | CUDA 13.0 (driver R580+); see container note below for cloud hosts |
| Node.js | 24+ (LTS) | Required by Angular 22 / TS 6 |
| npm | 10+ | Comes with Node.js |
The install scripts create a virtual environment, install PyTorch with CUDA support, and install all Python dependencies.
Windows:
cd backend
install.bat
Linux / macOS:
cd backend
chmod +x install.sh
./install.sh
Then install the frontend:
cd frontend
npm install
# 1. Create and activate a virtual environment
cd backend
python -m venv venv
# Windows: venv\Scripts\activate
# Linux: source venv/bin/activate
# 2. Install PyTorch with CUDA 13.0 (local dev; needs an R580+ driver).
# The published container ships CUDA 12.8 (cu128, Blackwell-capable) with a
# cu126 fallback for older host drivers — see "Run as a container" below.
pip install torch==2.12.1 torchvision==0.27.1 \
--index-url https://download.pytorch.org/whl/cu130
# torchaudio has no 2.12-series wheel yet (maintenance mode) and its own
# metadata pins torch==2.11.0, so it must be installed --no-deps or pip would
# downgrade torch back to 2.11.0.
pip install torchaudio==2.11.0 --no-deps \
--index-url https://download.pytorch.org/whl/cu130
# 3. Install remaining Python dependencies. torch/torchvision/torchaudio
# (installed above) and three packages whose declared metadata is wrong must
# be excluded from this bulk install and added back with --no-deps:
# scenedetect (its GUI opencv-python dep would clobber the pinned
# opencv-python-headless), sam3 (a stale huggingface-hub<1.0 ceiling), and
# hpsv2 (its pytest dev deps leaked into its install requirements and would
# abort the resolve against our own pytest pin). grep -v filters them out
# the same way install.sh does.
# Windows users: skip this bash step and run backend\install.ps1 or
# backend\install.bat instead, which do the equivalent filtering natively.
grep -ivE '^[[:space:]]*(scenedetect|sam3|hpsv2|torch|torchvision|torchaudio)([[:space:]=<>!~#]|$)' \
requirements.txt > /tmp/requirements.filtered.txt
pip install -r /tmp/requirements.filtered.txt
for pkg in scenedetect sam3 hpsv2; do
pip install --no-deps "$(grep -iE "^[[:space:]]*${pkg}[[:space:]]*==" requirements.txt | sed -E 's/#.*$//' | tr -d '[:space:]')"
done
# 4. Install frontend dependencies
cd ../frontend
npm install
Backend:
cd backend
start_backend.bat # Windows
./start_backend.sh # Linux / macOS
The backend creates required directories (datasets/, models/, models/upscale/, outputs/) and a default settings.json on first launch.
Frontend:
cd frontend
npm run start
Or enable auto-start in Server Settings — the backend will launch the frontend and open your browser automatically on first start.
The frontend runs on http://localhost:4200 by default. Both ports are configurable in Server Settings.
update.bat :: Windows
.\update.ps1 :: Windows (PowerShell)
./update.sh # Linux / macOS
Pulls the latest main, then brings your installed dependencies back in line
with it — reinstalling backend packages if requirements.txt moved, running
npm ci if the frontend lockfile moved, and rebuilding the SPA if this install
serves a built one.
It compares what is installed against what the checkout declares, rather than looking at what a given pull happened to change, so it also repairs drift from any other cause — a branch switch, an interrupted install, or simply having skipped a few releases.
--check | report what is out of date and change nothing (exit 1 if anything is) |
--no-pull | skip git; only bring dependencies in line with the checkout you have |
--build / --no-build | force or skip the production frontend build |
It will stop rather than touch a working tree it does not own: it refuses if you
have uncommitted changes, and only ever fast-forwards — it will not merge,
reset or stash. If your branch has diverged from origin, that is left to you.
Inside the container, use the in-app updater instead (Server Settings → Update). The container owns its own checkout and restarts itself once running tasks have drained; the script detects the container and stops rather than having two updaters write to one checkout.
The app ships as a single Docker image that serves the API, the WebSocket log stream, media, and the Angular UI from one port over HTTPS. RunPod is the example provider here, but the same image works on any host with an HTTPS ingress proxy.
uvicorn process serves everything at one origin: / (UI), /api,
/api/ws, /media. The frontend uses same-origin URLs, so it works
behind RunPod's per-port proxy with zero config.0.0.0.0 — no certificates to
manage.Two public RunPod templates track this image, one per CUDA variant. They pre-fill the image, the exposed port, the volume mount path and the environment variables, so deploying is choosing a GPU and changing one value.
| Template | Use it when | Deploy |
|---|---|---|
mastermerlin-mrln-arcane-tuner (cu128) | Default. Any Ampere-or-newer GPU including Blackwell; host driver R570+ | Deploy on RunPod |
mastermerlin-mrln-arcane-tuner-cu126 | Only a legacy host on driver R560–R565. No Blackwell support | Deploy on RunPod |
Two things to change before you use the pod for anything real:
Get your access token from the pod log. The templates ship the
placeholder MRLN_AUTH_TOKEN=123 so the pod boots out of the box, and the
container refuses to serve behind it: on first boot it generates a real token,
saves it to the volume so it survives restarts, and prints it. Open the pod's
log and look for:
[entrypoint] ===================================================================
[entrypoint] ACCESS TOKEN: <this is the one you sign in with>
Setting MRLN_AUTH_TOKEN to a value of your own is still honoured and takes
precedence — a token you chose is never replaced and never printed.
Attach a network volume mounted at /workspace. A template cannot bring
storage with it. Without one the pod runs fine and destroys everything when it
stops — including the multi-GB base-model downloads you just paid to transfer.
The token is on that volume too, so a pod without one gets a new token on
every restart.
Then skip to 3. Open the app.
Use this if you want to configure the pod yourself, pin a specific version tag, build the image from source, or deploy somewhere other than RunPod.
The published image is on Docker Hub — you can use it directly, no build required:
mastermerlin/mrln-arcane-tuner:latest # rolling latest (CUDA 12.8 / cu128)
mastermerlin/mrln-arcane-tuner:latest-cu126 # rolling latest, legacy R560–R565 drivers
mastermerlin/mrln-arcane-tuner:0.8.0-beta.3 # pinned version (CUDA 12.8 / cu128)
mastermerlin/mrln-arcane-tuner:0.8.0-beta.3-cu126 # pinned version, legacy R560–R565 drivers
Every variant has both a rolling name and a pinned one. Follow a
:latest* tag to get each release automatically; pin a version tag when you
want a pod to behave the same next month. :latest and :latest-cu128 are the
same image — the unsuffixed names belong to cu128 because it is the default
variant. The rolling cu126 tags are new; before them a version tag was the only
cu126 name, so a cu126 host had to be repointed by hand at every release.
The default image bundles CUDA 12.8 (cu128) · PyTorch 2.11.0 · Python 3.12
(runtime) and a Node 24 / Angular 22 production build of the UI. cu128 ships
Blackwell (sm_120/sm_100) kernels plus Hopper/Ada/Ampere, and needs an
R570+ host driver — which Blackwell cards require anyway, so it covers the
whole modern fleet. The -cu126 tag is for older hosts pinned to R560–R565
drivers (no Blackwell support).
"no kernel image is available for execution on the device" on a Blackwell card (e.g. RTX PRO 6000) means you're on an older cu126 image — pull
latest(cu128).
Building your own (only needed if you've modified the code). The CUDA target is parameterized via build args (default cu128):
GIT_SHA is required — the image is built from a specific commit rather
than from whatever the branch points at, so two builds of the same tag contain
the same code and the image records which. A build without it fails immediately
rather than quietly tracking a moving branch.
Build through docker-build.ps1 rather than calling docker build
directly. GIT_SHA alone is not enough to guarantee what ends up in the image:
the Dockerfile's own git rev-parse HEAD == $GIT_SHA assertion lives inside a
RUN, and a cache hit never re-runs it. A published image once shipped a
different commit than the one it was built with while the build exited 0,
printed writing image and named its tags — so the wrapper builds to a scratch
tag, reads the commit back out of the finished image, and applies the
release tags only if it matches. A mismatch leaves the previous image holding
:latest. It also writes the build log and the exact arguments it used to
.agent/workdir/, and it never pushes.
$SHA = git rev-parse HEAD # the full 40-char commit; a short sha is refused
# Primary (cu128 — Blackwell + modern fleet). Takes the version tag and latest.
.\docker-build.ps1 -GitSha $SHA -Variant cu128 -Version 0.8.0-beta.3 `
-TokenPath <path-to-a-file-holding-a-github-token>
# Fallback (cu126 — legacy R560–R565 drivers).
.\docker-build.ps1 -GitSha $SHA -Variant cu126 -Version 0.8.0-beta.3 `
-TokenPath <path-to-a-file-holding-a-github-token>
Each variant claims its own tags, and the wrapper applies them only after the image has proven its commit — so a failed verification leaves the previous image holding them:
| Variant | Tags applied on success |
|---|---|
cu128 (default) | mastermerlin/mrln-arcane-tuner:0.8.0-beta.3 and mastermerlin/mrln-arcane-tuner:latest |
cu126 (fallback) | mastermerlin/mrln-arcane-tuner:0.8.0-beta.3-cu126 |
Add -NoCache for a build you intend to publish: the clone layer is the one
that went wrong, and rebuilding it unconditionally removes the ambiguity at the
cost of a long build. Pushing stays a separate, deliberate step:
docker push mastermerlin/mrln-arcane-tuner:0.8.0-beta.3
docker push mastermerlin/mrln-arcane-tuner:latest
docker push mastermerlin/mrln-arcane-tuner:0.8.0-beta.3-cu126
The commit must already be pushed to the remote — the build clones it, so a local-only commit fails the build rather than baking in code nobody else can retrieve.
Ollama (the optional caption-refinement sidecar) is installed by piping
ollama.com/install.sh into a root shell, which is an unpinned third-party
script executing at build time. For a build you intend to publish, pin it:
# Get the digest once, then pass both — one without the other is refused.
# The published asset is a zstd tarball; the .tgz form no longer exists upstream.
# Authoritative digests are in each release's sha256sum.txt.
curl -fsSL https://github.com/ollama/ollama/releases/download/<tag>/ollama-linux-amd64.tar.zst | sha256sum
.\docker-build.ps1 -GitSha $SHA -Variant cu128 -Version 0.8.0-beta.3 `
-TokenPath <token-file> -OllamaVersion <tag> -OllamaSha256 <digest>
--build-arg INSTALL_OLLAMA=0 skips it entirely; the app starts fine without
it and simply reports the sidecar as disabled.
The container runs as UID 10001, not root. The entrypoint starts as root
only long enough to take ownership of the mounted data volume, then drops. If
you mount a volume whose contents are owned by a different UID, files created by
an earlier root-run container may need chown -R 10001:10001 once.
GPU: any NVIDIA Ampere+ GPU, including Blackwell (RTX 50xx / RTX PRO
6000 Blackwell). The default image is built for CUDA 12.8 (cu128) and
needs an R570+ host driver — standard on current cloud hosts and mandatory
for Blackwell anyway. On a legacy host stuck on R560–R565, use the
:0.8.0-beta.3-cu126 tag instead (no Blackwell support). Avoid CUDA 13 in the
container: it needs R580+ and its forward-compat layer breaks cuBLAS on older
drivers.
Container image: mastermerlin/mrln-arcane-tuner:latest
Volume (strongly recommended): attach a network volume mounted at
/workspace. The SQLite DB, datasets/, models/, outputs/, and the
Hugging Face cache (hf-cache/) are stored there, so base models /
encoders download only once and your work survives restarts. Without a
volume the container runs but all data — including multi-GB model downloads —
is lost when the pod stops.
Expose HTTP port: add 8000 to the pod's Expose HTTP Ports field.
Environment variables:
| Variable | Purpose | Default |
|---|---|---|
MRLN_AUTH_TOKEN | Require this token to access the app. Leave it unset (or at the templates' 123) and the container generates one and prints it to the log. | generated on first boot |
MRLN_BIND_HOST | Address to serve on. The container needs 0.0.0.0 to be reachable at all; local installs default to loopback. | 0.0.0.0 (container) |
PORT | Internal port (match the exposed HTTP port). | 8000 |
MRLN_DATA_DIR | Persistence root (DB, datasets, models, outputs, HF cache). | /workspace |
HF_TOKEN | Hugging Face token — set it if you train/pull gated models (e.g. some FLUX weights). | unset |
HF_HOME | Hugging Face cache location. Auto-set to $MRLN_DATA_DIR/hf-cache so downloads persist on the volume — only override to relocate the cache. | /workspace/hf-cache |
CUDA_VISIBLE_DEVICES | Pin a specific GPU on multi-GPU pods. | all |
RunPod exposes the port at:
https://[POD_ID]-8000.proxy.runpod.net
Open it; you'll get a sign-in page — enter the token once and a cookie keeps you signed in.
⚠ Breaking change in this release: the container needs
MRLN_AUTH_TOKENEarlier versions started an unauthenticated server on
0.0.0.0whenMRLN_AUTH_TOKENwas unset. On a RunPod pod that proxy URL is public, so anyone who guessed it had full control of your datasets, models and GPU — and nothing said so.The app now refuses to start when it is bound to an address other machines can reach and no token is set:
- set
MRLN_AUTH_TOKENto a long random string, or- set
MRLN_BIND_HOST=127.0.0.1for a private, machine-local run.In the container you no longer have to do either. The entrypoint treats an absent token — and the
123the published RunPod templates ship — as no token at all: it generates a real one, saves it next to your data so restarts keep it, and prints it to the pod log as[entrypoint] ACCESS TOKEN: …. A token you set yourself is never replaced and never printed.A placeholder is worth naming for what it was: it answered this guard while giving none of the protection the guard exists for, since the pod URL is public and
123is the first string anyone tries. A pod that looks configured is worse than one that visibly refuses.Local installs are unaffected in normal use:
start_backendnow binds loopback by default instead of0.0.0.0, so it starts with no token as before. It used to publish an open server onto every network you joined, including untrusted wifi. To reach a local install from another machine, set bothMRLN_AUTH_TOKENandMRLN_BIND_HOST=0.0.0.0.
/workspace volume directly.CUDA_VISIBLE_DEVICES.8000 and (optionally) set
MRLN_AUTH_TOKEN.MRLN Arcane Tuner is a full-stack application with a FastAPI backend and an Angular frontend connected via REST API and WebSocket.
┌──────────────────────────────────────────────────────┐
│ Angular 22 SPA │
│ 43 Standalone Components · Signals · Tailwind │
└──────────────────┬────────────────┬──────────────────┘
│ REST │ WebSocket
┌──────────────────┴────────────────┴──────────────────┐
│ FastAPI Backend │
│ 9 Route Domains · Structured Logging · Middleware │
├─────────────┬────────────┬───────────┬───────────────┤
│ Dataset │ Training │ AI │ System │
│ Manager │ Engine │ Services │ Settings │
├─────────────┴────────────┴───────────┴───────────────┤
│ PyTorch · Diffusers · PEFT · SQLite │
└──────────────────────────────────────────────────────┘
For the full component and API route inventory, see docs/ARCHITECTURE.md.
The dataset pipeline is designed to get your images from raw collection to training-ready with maximum control.
Automatic directory scanning with image–caption pairing. Supports .png, .jpg, .webp images with paired .txt caption files. Incremental and full rescan modes.
The library card and workspace details footer both need one representative image, and by default that's just whatever the scanner enumerates first. A pin control (left of Adjust image in a grid tile's top-right cluster, left of Adjust in the details footer) lets you choose it instead — it's a toggle, so unpinning re-elects the automatic choice immediately. The pin is persisted (datasets.preview_pinned, PUT /datasets/{name}/preview) and survives a rescan — every scan used to overwrite the cover with the next enumerated file, which is what made pinning worth having in the first place. If the pinned file is later deleted, the next scan clears the pin and falls back rather than showing a hole. Library and workspace-grid covers are served from a sized thumbnail rendition, not the full training source.
A non-destructive adjustment pipeline, ten operations deep, that renders into a saved overlay rather than touching the source file until you explicitly Save, Bake in, or apply it to other images ("Mass edit"):
| Stage | Controls |
|---|---|
| White Balance | Temperature / tint |
| Curves | Per-channel Bézier curves (master + individual R/G/B) |
| Color & Tone | Hue rotation, saturation, contrast |
| HSL | Selective hue/saturation/lightness control per color range |
| Sharpen | Unsharp mask / kernel / high-pass, with radius, amount and threshold |
| Vignette | Amount, midpoint, feather, circular or rectangular shape |
| Lens | Barrel distortion and vertical/horizontal keystone correction, auto-crop |
| Stacked LUT | Apply .cube LUT files, tetrahedral interpolation, adjustable strength |
| Color Match | Match tone/color to a reference image (CDF or wavelet method); always applies first, not reorderable |
Three further stages run a model rather than a formula, so results can vary run to run — Denoise, Face Restore and Upscale (restoration/upscale models you supply, tile-based). Every enabled operation is individually toggleable and drag-reorderable (except Color Match). Cropping is a separate, destructive editor — it changes the image's aspect-ratio bucket, so it is not part of this pipeline.
All adjustments include a real-time canvas preview with live histogram visualization.
Resolution-aware aspect-ratio bucketing with visual crop preview. Images are automatically grouped into optimal width×height buckets (divisible by 32) matching your target training resolutions.
Bucketing modes:
Dataset version bumping invalidates latent and text embedding caches, ensuring training always uses current image state. Cache admin UI lets you inspect and purge cached data.
Tiled neural upscaling using ESRGAN and SwinIR models for images that need higher resolution before training.
Group the datasets, templates and job history for one LoRA effort so you stop re-picking the same dataset and re-configuring the same training settings for every run. A project scopes its own branched caption/mask/training/adaptive-targeting templates (independent copies of the global ones), links existing datasets without moving files, and exposes a three-step Quick Train flow for launching a run without leaving the page. Export bundles a project's templates and datasets (embed / reference / exclude, chosen per dataset) into one portable zip; the same import wizard detects whether a zip is a dataset, a template bundle or a full project. See docs/projects-guide.md.
A saved configuration you reuse instead of re-deciding it every time — a caption system prompt, masking parameters, a training config per model family, or a set of adaptive-targeting knobs. Created where you tune it (the Datasets tab's caption/masking settings, the Training screen's Template Selection and Adaptive Layer Targeting cards) and listed together on one /templates screen, Global or scoped to a project, with per-row edit / edit-JSON / branch / delete plus export and import (single template or a filtered bundle, with an import plan that flags name clashes and missing model definitions first). See docs/templates-guide.md.
Integrated AI models for automated dataset annotation, running as GPU-backed batch services:
| Model | Specialty |
|---|---|
| Florence-2 | Fast, reliable descriptions. Multiple detail levels. |
| JoyCaption Beta | 12 caption types (descriptive, prompt-style, tag lists). Extensive control options. |
| Qwen3-VL | Large vision-language model for nuanced descriptions. Configurable variant (4B/8B). |
| Youtu-VL | Tencent's vision-language model with fine-grained parameter control. |
All models support batch processing with real-time progress, custom system prompts, and per-model template management.
| Model | Approach |
|---|---|
| SAM 3 | Text-prompted segmentation (Meta's Segment Anything). Multi-mask output. |
| RemBG | Background removal with 15+ model variants (BiRefNet, ISNet, U2Net, BRIA). Alpha matting support. |
Supports batch mass-apply across entire datasets.
Training is configured through a dynamic JSON Schema-driven UI — the form auto-generates from model-family definitions, so new fields appear automatically without frontend code changes. See docs/training-guide.md for the full storyline: picking a model, attaching datasets, reading the VRAM estimate, Adaptive Layer Targeting, and every field on the form grouped as the screen groups them.
29 families, 54 shipped definitions. A family is an architecture with its own loader, driver, trainer, sampler and saver; a definition is one concrete checkpoint of it, declared in YAML. Archetypes, capability flags and the shared support packages are in ARCHITECTURE.md.
This table is generated from backend/app/engine/models/families/ and pinned by backend/tests/test_readme_family_table.py, so a family added without a row here fails the gate. It listed three of twenty-eight until 2026-08-28 — there is no text-encoder column because populating it accurately for every family means reading every loader, and twenty-five blanks would say less than no column at all.
Image — 20 families
| Family | Definitions shipped |
|---|---|
boogu_image | Boogu-Image 0.1 Base · Boogu-Image 0.1 Edit · Boogu-Image 0.1 Turbo |
chroma | Chroma1 Base · Chroma1 HD |
dreamlite | DreamLite Base · DreamLite Mobile |
ernie_image | ERNIE Image (Base 8B) |
flux1 | FLUX.1 Dev · FLUX.1 Kontext Dev · FLUX.1 Schnell |
flux2 | Flux 2 (Dev) · Flux 2 (Klein Base 4B) · Flux 2 (Klein Base 9B) |
hidream_o1 | HiDream-O1-Image (Full) |
ideogram4 | Ideogram 4 (fp8, 9.3B) |
krea2 | Krea-2 Raw · Krea-2 Turbo |
longcat_image | LongCat-Image |
lumina2 | Lumina-Image 2.0 |
microsoft_lens | Microsoft Lens (Base 3.8B) |
nucleus_image | Nucleus-Image |
omnigen2 | OmniGen2 |
ovis_image | Ovis-Image 7B |
prx | PRX 512 T2I (SFT) |
prx_pixel | PRX Pixel T2I |
qwen_image | Qwen-Image 2512 · Qwen-Image-Edit 2509 · Qwen-Image-Edit 2511 |
sdxl | Illustrious-XL v2.0 · NoobAI-XL v1.1 · Stable Diffusion XL Base |
zimage | Z-Image Base · Z-Image De-Turbo (ostris) |
Video — 8 families
| Family | Definitions shipped |
|---|---|
bernini_r | Bernini-R 14B (Video Edit, MoE) · Bernini-R 1.3B (Video Edit) |
hunyuan_video15 | HunyuanVideo 1.5 480p I2V · HunyuanVideo 1.5 480p T2V |
kandinsky5 | Kandinsky 5.0 I2V Pro SFT 5s · Kandinsky 5.0 T2V Lite SFT 5s |
ltx2 | LTX 2.3 (Lightricks) · LTX 2.5 (Lightricks) |
minimax_h3 | MiniMax H3 (text → video+audio) · MiniMax H3 (first/last frame → video+audio) · MiniMax H3 (reference → video+audio) — not selectable in this release: the family ships as a scaffold (loader, video contract, vendored model classes) and training lands in a later one, so the three definitions are gated out of the model pickers |
wan21 | WAN 2.1 I2V 14B 720P · WAN 2.1 I2V 14B 480P · WAN 2.1 T2V 14B · WAN 2.1 T2V 1.3B |
wan22 | WAN 2.2 I2V A14B (MoE) · WAN 2.2 T2V A14B (MoE) |
wan22_ti2v_5b | WAN 2.2 TI2V 5B (Dense) |
Audio — 1 family
| Family | Definitions shipped |
|---|---|
ace_step15 | ACE-Step 1.5 (Turbo) · ACE-Step 1.5 XL (Base) |
⚠️ Available but experimental. These features ship and pass the test suite, but are still being validated on real end-to-end training runs. Treat results as beta-quality and expect rough edges.
MRLN Arcane Tuner supports a wide range of optimizers, from proven defaults to cutting-edge research:
Standard (Adam-family):
Adaptive Learning Rate:
1.0.Memory-Efficient:
Second-Order:
Advanced:
| Strategy | Best For |
|---|---|
| logit_normal | Default for flow matching (Flux). Mid-range focus. |
| uniform | Equal probability baseline. |
| sigmoid | Simplified logit-normal with fixed parameters. |
| cosmap | Cosine-mapped smooth distribution. |
| mode | Configurable mid-range emphasis. |
| flux_shift | Resolution-dependent shifting. Original Flux recipe. |
| radc | Resolution-Aware Dynamic Curriculum. Progressive coarse-to-fine learning. Best for multi-phase curriculum training. |
RADC is a standout feature — it progressively shifts the noise focus from high-noise (learning structure and composition) to low-noise (refining details and textures) over the course of training, with optional resolution-aware weighting for multi-resolution datasets.
Periodically measures how much each LoRA module's effective weight is still changing (an EMA-smoothed, windowed norm) and freezes the ones that have stopped moving, so late-stage updates are confined to the modules still learning. Off by default.
Selection runs per projection group — every block's to_v is ranked against the other to_vs, never against the feed-forward projections. Raw ‖ΔW‖² is not comparable across matrix shapes: under grouped-query attention a to_v delta has an order of magnitude fewer elements than an ff.gate delta, so a single global ranking would retire the entire text-conditioning pathway on shape alone. Each group also carries its own share of the min_active_pct floor, so no pathway can be eliminated wholesale.
This is a regularizer, not a speedup — it does not reduce step time. Freezing pins a module's learned delta; it does not remove the module. The forward pass is unchanged by design: a frozen module's delta is part of the model, and dropping it would silently revert what that module learned. The backward pass still runs through every block down to the earliest module that is still active, and since the modules that keep learning are usually spread across the full depth, there is rarely a cold prefix for autograd to skip. What you gain is capacity control — fewer modules can absorb new information during the phase where a LoRA is most prone to memorizing training specifics. Note also that freezing stops further drift in a module; it never walks back drift that already happened.
Two action modes: freeze flips requires_grad in place; rebuild additionally checkpoints and relaunches the same job with the optimizer rebuilt over only the kept parameters, reclaiming optimizer-state VRAM you can then spend on batch size or resolution. In both modes every checkpoint and the exported LoRA still contain all modules.
Conservative / Balanced / Aggressive presets are fully editable and save as your own templates. The narrowing is plotted live on the training curve and summarized afterward in Training Stats, which lists the earliest still-active block per event — the number that tells you whether any backward work could have been skipped at all. Watch min_active_pct too: once the active set reaches that floor, later events are no-ops.
alpha = rank / 2.Save, load, and manage training configurations as templates per model family. Auto-save on job creation.
Full checkpoint support including LoRA weights, optimizer state, scheduler, GradScaler, EMA shadow weights, and latent/embedding cache manifests. Resume training with selective cache re-use.
See docs/jobs-guide.md for the full storyline: reading a
running job's live metrics and samples, the two-mode Resume dialog, downloading
a finished LoRA, and the cross-run Training Statistics modal.
All · 1k · 500 · 100 narrows how much of the run the chart shows; it's a view over the data, so the KPI rail (best loss, convergence) stays keyed to the whole run regardless of the window⟳) — re-reads the saved curve, including on a running job. The trainer only rewrites loss_history.json at the end of a run and flushes step_metrics every 50 steps, so a live re-read is accurate to the last flush, not to the instantAdaptive: n/m layers, next to the live chip, for runs using adaptive layer targeting<elapsed> elapsed · started <time>. Elapsed comes from the trainer, not wall-clock-minus-start: it's real run time (paused time excluded, and the offset from earlier sessions of a resumed run carried forward), so a pause doesn't inflate it and a backend restart doesn't lose itSQLite-backed persistent tracking of all training runs:
See docs/tools-guide.md for the full storyline:
Inspect's Speed Training Suggestion and per-layer norm graphs, copying a
targeted layer list, and Resize's SVD rank change with the path roots both
tools are restricted to.
Analyze any .safetensors LoRA file without loading a model:
ss_* keys (optimizer, LR, schedule, resolution, etc.)ss_tag_frequency metadataSVD-based rank change (up or down) with proportional alpha scaling. Reconstructs the effective weight delta W = B @ A, decomposes via truncated SVD, and re-factors to the target rank.
Combine Inspect results with Targeted Layer Training — inspect a reference LoRA to identify which layers learned most, then configure your next training run to target only those layers for more efficient, focused learning.
See docs/server-guide.md for the full storyline:
the health KPI rail, restart and in-app updates, Connection/Models settings,
the LLM Refine Endpoint's Save & Test, and the live, filterable Server Logs
card.
Detailed architecture documentation, including full API route inventory and component listing:
Created and maintained by master-merlin.
Repository: github.com/master-merlin/mrln-arcane-tuner — issues, contributions, and discussion are welcome there.
MRLN Arcane Tuner is licensed under the Apache License 2.0. The full text is
in LICENSE.
Third-party code vendored into this repository keeps its own licence. Those
attributions — Apache-2.0 and MIT — are listed in NOTICE, together
with the upstream each component came from.
MRLN Arcane Tuner ships no model weights. It downloads them, on your instruction, from the upstream repository named in each model definition. Those weights carry their own licences, which are not this project's licence.
Two things vary per model, and they are independent, so neither answers the other:
HF_TOKEN before a download will succeed.black-forest-labs/FLUX.1-schnell shows why the two are separate: it is
Apache-2.0 and still gated.
The application does not check, gate, or enforce any of this — it cannot know your intended use, and the agreement is between you and the model's publisher. The table below is information, not enforcement. Verified against the HuggingFace model API on 2026-08-25; upstream terms can change, so treat the model page as authoritative.
Restricted — read the terms before you train something you intend to sell:
| Family | Upstream weights | Licence | Access |
|---|---|---|---|
ideogram4 | ideogram-ai/ideogram-4-fp8 | ideogram-4-non-commercial — non-commercial | open |
flux1 | black-forest-labs/FLUX.1-dev | flux-1-dev-non-commercial-license — non-commercial | gated — you must accept the agreement on HuggingFace first |
flux2 | black-forest-labs/FLUX.2-dev | flux-non-commercial-license — non-commercial | gated |
Permissive (verified): hidream_o1
(HiDream-ai/HiDream-O1-Image,
MIT) · sdxl
(stabilityai/stable-diffusion-xl-base-1.0,
OpenRAIL++) · boogu_image
(Boogu/Boogu-Image-0.1-Base,
Apache-2.0 as tagged upstream).
Everything else — check the model page. The families here draw on roughly
fifty upstream repositories (Wan, Qwen, Kandinsky, LTX-2, ACE-Step, OmniGen2,
Lumina, Chroma, ERNIE, Z-Image, Krea, LongCat, Nucleus, Ovis, PRX, DreamLite,
HunyuanVideo, Bernini-R, Lens and others), each with its own terms, and some
are gated or regionally restricted. The upstream repository for every
model is named in its definition under
backend/app/engine/models/families/<family>/definitions/.
Two things worth being explicit about:
Some model families vendor a small amount of upstream Python (a transformer
forward, a scheduler, a cache helper) under
backend/app/engine/models/families/<family>/vendor/, because the released
diffusers does not carry that architecture. Each file names its upstream and
revision in its header.
NOTICE is the authoritative list of what is vendored and under
whose terms — it is kept in step with the tree, and duplicating it here would
only give the two copies a chance to drift apart.
Third-party dependencies installed from PyPI and npm keep their own licences; the CI gate publishes an inventory of both on every run.
9 commits
2 commits
Python
67.9%
TypeScript
29.0%
HTML
1.4%
CSS
1.1%