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 free, 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:
- The EasyInstaller is original ExpSoft C# / WPF code. It does not embed OneTrainer source.
- At Setup time it fetches OneTrainer from the upstream repository (Nerogar’s GitHub) into a
project/sub-folder, at a commit validated by ExpSoft — an update upstream never changes an existing install. The source is intact apart from the requirement files Setup adjusts — the AGPL-3.0 source lives there, alongside its LICENSE file, ready to be inspected. - At Run time, the EasyInstaller launches OneTrainer’s own Python GUI — its Qt interface,
train_ui_qt.py— as a sub-process. The user interacts directly with OneTrainer’s UI — the EasyInstaller is a launcher and a configurator, not a re-skin.
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 uv and a standalone Python instead of Miniconda
Up to version 1.1.0.5 the EasyInstaller installed Miniconda with Python 3.10. Version 1.2.0.0 replaces it with uv: Setup downloads the uv release as a zip, extracts it into runtime/uv/, and has it install a standalone CPython 3.12 into runtime/python/. No installer program runs, and --no-bin keeps uv from placing a python3.x.exe shortcut in the user profile, so nothing lands outside the app folder.
The uv version and the Python version are pinned together (uv 0.11.8, CPython 3.12.13). Each uv release ships its own CPython build, and Windows Smart App Control judges every binary on its own; this pair is the one already accepted on the ExpSoft reference PC. Python 3.12 sits inside the range the pinned OneTrainer commit supports (3.10 to below 3.14).
uv then creates the environment (uv venv --relocatable) and installs the requirements. Two adaptations were needed. uv refuses editable Git requirements, so OneTrainer’s -e git+…#egg=name lines become ordinary Git requirements pinned to the same commit. The Git-hosted packages (diffusers, mgds, Muon) are built without build isolation, because an isolated build runs a throw-away python.exe from the cache, which Smart App Control blocked on the test PC. Last, on the CUDA 13.0 install, helpers/constraints.txt — the exact versions of the 182 packages of the validated install — is applied, so a fresh install gets the same set.
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: after checking out OneTrainer (Setup Step 4, and again before the dependencies are installed), a PatchRequirements() pass rewrites the requirement files:
- Lines matching
triton-windowsortriton==are dropped fromrequirements-cuda.txtandrequirements-cuda-legacy.txt. requirements.txtis pointed at the CUDA file this PC needs: upstream’srequirements-cuda.txt(PyTorch 2.12 with CUDA 13.0), or upstream’srequirements-cuda-legacy.txt(CUDA 12.6) when the 40xx package runs on a driver older than CUDA 13. The wheel index URLs are no longer rewritten.
The ten ExpSoft 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. Upstream now pins triton-windows itself and turns compilation on in its Krea 2 and Ideogram 4 presets, and Triton compiles fresh launcher DLLs on the user’s PC at run time. The Run tab therefore writes a note in its log at each launch: with a non-ExpSoft preset, a TritonMissing error means turning off Compile transformer blocks in OneTrainer’s Model tab.
Two GPU packages — one C# source, the CUDA build chosen at Setup
Version 1.2.0.0 ships in two packages, each a separate ZIP:
- 40xx — for RTX 20xx, 30xx and 40xx.
- 50xx — for RTX 50xx (Blackwell).
The CPU-only package of earlier versions is discontinued.
Both install the PyTorch build the pinned OneTrainer commit asks for: PyTorch 2.12 with CUDA 13.0. CUDA 13 needs a recent NVIDIA driver, so the GPU detection step reads the CUDA version the driver reports through nvidia-smi. On the 40xx package, a driver below CUDA 13 switches Setup to upstream’s CUDA 12.6 requirement file. The 50xx package never takes that fallback — CUDA 12.6 has no Blackwell kernels — and asks for a driver update instead.
The split lives in the C# csproj as separate configurations: Release-50xx defines GPU_50XX, and an #if-gated TargetSeries constant in SetupService.cs tells Setup which package it is. The GPU detection step also compares the card with the package and warns on a mismatch. RTX 50 cards are recognised by their model number or a “Blackwell” name, so an RTX 3050 or 4050 is not mistaken for one.
The executable itself is a framework-dependent single file for .NET 8 (about 1.8 MB): it needs the .NET 8 Desktop Runtime, which Windows offers to download the first time the app starts.
Profile regeneration on every launch — inside OneTrainer’s preset convention
OneTrainer keeps its presets in training_presets/, one folder per model family, and its Qt interface lists only files whose name starts with # — Nerogar’s built-in presets, such as Flux 2/#flux2 LoRA 16GB.json. That is why the 1.1 profiles (ExpSoft_*.json at the root of the folder) no longer appeared once OneTrainer moved to its Qt UI. Version 1.2.0.0 writes its ten profiles as training_presets/ExpSoft/#ExpSoft <name>.json, so they show up as a group of their own, Load Preset › ExpSoft:
| Profile | Model | Upstream preset | Resolution | Batch | Offload |
|---|---|---|---|---|---|
#ExpSoft Klein 9B LoRA 16GB | Klein 9B | Flux 2 · 16GB | 512 | 2 | preset |
#ExpSoft Klein 9B LoRA 8GB | Klein 9B | Flux 2 · 8GB | 512 | 1 | 0.7 transformer + 0.7 text encoder |
#ExpSoft Klein 9B LoRA 1024 | Klein 9B | Flux 2 · 16GB | 1024 | 1 | 0.5 transformer |
#ExpSoft Klein 4B LoRA 16GB | Klein 4B | Flux 2 · 16GB | 512 | 4 | preset |
#ExpSoft Klein 4B LoRA 8GB | Klein 4B | Flux 2 · 8GB | 512 | 2 | 0.5 transformer + 0.5 text encoder |
#ExpSoft Klein 4B LoRA 1024 | Klein 4B | Flux 2 · 16GB | 1024 | 2 | 0.3 transformer |
#ExpSoft Klein 4B LoRA 1536 | Klein 4B | Flux 2 · 16GB | 1536 | 1 | 0.6 transformer |
#ExpSoft Krea 2 LoRA 16GB | Krea 2 Raw | Krea 2 · 16GB | 512 | 2 | 0.3 (preset) |
#ExpSoft Krea 2 LoRA 1024 | Krea 2 Raw | Krea 2 · 16GB | 1024 | 1 | 0.9 transformer |
#ExpSoft Ideogram 4 LoRA 512 | Ideogram 4 | Ideogram 4 · 16GB | 512 | 2 | 0.5 transformer |
Each profile is OneTrainer’s own preset for its model, read from the pinned checkout, with ExpSoft overrides on top: the model (the local folder when it was downloaded through the Settings tab, otherwise the Hugging Face repository id), resolution, batch size, per-component offload with activation offloading and gradient checkpointing, compile = false (the Triton-out posture from the previous section) and the absolute paths of the install. Learning rate, rank, precision and quantisation stay those of the upstream preset, with one exception. On Ideogram 4, the upstream strength stayed stable but did not learn the test subject in 600 steps, so the ExpSoft profile uses a learning rate of 1e-4 with rank 64 / alpha 64 over 60 epochs, and leaves out Ideogram’s unconditional transformer, which only serves the negative branch of the sample previews. Offload keeps a fraction of a component’s layers in system RAM: on the 16 GB test card, Krea 2 at 1024 px with the preset’s 0.3 offload spilled into shared memory (42 s per step), while 0.9 plus activation offloading ran at about 5 s per step.
The non-obvious decision is that these ten profiles are regenerated from scratch on every launch of OneTrainer from the Run tab, rather than being saved once at Setup time. Three reasons:
- Drive-letter changes. If the install folder is on
F:today andG: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. - OneTrainer schema migrations. OneTrainer loads its built-in presets without config migration, so they must already use the current schema — since config version 11, offload and gradient checkpointing are set per component. Building every ExpSoft profile from the upstream preset of the pinned checkout keeps it on the schema that OneTrainer version expects. No “schema version mismatch” errors at load time.
- Strict scope.
ProfileServicewrites only#ExpSoft *.jsonfiles insidetraining_presets/ExpSoft/, and removes those a version no longer ships (plus, once, the root-levelExpSoft_*.jsonfiles of 1.1). Nerogar’s presets are read but never written.concepts.jsonis created once and never rewritten, so the user’s concepts stay theirs. Edits made directly to an ExpSoft profile are overwritten at the next launch; OneTrainer’s Save config keeps them in a file of their own.
Portable venv — relocatable from the start, patched on every launch
Setup creates the environment with uv venv --relocatable. A few files still record absolute paths: pyvenv.cfg (its home line points at runtime/python/cpython-…), the text files of Scripts/, every .pth and .egg-link file in site-packages/, and every direct_url.json in .dist-info/ folders. If the install folder moves (different drive letter, different parent path), those need to follow.
PathService.AutoPatchVenvPaths() runs at every launch from the Run tab. It reads the home line of pyvenv.cfg, derives the app root the venv was created under, compares it to the current AppContext.BaseDirectory, and if they differ replaces the old root with the new one in those files, in both backslash and forward-slash form, skipping .exe, .dll and .pyd binaries. The patch is idempotent — running it on an already-correct venv is a no-op.
End result: move the install folder to another drive, or copy it to another Windows PC with the .NET 8 Desktop Runtime installed, 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 a samples/<n - prompt>/ folder of the training’s workspace. Each ExpSoft profile has its own workspace (workspace/<profile>/), so every training gets its own samples folder. By default they’re invisible unless you go looking. The EasyInstaller adds a sixth tab — Samples — that watches the whole workspace/ folder via a FileSystemWatcher with IncludeSubdirectories, keeps every image (PNG, JPEG, BMP, WebP) found under a samples folder, labels it with its training and prompt, and presents new samples in a WrapPanel gallery as they arrive, newest first.
A few small implementation details that pay back:
- The thumbnails use
BitmapImagedecoded from aFileStream(not from aUriSource), so the file handle is closed immediately after decode. The user can move, rename or delete the sample file without WPF holding a lock on it. DecodePixelWidth = 256on the decode side. On a long training run that produces 500 samples, that’s the difference between a few hundred MB of thumbnail RAM and a few GB.Task.Delay(500)between theFileSystemWatcherevent and the actual decode. The event fires before the file finishes writing; the brief delay lets the writer close it cleanly.- Click a thumbnail and a full-screen lightbox opens with a translucent overlay; click anywhere to close. Two lines of XAML, big quality-of-life delta.
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 stops OneTrainer, ending its whole process tree. A deliberate stop 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:
- OneTrainer — Nerogar, AGPL-3.0. The training engine, the GUI, the support for two-dozen architectures, the active maintenance. All of that is upstream work. The EasyInstaller is a launcher, not a fork.
- PyTorch — Meta + community, BSD-3-Clause.
- uv — Astral, MIT / Apache-2.0 (Python and package manager); CPython itself is PSF License.
- Portable Git for Windows — GPL-2.0.
- customtkinter — MIT. OneTrainer’s original GUI toolkit.
- PySide6 — Qt for Python, LGPL-3.0. OneTrainer’s training UI.
- transformers + diffusers + huggingface_hub — HuggingFace, Apache 2.0.
- Microsoft.Web.WebView2 — Microsoft, distribution permitted.
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
- For AGPL-3.0 upstream dependencies, an independent launcher that
git clone’s the upstream source at Setup time and runs it as a sub-process is the cleanest legal posture. The upstream stays itself; the wrapper stays separate. - Patching out a known-problematic Windows-specific dependency at requirements time is more honest than “try to make it work and hope.” Document the trade-off in the disclaimer and move on.
- Pin the upstream itself, not just its wheels: a fixed OneTrainer commit plus a frozen package list is what keeps a fresh install reproducible months later. Within that, a small driver check at Setup — CUDA 13.0, or upstream’s CUDA 12.6 file on older drivers — is all the auto-detection needed.
- Regenerate config files at every launch when they encode absolute paths and upstream schema versions. Don’t persist what you can derive — and never regenerate what the user edits (
concepts.jsonis now written once).