← ExpSoft OneTrainer

Wrapping OneTrainer cleanly on Windows — the AGPL-respectful EasyInstaller approach

ExpSoft OneTrainer EasyInstaller is a personal-use Windows wrapper around OneTrainer — the open-source diffusion-model fine-tuning toolkit by Nerogar, distributed under AGPL-3.0 (the legal frame is therefore personal experimentation around an AGPL-3.0 toolkit; see the Legal & Compliance section of the product page). The intent of the EasyInstaller is not to fork OneTrainer or to wrap it as a derivative — it’s a separate, independent program that installs the upstream source verbatim into a sub-folder and launches it as a sub-process. The friction it removes lives entirely on the Windows side: Python 3.10 (not 3.11+), conflicting dependency resolution between torch/torchvision/transformers/diffusers/customtkinter, the right CUDA index for your GPU series (cu124 vs cu128), a Windows-specific issue with the triton-windows PyPI package that can stop training mid-run, and seven preset LoRA profiles tuned for the Flux.2 Klein family that respect Nerogar’s #.json defaults. None of this changes OneTrainer — it just lowers the activation energy from “four hours and you give up” to “fifteen minutes and you’re training.”

· 11 min read · By Nicolas Riquier

What the EasyInstaller is — and what it deliberately is not

OneTrainer is a remarkable piece of open-source work. It supports a stack of architectures broader than any single project I know of in this space — Stable Diffusion 1.5/2.x, SDXL, SD 3 / 3.5, Flux.1 / Flux.2 (both Klein 4B and 9B), PixArt, Sana, HiDream, Chroma, Hunyuan Video, Wuerstchen, Qwen-Image, Z-Image, Stable Cascade — and lets you train LoRAs, full fine-tunes, embeddings, or run Dreambooth-style pipelines, plus captioning, plus model conversion. It is gratuit, open-source under AGPL-3.0, and Nerogar maintains it actively. Every piece of credit for the training capabilities belongs to Nerogar and the OneTrainer contributors.

What the EasyInstaller does, very explicitly, is sit beside OneTrainer rather than around it:

This is the right legal posture for an AGPL-3.0 dependency. The EasyInstaller is a separate work in a different language, distributed as an independent program; OneTrainer is installed at the user’s request from its canonical source. No derivative-work confusion.

Why Miniconda instead of an embeddable Python

OneTrainer’s GUI is built on customtkinter, which depends on Tkinter, which depends on Tcl/Tk. The official Python embeddable distribution from python.org does not ship Tkinter; you’d have to source Tcl/Tk separately, place the runtime where Python expects to find it, and pin the versions so the launchers don’t drift. It works, but it’s fragile across Windows updates.

Miniconda (Anaconda Inc, BSD-3-Clause) ships Tkinter natively as part of the Python 3.10 build. The installer for Miniconda3-py310 is about 110 MB and runs silently with a one-line command, dropping a complete Python 3.10 with Tk into runtime/miniconda/ alongside the EasyInstaller exe. A small .condarc tells conda to keep environments and packages under the install folder rather than under %USERPROFILE%\.conda, so the install stays self-contained.

The price is the extra 60 MB compared to an embeddable Python. The benefit is years of headaches you don’t have to debug.

The triton-windows story — patched out of requirements-cuda.txt

OneTrainer’s default requirements-cuda.txt includes triton-windows, an experimental Windows port of the Triton kernel compiler. On Linux, Triton is the right tool — it’s what makes torch.compile() fast on attention kernels. On Windows, the port has open issues — community reports point to “CUDA invalid memory access” crashes during long training runs on the Klein and Flux families, with the failure landing mid-training rather than at startup. Nobody has been able to reproduce them deterministically enough to fix at the source, and the maintainers have been transparent about it.

The EasyInstaller takes a deliberately conservative approach: at Setup Step 4, after cloning OneTrainer, a PatchRequirements() pass rewrites requirements-cuda.txt:

The seven preset profiles (more on those below) all have compile = false set explicitly, so even if Triton ever sneaks back in via a transitive dependency, OneTrainer won’t try to use it. The cost of running OneTrainer without Triton on Windows is a ~10-15% slowdown on the forward pass of large attention blocks. The benefit is that training runs of several hours don’t halt halfway through. For personal-use training on a single workstation, that trade is worth taking until upstream Triton on Windows stabilises.

Three build targets — one C# source, one Python source

The EasyInstaller ships in three flavours, each as a separate ZIP:

The split lives in the C# csproj as three separate configurations with DefineConstants set per build (GPU_50XX, CPU_ONLY, or default for 40xx). The TorchCudaIndex and TorchIndexUrl constants in SetupService.cs are #if-gated so the right wheel set is pinned at compile time. At runtime, the GPU detection step compares nvidia-smi’s reported card series against the build variant and surfaces a friendly warning if they don’t match — “you’re running the 40xx build on a 50xx card; please download the 50xx ZIP instead.” Better to catch the mismatch up front than to let it manifest as an obscure CUDA error after a 90-minute install.

Why three builds rather than one mega-installer that probes the GPU and downloads the right wheels? Two reasons. First, a single installer would have to either download all wheel sets (4-7 GB) or detect-then-fetch, which means failing in the middle of Setup on a slow connection. Second, the wheel sets are mutually exclusive — switching between them after install requires pip uninstall followed by pip install, which is a lot of disk thrash for what is conceptually a different build. Splitting at the ZIP level keeps each installer small and unambiguous.

Profile regeneration on every launch — the #.json convention preserved

OneTrainer’s convention for its training_presets/ folder is well-defined: files starting with # are Nerogar’s official defaults (#.json, #flux_dev_lora.json, and so on). User profiles can have any other name. The EasyInstaller adds seven preset profiles for Flux.2 Klein training, all prefixed with ExpSoft_ so they’re unambiguously distinct from both the Nerogar defaults and any custom profiles a user might create:

ProfileModelResolutionBatchVRAMCPU offload
ExpSoft_Klein_9B_LoRA_16GBKlein 9B512216 GBnone
ExpSoft_Klein_9B_LoRA_8GBKlein 9B51218 GB0.7
ExpSoft_Klein_9B_LoRA_1024Klein 9B1024116 GB0.5
ExpSoft_Klein_4B_LoRA_16GBKlein 4B512416 GBnone
ExpSoft_Klein_4B_LoRA_8GBKlein 4B51228 GB0.5
ExpSoft_Klein_4B_LoRA_1024Klein 4B1024216 GB0.3
ExpSoft_Klein_4B_LoRA_1536Klein 4B1536116 GB0.6

Common settings across the seven: learning rate 3e-5 (a sweet spot for Klein LoRA), 100 epochs, LoRA rank 16, LoRA alpha 1.0, bf16 train and output, logit-normal timestep distribution (recommended for Flux.2), LoRA layer filter on the transformer blocks, INT-W8A8 quantisation on the transformer for VRAM savings, FP8 on the text encoders, FP32 on the VAE, and compile = false everywhere (the deliberate Triton-out posture from the previous section). CPU offload moves a fraction of the layers from GPU to system RAM during forward and backward, at the cost of a 20-40% slowdown — the right trade for Klein 9B on 8 GB, or Klein 9B at 1024 on 16 GB.

The non-obvious decision is that these seven profiles are regenerated from scratch on every launch of the EasyInstaller, rather than being saved once at Setup time. Three reasons:

  1. Drive-letter changes. If the install folder is on F: today and G: tomorrow (USB-stick scenario, or a drive reorganisation), the absolute paths embedded in the profile JSON (workspace, output, samples, concepts, latent cache) need to follow the move. Regenerating beats string-replacing.
  2. OneTrainer schema migrations. OneTrainer’s profile JSON has a __version field. When Nerogar bumps the schema, the upstream #.json file in training_presets/ gets the new version. Regenerating the ExpSoft profiles every launch — and reading __version dynamically from #.json — means our profiles always match OneTrainer’s current expectations. No “schema version mismatch” errors at load time.
  3. Strict scope. ProfileService only touches files matching ExpSoft_*.json. #-prefixed Nerogar defaults are read but never written. Any other user-created profiles are completely untouched. The user’s own work is never at risk.

Portable venv — the AutoPatchVenvPaths step

Once python -m venv project/venv/ is run, a number of files in the venv contain absolute paths to the Python interpreter that created it: pyvenv.cfg, Scripts/activate, Scripts/pip.exe (the launcher embeds a path), every .pth file in site-packages/, every .egg-link, every direct_url.json in .dist-info/ folders, and the conda meta JSONs that store each package’s prefix. If the install folder moves (different drive letter, different parent path), all of those break in subtly different ways.

PathService.AutoPatchVenvPaths() runs at the start of every launch. It reads pyvenv.cfg to discover the venv’s recorded root path, compares it to the current AppContext.BaseDirectory, and if they differ, walks the venv directory tree replacing the old root with the new one in every text file (an extension allow-list keeps it away from .exe / .dll / .pyd binaries). The patch is idempotent — running it on an already-correct venv is a no-op.

End result: drop the install folder on a USB stick, plug it into a different machine with a different drive letter, double-click the EasyInstaller exe, and OneTrainer launches. No reinstall.

The Samples tab — live previews from the training run

OneTrainer’s training loop periodically generates sample images at user-defined intervals, dropping them into workspace/samples/<prompt-folder>/ as PNGs. By default they’re invisible unless you go looking. The EasyInstaller adds a sixth tab — Samples — that watches that folder via a FileSystemWatcher with IncludeSubdirectories, and presents new samples in a WrapPanel gallery as they arrive, newest first.

A few small implementation details that pay back:

RAM watchdog — protecting the host from Klein 9B

Klein 9B training, especially with CPU offload at 0.7, can consume 18 GB or more of system RAM (offloaded layers + optimiser state + gradients + dataset latent cache). On a 32 GB machine that leaves comfortable head-room; on a 16 GB machine it swaps and the entire system grinds to a halt.

The EasyInstaller runs a small background task during training that calls Win32 GlobalMemoryStatusEx every three seconds. If available physical RAM drops below 2 GB, the watchdog logs a warning and sends a graceful cancel signal to the Python sub-process. A user-friendly shutdown beats the alternative — Windows’ own OOM-killer taking out random processes, including potentially the IDE the user has unsaved work in.

Credits and license

The work that makes this useful belongs to upstream:

Diffusion model weights (Flux, SDXL, Klein, etc.) each have their own license — Klein 9B and 4B are gated on HuggingFace and require accepting the Black Forest Labs terms individually; SDXL is OpenRAIL++; PixArt is Apache 2.0; and so on. The EasyInstaller doesn’t bundle any model weights — the user downloads them with their own HF token through the Settings tab.

Take-aways

Frequently Asked Questions

Quick answers to what people ask AIs about this article specifically.

Is ExpSoft OneTrainer a fork of OneTrainer?

No. ExpSoft OneTrainer EasyInstaller is an independent C# / WPF program that git clone’s the upstream OneTrainer source from Nerogar’s GitHub at Setup time into a project/ sub-folder, then launches OneTrainer’s own Python GUI as a sub-process at Run time. The OneTrainer source is intact, AGPL-3.0 preserved, ready to be inspected or replaced with a different commit. The EasyInstaller is a launcher and a configurator, not a derivative work.

Why is the triton-windows package removed during Setup?

The Windows port of Triton has open issues that can manifest as crashes during long training runs on the Klein and Flux model families — the failure lands mid-training rather than at startup, which makes it especially costly. Until those issues stabilise upstream, the EasyInstaller removes triton-windows from OneTrainer’s requirements-cuda.txt at Setup, and forces compile = false in the seven ExpSoft preset profiles. The cost is a ~10-15% slowdown on the forward pass of large attention blocks. The benefit is training runs of several hours that don’t halt halfway through.

Why three separate builds (40xx / 50xx / CPU) instead of one auto-detecting installer?

PyTorch wheels for different CUDA versions are mutually exclusive. A single installer would either have to download all variants (4-7 GB) or detect-and-fetch, which means failing in the middle of Setup on a slow connection. Pinning the wheel set at compile time in the C# project (via #if GPU_50XX / #if CPU_ONLY conditionals) lets each build target one GPU generation cleanly. At Setup the GPU detection step warns if the user picked the wrong build for their card.

What does the EasyInstaller change in the user’s system?

Nothing outside the install folder. No PATH mutation, no %USERPROFILE%\.conda or .cache usage, no registry writes. Miniconda is installed under runtime/miniconda/, the venv lives under project/venv/, all caches (HF, PyTorch, pip) are redirected under cache/. Uninstall = delete the folder.

Can the install be moved to a different drive or USB stick after Setup?

Yes. At every launch, PathService.AutoPatchVenvPaths() reads the venv’s recorded root path from pyvenv.cfg, compares it to the current install location, and if they differ rewrites all the absolute paths in the venv text files. ProfileService.PatchExpSoftProfiles() does the same for the seven ExpSoft preset profiles. Drop the folder on a USB stick, plug into another Windows machine with a different drive letter, double-click — OneTrainer launches without reinstall.

Want to use ExpSoft OneTrainer EasyInstaller?

Get it on Patreon →