Every setting in LoRA Dataset Studio, explained: what it does, its default, when to change it, and the traps to avoid.
Open Settings from the top nav. Each rail entry on the left is a section (Overview, Image engines, Scraping & sources, Local tools, Captioning & quality, Training, Server & access, Maintenance); its little LED shows live health at a glance — green when the section is fully configured, amber when it's partly set up, off when nothing is configured yet.
A few things hold true everywhere:
- Ordinary fields wait for Save. Change a field and a floating Unsaved changes bar appears with Save and Discard. Navigate away with changes pending and they're kept in the bar, not written. The optional Usage statistics choice is separate: its buttons save immediately, including when you turn sharing off.
-
Where values live. Ordinary settings are written to
config.json(git-ignored, in your data directory). Secrets — API keys and tokens — go to a separate.envfile and are never written toconfig.jsonor committed. - Secret fields are write-only. An API-key box is always blank, even when a key is saved (a ✓ Configured badge tells you it's there). Typing a new value replaces the old one; leaving a field blank never erases a saved key — that would be too easy to do by accident. To actually remove a key, use its Remove button.
- Test buttons probe what's saved, not what's typed. Hitting Test first persists whatever you've typed, then tests the saved setting end-to-end. So a Test result always reflects the value the app will really use.
-
Server changes need a restart. Host, port and the access token only take effect when the server process starts (with
LDS_REQUIRE_LOCAL_TOKEN=1, a token saved in Settings applies at once). Those fields show a Running vs Saved comparison and a Save & restart to apply button. Everything else — including scraping credentials — applies immediately, no restart. - Every field can go back to its shipped value. Change a number, a path, a dropdown or the enabled-engine list and a small ↺ Reset to default button appears right under it; it disappears again once the field is back where it started, so a button you can see is always a button that does something. The value it restores is read from the server, not from a copy inside the page — so if a shipped default changes in a later release, Reset gives you the new default rather than the one your version was built with. On the fields that mean "work it out yourself" when left blank (the engine model slugs, the Krea base model, the dataset images root), Reset empties the box rather than typing today's answer in, which is how you keep following future improvements instead of freezing one. Nothing is written until you Save, same as any other edit.
- Search finds settings, not just sections. The search box at the top of the page matches both section names and individual settings, so typing "budget" or "vision model" jumps you straight to the right field.
For containerized or scripted setups, a handful of environment variables override paths and binds before config.json is even read. You rarely need these — the UI covers the normal cases.
| Variable | Overrides |
|---|---|
LDS_DATA_DIR |
Runtime data directory (where config.json, datasets and trash live). |
LDS_CONFIG |
Path to config.json. |
LDS_ENV |
Path to the .env secrets file. |
LDS_HOST |
Bind host — takes priority over server.host. |
LDS_PUBLIC |
Set to 1 when the app is served on a URL the public internet can reach (a rented GPU box's proxy hostname, a tunnel). Forces the access-token gate on whatever server.require_token says, and makes the launcher generate a token if none exists. Only affects non-loopback binds. |
LDS_REQUIRE_LOCAL_TOKEN |
Set to 1 to require the access token for local connections as well as network clients, regardless of server.require_token or LDS_ALLOW_UNAUTHENTICATED. Only loopback GET/HEAD /api/health stays public for supervision. The launcher generates a token if needed and authenticates the browser tab it opens. Restart after enabling this in .env. |
LDS_ALLOW_UNAUTHENTICATED |
Set to 1 to deliberately opt out of the token gate — for setups that already authenticate the connection themselves (a VPN, a reverse proxy that requires its own login, a trusted Docker network). Overrides LDS_PUBLIC, but cannot disable LDS_REQUIRE_LOCAL_TOKEN. |
LDS_EXTENSIONS |
Set to 0 to disable the local extension loader entirely — no package under the extensions directory is imported. See the Extensions guide. |
LDS_EXTENSIONS_DIR |
Directory the extension loader scans (default backend/extensions/, which is gitignored and never ships). Mostly for tests and unusual layouts. |
FLASK_DEBUG |
1 enables Flask debug mode. |
The Overview section has no settings of its own. Its capabilities grid shows the shared tools that LDS can currently use. Optional product controls and their preparation belong to each plugin's Settings button in Plugins.
The Plugins tab combines browsing and managing plugins on the same cards. Use All, Installed or Updates to filter them. Installed cards keep their controls even when the store is offline, including plugins absent from the catalog. To uninstall one, open More actions → Remove plugin on its card. Purchases is a separate tab for licenses.
Every row is a link to the control that turns that capability on, not just to the right screen: picking OpenRouter lands on the OpenRouter key field with it scrolled to and highlighted; picking Person masks opens the Setup wizard step that installs it. Use the grid as your first stop to answer "why is this feature greyed out?" — the answer is one click away on the row itself.
A row marked ◐ in amber is not broken: the tool is installed, it just isn't running (typically launch ComfyUI to enable for Klein, the Test Studio and the video rows whose verdict needs ComfyUI up). Those rows lead to the ComfyUI API URL field and its Test button rather than to an install you have already done. The counter at the top reads how many rows are ready plus, when it applies, how many are waiting on a process.
If nothing on the grid tells you where to start, the line at the bottom opens the Setup wizard, which scans the machine and installs what it can.
This section controls the shared local image engines, Klein and Krea 2 Edit. Configure ComfyUI under Local tools. Additional engines appear when their plugin is active; their credentials and provider options live on that plugin's settings page.
Test Image / Studio supports all seven image training families: Z-Image, SDXL, Krea 2, FLUX.1, FLUX.2 Klein, Anima and Qwen-Image 2.1. The Trained image models group prepares FLUX.1, Anima and Qwen 2.1 using the same model selection that generation checks before launch.
Each family needs its own diffusion model, text encoder(s) and VAE. Leave a file field blank to detect compatible installed files, including shared ComfyUI model roots. Test Studio also offers Download buttons beside missing models, with file sizes, progress and a check after installation, so you can prepare the files without leaving your test. Choose Install in Settings; existing valid files are reused. Downloads are explicit and show progress and cancellation. Save file selections before Check models again. A missing or damaged custom selection must be corrected or cleared; downloading the recommended model does not silently change that selection.
| Setting | Purpose |
|---|---|
studio_models.<family>.diffusion_model |
Optional ComfyUI-relative base model name; <family> is flux, anima or qwenimage21
|
studio_models.<family>.text_encoder |
Optional compatible encoder selection |
studio_models.flux.text_encoder_2 |
Optional FLUX.1 CLIP-L selection, in addition to T5 |
studio_models.<family>.vae |
Optional compatible VAE selection |
Qwen-Image 2.1 uses Qwen3-VL 8B and the dedicated 2.1 VAE. Its older Qwen Image/Edit counterparts are incompatible. If native nodes are missing, choose Fix missing ComfyUI nodes in Studio or this Settings group. The repair panel provides update steps for Windows portable, Desktop and manual/server installations, links to ComfyUI and the official downloads, and identifies custom node packages when a test requires them. Finish active jobs before updating or restarting. Preserve local changes in customized installations; the panel also links instructions for sharing existing models with a separate installation. After restarting, use Check nodes again to query the running ComfyUI afresh. A failed connection or a still-missing node keeps the repair open; your prompt and checkpoint choices remain in place. LDS guides the update; it does not run ComfyUI's updater or restart an external process for you. LDS checks files and node availability separately and cannot report readiness while ComfyUI is unreachable. Sampler defaults follow the selected family; FLUX.1's guidance control is distilled guidance, while its sampler CFG stays at 1.
-
Default engine →
engines.default. The engine preselected in the workspace. A fresh core installation uses Klein; available choices follow the active engine catalog. -
Enabled engines →
engines.enabled. Choose which available engines appear in generation controls. An inactive plugin's engines are hidden, while stored choices are preserved for when that plugin is enabled again.
In the workspace the engine cards are checkboxes, not a one-of-five choice: tick as many as you want. Each engine has its own colour (Klein indigo, Krea 2 Edit violet, Nano Banana amber, ChatGPT sky, OpenRouter fuchsia) and every generated tile is labelled with the engine that made it, so a mixed batch stays readable.
From two engines on, a mode appears deciding what "several engines" means:
| Mode | What it does | Images produced |
|---|---|---|
| Split across engines (default) | Each selected shot goes to one engine, round-robin. | Same as one engine — 25 shots stay 25 images. |
| All engines | Every engine renders every shot. | Multiplied — 25 shots on 3 engines = 75 images. |
Split gives you a more varied dataset for the price you already pay; All engines is for comparing engines on identical shots before keeping the best. The image count and estimated cost update live as you tick, and the Generate button carries the real total.
Good to know:
- The cost shown is the whole run's. The two local engines contribute nothing (it's your GPU) and neither does ChatGPT on the subscription lane.
- The local engines run last and in series. The API batches start immediately in the background; the local GPU handles its own shots one at a time behind them. The Klein and Krea cards say so while a mixed run is being set up.
- API batches have no fixed image-count limit. Select as many shots as needed in Split or All mode. Local images respect the configurable Local generation queue limit in Settings → Local tools → ComfyUI, including in a mixed run; API images do not count towards that local budget.
- 🔞 NSFW shots stay local-only. The uncensored catalog unlocks only when every ticked engine is local (Klein, Krea 2 Edit, or both), because those shots must never reach a third-party API.
- Regenerating one tile (🔄) uses the first ticked engine, not all of them.
The second local engine. Where Klein restages your reference with a general instruction-edit model, Krea 2 Identity Edit is trained specifically to keep an identity: from a single reference photo it holds the face, the body and the permanent markings while changing the angle, framing, light, background and clothes — with no character LoRA. That is what makes it useful before a LoRA exists, which is the whole point of building a dataset.
It is not installed by the app. It needs, inside your own ComfyUI:
- the comfyui-krea2edit custom-node pack in
custom_nodes/(no Python dependencies), then a ComfyUI restart; - a Krea 2 Raw or Turbo base model under a
krea-named folder inmodels/diffusion_models(ormodels/unet) — from Comfy-Org/Krea-2 ▸ diffusion_models (public, no account needed;krea2_turbo_fp8_scaled.safetensorsis the usual pick); - the Krea 2 Identity Edit LoRA in
models/loras— from Civitai; - the Qwen3-VL 4B text encoder in
models/text_encodersand the Qwen Image VAE inmodels/vae— both from the same Comfy-Org/Krea-2 repo, undertext_encoders/andvae/. Keep the filenames as published:qwen3vl_4b_fp8_scaled.safetensorsandqwen_image_vae.safetensors. The other Qwen encoders (qwen_2.5_vl_*,qwen_3_8b_*) belong to different models and are deliberately never picked up here.
The engine card in the workspace names whichever of these is still missing, one actionable line at a time, and the app never guesses a download URL for weights it cannot verify. Every path above is found by searching your ComfyUI model roots — including any extra_model_paths.yaml roots — so a non-standard layout works untouched.
Settings. The four calibration dials below (reference grounding, sampler steps, reference pull, identity LoRA strength) are editable both here and in the 🧬 Krea 2 Edit tuning panel of the workspace's Generate variations screen — same key, same endpoint, one value: whichever slider you move, the other screen shows the new number and every future Krea run uses it. The two file-path fields are on this page only, because they are filled once at install rather than adjusted while judging an image.
-
Reference grounding →
krea.grounding_px. Range512–1536, default1024. The dial of this engine: the resolution your reference is shown to the model's vision encoder at. At the low end it follows the shot description (more variety in pose, outfit and scene, looser likeness); higher values favor reference likeness and can copy the very pose and outfit you asked it to change. 1024 is the identity-first profile shipped since the v4 calibration, paired with reference pull4.0— the two always ship together. Lower it to512when you want variety more than likeness: that was the v3 default, and it keeps the prompt and the selected catalog card more firmly in charge. This is Krea-only: it does not change ChatGPT, Gemini/Nano Banana, OpenRouter, or Klein. -
Sampler steps →
krea.steps. Range1–50, default12. This base is guidance-distilled, so more steps mostly buy waiting;8— the value the model's own reference workflow uses, and the v3 default — remains a perfectly good choice and renders faster.12ships because it is the value the v4 calibration was measured at, not because 8 was found wanting. (This line said "default 10" for a while, then "8"; the shipped default is now 12.)
A second sampling pass at a higher latent resolution on the Krea generation graph — the Test Studio grid and the LoRA Canvas (not the Krea 2 Edit engine above, which is a different graph). Krea composes at the resolution it samples: asked for the final size in one pass it loses framing and starts duplicating subjects; kept small it caps the detail no upscaler can invent afterwards. Sampling small, upscaling the latent and re-sampling lets the model draw that detail instead. Core ComfyUI nodes only — nothing to install. These are the defaults; the Studio's Sampling section can override the factor and the rewrite per run (its "Settings default" option names what is set here), and a cell keeps its own values so a 🔄 resume renders what it replaces.
-
Second pass →
krea_hires.scale.1.0= off (default) — and off adds no node at all, so the graph is exactly what it was before this setting existed.1.25–2.0= the latent upscale factor between the two passes;1.5is the value of the reference workflow this was ported from. ×2 on the latent is ×4 the pixels, and the second pass pays that in time and VRAM. -
How much it may rewrite →
krea_hires.denoise. Range0.05–1, default0.5. The dial of the feature: near1the second pass ignores the first and renders a different picture at the larger size; too low and it costs a full pass to change nothing.0.5keeps the composition and rewrites the texture. -
Second-pass steps →
krea_hires.steps. Default0= inherit the first pass's count, which is the sane default (at a denoise below 1 the pass only walks part of the schedule anyway). Read whenever the pass runs — including a run that armed it from the Studio while this switch is off. -
Base model file →
krea.base_model. This is the GENERATION setting only — the checkpoint ComfyUI loads for Krea 2 Identity Edit. It has nothing to do with LoRA training, which never reads it: training pulls its base from Hugging Face and picks it from the Krea 2 training base dropdown in the training panel (Raw, the default and the official recommendation — you train on Raw and apply the LoRA on Turbo at inference). Nobody can accidentally train on Turbo by leaving this field alone. (The naming confusion was raised by strouder, GitHub #19.) Blank (default) = the app picks a Krea 2 Turbo then Raw build from your ComfyUI. Set it only if you own several. A bare filename is enough — it is matched against everykrea-named folder under your diffusion-model roots, at any depth, so a build filed inunet/Krea/archive/resolves like one sitting directly inunet/Krea/. (Until 2026-08-22 that search stopped one level down, so a deeper file was reported as "not on disk" and the engine refused to run.) When two files share a name, the shallower one wins. Checkpoints that merely carry "krea" in their name but are not Krea 2 bases are skipped on purpose — the identity LoRA renders pure noise on them, which looks like a broken app rather than a wrong file. The field is a searchable list of the Krea bases actually on disk — the same candidates the app itself would elect, so a checkpoint it refuses is never offered; you can still type a name for a file you have not downloaded yet. A name that is not on disk stops the engine rather than falling back to automatic election: this exact fallback once had a whole training run on a third-party finetune nobody chose, because the field showed one file and the graph loaded another. When the field is blank, the line under it names the build your runs actually load — with two Krea checkpoints whose filenames both read as "turbo", the tie-break used to pick one silently, and the only way to find out was reading a finished PNG's metadata, long after the quality judgements had been made on a model nobody chose. The name is resolved server-side by the same code the generation path calls, never re-ranked in the browser. Clear the field to go back to automatic detection. -
Identity edit LoRA →
krea.identity_lora. A searchable list of the LoRAs on disk (typing a path still works). At its shipped default — the canonical download name, which nobody typed — nothing is there under that name means the app searches your LoRA folders for akrea2_identity_editfile, so a renamed download still works. A name you chose yourself and that is not on disk stops the engine instead, naming the file: the identity LoRA is the face transfer, and quietly substituting another one changes every image you generate. Clear the field to return to the automatic search. -
Krea 2 Edit generation LoRA presets →
krea.generation_lora_presets. Named, ordered combinations of your own LoRA files, chained after the identity-edit LoRA when Krea 2 Edit generates dataset images. Max 8 LoRAs per preset, 12 presets; inside a preset the row order is the chain order. Per run you pick one preset in the workspace's 🧬 Krea 2 Edit tuning panel — which opens on Preset selected by default →krea.default_generation_lora_preset(default: empty = None), a starting point you can override for a single run without rewriting the setting — the run sends only the preset's NAME and the app resolves the files from this list, so renaming a preset can never make a run load something you didn't configure. Strength runs to 6, and to 20 for utility LoRAs whose filename saysfilter-bypass: those have no measurable effect below about 10. Limits worth knowing: the LoRA must be trained for Krea 2 (another architecture loads as a silent no-op — the picker badges it), only the model side is patched so a LoRA's text-encoder weights are ignored, a row whose file has moved is skipped with the rest of the chain still applied, and the 🔄 single-image regenerate in the workspace does not carry a preset. A row pointing at the same file as Identity edit LoRA is skipped too — it would chain the identity LoRA a second time on top of itself, summing both strengths into one delta well past what the file was trained for (visible as blocky, posterized output, not a subtler quality loss) — the preset editor flags that row as you write it rather than leaving the only trace in the server log, and it compares paths the way the server does, so a different separator or capitalisation cannot dodge the warning. Empty by default. (Preset mechanism by @waltm, Discord.)
Two more Krea calibration dials, on this page and in the 🧬 Krea 2 Edit tuning panel of the workspace's Generate variations screen — that panel is where you judge what they do, so it is where you should be able to turn them. They were reachable only from there for a while, and reference grounding only from here, which meant "where do I change this?" had a different answer per dial. They are settings either way: moving a slider on either screen saves the value globally and it applies to every Krea run from now on. The panel says so above the sliders.
-
Reference pull →
krea.ref_boost. Range0–10, default4.0. How hard the source latent is pushed back into the model at every denoising step. This is the lever for "the subject doesn't look enough like my reference" — raise it and likeness sharpens; raise it far and the model also recopies the composition, pose and outfit the shot card asked it to change. It ships paired withgrounding_pxand always has (v1 = 1024 / 4.0, v2 = 512 / 1.0, v3 = 512 / 0.25, v4 = 1024 / 4.0), so moving one dial alone puts you on a combination no shipped profile ever calibrated — this is the dial that brings the pair back into balance. What the v4 default rests on, stated so you can judge it: a benchmark on a single reference, four scored images per profile, where 1024 / 4.0 led by +0.17 face similarity on bust framing with no overlap between runs — and did so at a lower pull than the setup it beat, so the extra likeness was not bought by recopying. Face-framing cards were dominated by seed noise and measured nothing; body framings produced no face score at all. It is a deliberate choice for identity over variety, not a proven optimum. -
Identity LoRA strength →
krea.identity_lora_strength. Range0–1.5, default1.0. The weight applied to the Krea 2 identity-edit LoRA itself — the piece that carries the face across. Below 1 softens the likeness and leaves more room for the prompt;0disables the face transfer entirely; above 1 is past the weight the file was trained for and can look waxy or posterized rather than simply more similar.
Both are re-clamped by the server on every run, so a hand-edited config.json out of range is corrected rather than obeyed.
Two behaviours worth knowing before you build a dataset with it:
- The selected card's framing is honored. Krea Fit v1.2 uses the selected catalog card's framing and aspect ratio (including its 1:1 / 3:4 shape) instead of copying the source photo's shape.
- The dataset's extra reference images are never used by Krea — not when generating variations, and not when editing. Identity comes from the primary reference alone. Klein and the API engines are the ones that read those extra angles to strengthen identity. Krea's one spare slot lives in the ✦ Edit reference dialog instead. Add an image there with + and it becomes the second input of that single edit. The edit model was trained on two-input edits where the second image is a different subject — another person, or a scene to place yours in ("scene first, subject second"). So use it to compose: "put her in this room", "next to this person". Another angle of the same face is off-label there and can come back duplicated, which is exactly why that slot is not wired to the dataset's angles.
Outfits and expressions are steered differently here than on the other engines: this model preserves anything it is not positively told to change, so the catalog's "a different outfit (not the one in the reference)" phrasing is rewritten at generation time into a concrete garment ("wearing a red knit sweater"), picked from the shot's own name — so outfits genuinely differ across the dataset while regenerating one shot reproduces its own.
Contributed by socrasteeze (GitHub). Pin the exact files the Klein graph loads instead of relying on auto-detection. Every field accepts a full absolute path or a ComfyUI-relative loader name; empty fields keep the default behaviour (the canonical download filename first, then a narrow token scan of the ComfyUI model folders). Each field now lists the files actually found in that ComfyUI folder (extra_model_paths.yaml roots included), with a ↻ to rescan after you drop a new file in; free text stays available because an absolute path from outside every ComfyUI root is a legitimate value no scan can enumerate.
-
Diffusion model (UNET) →
klein.unet. A full path, or a name relative to a diffusion-model folder — e.g.klein/flux-2-klein-9b-fp8.safetensorsundermodels/unet, or a bare filename for a file sitting at a folder root. This is also what lets you use a UNET that does not live in aklein-named subfolder, which the automatic scan would never find. Default empty (auto-detect). -
Text encoder →
klein.text_encoder. Full path, or relative tomodels/text_encoders— e.g.qwen_3_8b_fp8mixed.safetensors. Default empty. -
VAE →
klein.vae. Full path, or relative tomodels/vae— e.g.flux2-vae.safetensors. Default empty. -
Consistency LoRA →
klein.consistency_lora. Full path, or relative tomodels/loras. The structure-anchoring LoRA chained onto the Klein edit graph — this is the same key that was previously config-only. Unlike the three above it has a shipped default, so clearing it disables the LoRA rather than turning on auto-detection. Defaultklein/Flux2-Klein-9B-consistency-V2.safetensors(the Setup download location).
How references resolve:
- A full path under any of ComfyUI's model folders — including folders registered in
extra_model_paths.yaml(the app parses it exactly like ComfyUI does) — is converted automatically to the relative loader name ComfyUI's nodes need, and the field shows Found. Nothing is copied or moved. - A full path anywhere else — Downloads, a Hugging Face cache, another drive — is hardlinked (or symlinked) into
<ComfyUI models>/<type>/lds-pinned/so stock loader nodes can open it, and shows the same Found. Your config keeps the original absolute path; the link is created when the reference resolves, costs no extra disk, and is reused on later runs. Staged files are deliberately not put in aklein-named folder, so they never show up as a second copy in the Klein model picker. - A reference that cannot be resolved — no such file, or the link could not be created (a read-only models folder, or another volume on an account without symlink rights) — is refused rather than substituted, with a badge here and the file named. It used to fall back to auto-detection and only show the badge: the run then went ahead on a different file from the one displayed, and the only symptom arrived with the images. Clearing the field is the explicit way back to auto-detection.
-
A bare file name is valid here. The model dropdown in the workspace writes this same key and stores names without their folder, so
flux-2-klein-9b-fp8.safetensorsandklein\flux-2-klein-9b-fp8.safetensorsboth name the same file and both resolve. They did not always: the bare form was read as "not on disk", which meant picking a model from the app's own menu switched the engine off, explaining that nothing was missing. Nothing you typed was ever wrong there. - An unresolvable Diffusion model does not darken the whole engine while usable Klein builds sit on disk. That slot has a picker in the run panel, so switching the engine off removed the very control that would have repaired it — a feature announced as optional cannot also be a feature one missing file takes hostage. The card asks for a model instead, and the run refuses only if it is started without one (never by quietly loading a neighbouring file). With no Klein build on disk at all there is nothing to ask, and the engine stays off.
- The one exception is Consistency LoRA left at its shipped default, which nobody chose: at that value a missing file is still simply skipped, exactly as before.
-
A bare file name is valid here. The model dropdown in the workspace writes this same key and stores names without their folder, so
- Native / bf16 UNETs (a filename without
fp8) now load withweight_dtype: default; FP8 builds keepfp8_e4m3fn. Both shipped Klein graphs hardcodedfp8_e4m3fn, which quantized a full-precision pin on load without saying so. The canonical download carriesfp8in its name, so a stock install renders exactly as before. - Generation-LoRA preset rows accept full paths the same way.
-
Not cleaned up automatically. Changing or clearing a pin leaves its link behind in
lds-pinned/; the folder is safe to delete by hand when nothing points there any more.
Traps and good-to-knows:
- The dataset's own Klein model (per dataset) choice still wins over
klein.unetfor that dataset's runs — the pin is what auto-detection falls back to, not an override of an explicit per-run pick. - This is the fix for a model the app insists is "missing" while you are looking at it. Auto-detection is deliberately narrow (a wrong model fails at sampling time with a cryptic shape error, which is worse than a missing one), so it declines any file it cannot confidently name — and a declined file is reported as missing. Pinning it by name removes that discretion: the file resolves, and the integrity check then tells you the truth about it, including "present but unreadable" for a corrupt or half-downloaded weight, with the delete-and-re-download action attached.
- Pinning the wrong kind of file (e.g. another family's text encoder) is not validated — that generate will fail at sampling time with a shape error. The narrow auto-detection exists precisely to avoid that; only pin files you know are Klein-compatible.
Idea from @waltm on Discord. Named combinations of generation LoRAs that stack on top of the local Klein edit graph. Stored in klein.generation_lora_presets (default: empty — no presets).
Each preset has a name and an ordered list of LoRAs, and each LoRA row has:
- a file — a name relative to your ComfyUI
models/lorasfolder (e.g.klein/my-lora.safetensors), exactly like the consistency LoRA. The field is a searchable dropdown of the LoRAs actually on disk (every folder,extra_model_paths.yamlincluded), with Klein-compatible files listed first and each one badged by architecture; free text still works for a file you haven't downloaded yet; - a strength —
0–1.5, default0.6.
Use + New preset, Duplicate, Delete and rename to manage them, and the up/down controls to set chain order. Each preset can chain 8 LoRAs. Save as many named presets as you need.
The strengths are also editable from the ✨ Upscale & improve window (the one the ✨ button opens on a picture, and the inline panel on the dataset's bulk toolbar): once a preset is picked there, its LoRAs are listed with a slider each, saved as you drag. That is tuning only — the same klein.generation_lora_presets values, app-wide, so a change there applies wherever that preset runs, generation included. Adding, removing, reordering and renaming stay here, because those change what the preset is for every surface that runs Klein; the window links straight back to this card for them.
How presets are used matters:
- A preset is chosen per run in the 🖥️ Klein tuning panel of the workspace. The panel opens on Preset selected by default →
klein.default_generation_lora_preset(default: empty = None, the behaviour every install had before this key existed). That is a starting point, not a lock: the run panel still offers None and every other preset for that run, and choosing there never rewrites the setting. Fail-closed like the rest of the chain — a default naming a preset you have since renamed or deleted falls back to None, and the Settings field says so instead of pretending it is empty. Each engine has its own default key (klein.default_generation_lora_preset/krea.default_generation_lora_preset), becauseklein.generation_lora_presetsandkrea.generation_lora_presetsare independent lists where the same name can designate two different chains. - Resolution happens by name on the server, and it's fail-closed: if a run references a preset name that no longer exists, it runs with no extra LoRAs rather than erroring.
- Trap: renaming a preset does not follow a run that referenced it by the old name — that run silently falls back to no extra LoRAs. Rename before you queue, or re-pick the preset on the run.
- There is deliberately no automatic NSFW gating on individual LoRAs — the preset you pick carries the intent. If you want an "NSFW full" stack, make it a preset.
-
Trap: a row pointing at the same file as the consistency LoRA (
klein.consistency_lora) is skipped — it would chain that LoRA a second time on top of itself, summing both strengths into one delta well past what the file was trained for (visible as blocky, posterized output). The preset editor now flags that row as you write it, on the row itself, instead of leaving the only trace in the server log — which is how a preset holding exactly one such row produced a run with no extra LoRA and nothing on screen to explain it. The check compares paths the way the server does (separators unified, case ignored), soklein/x.safetensorsandklein\X.safetensorsare both caught. It does not claim to catch an absolute path aliasing the same file — the server still drops that row, quietly.
Raised by ashish.sinha. Generation steps → klein.generation_steps (1–50, default 5). How many sampler steps the local Klein engine spends on each generated image — variations, regenerations and the automatic small-image rescue. The shipped workflow had this pinned at 5 with no way to change it; the default is that same 5, so nothing moves until you raise it. More steps usually render more cleanly and cost proportionally more time (10 steps ≈ twice the wait per image).
It is a rendering knob, not an anatomy fix: extra limbs, tails or wrong body parts come from the identity prompt describing the wrong kind of subject (see the subject-type note below), and no number of steps repairs that.
Enhancement LoRA on edits → klein.edit_base_lora_strength (0–2, default 0). How much of the detail LoRA (klein/realistic.safetensors) Klein mixes into an edit: the ✦ reference edit, variations, regenerations and the small-image rescue. The shipped workflow carries that LoRA at 0.8 and nothing on these lanes ever turned it down — which stayed invisible while the file existed on no install (the node was skipped), and became real once Setup started downloading it: from then on every Klein edit ran with a style LoRA at 0.8 pulling the result away from the instruction you typed. The default 0 is the render every install had before that download existed; raise it to let the LoRA add detail on purpose. “Upscale & improve” is unaffected — it has its own klein.improve_base_lora_strength.
Separate from Upscale & improve ▸ Steps (klein.improve_steps), which drives the manual improve pass only.
Not in Settings — it sits at the top of the Generate variations panel, above the shot cards, whenever a local engine is selected. Output size (MP) → variations.output_megapixels (0.5–2.0, default 2.0).
It is one dial for both local engines, on purpose. Before it existed the two disagreed: Klein rendered every shot at a hardcoded 2 MP in the reference photo's shape, while Krea 2 Edit used the shot card's shape but never spent more pixels than the reference itself held — so a 1024×832 reference gave you 0.84 MP Krea tiles next to 2 MP Klein ones, in different shapes, in the same dataset, with nothing on screen explaining either number.
Now both engines spend this budget on the shot card's ratio: pick 2.0 and a portrait card renders at 1216 × 1632, a square one at 1408 × 1408, a 16:9 one at 1872 × 1056. Two consequences worth knowing:
- Klein no longer follows the reference photo's shape. A card that says 16:9 gets 16:9, whatever your reference is. That is the point — a dataset's framing should come from the shots you picked, not from how you happened to crop one photo.
- The dial wins even above the reference's own pixel count. Asking for 2 MP from a 0.85 MP reference genuinely renders 2 MP; the model paints the extra pixels rather than resampling them. It is a generation, not a copy — but do not expect detail your reference never had.
Lower it for a shorter wait and less VRAM per image (0.5 MP renders visibly softer shots, fine for a quick trial run). 2.0 is the ceiling because the Flux edit models lose coherence past roughly that; to go bigger, generate at 2.0 and then run ✨ Upscale & improve, which has its own separate budget (klein.improve_megapixels, up to 8 MP).
Unaffected: the ✦ reference edit (no shot card, so it keeps your source's exact frame and is never upscaled) and the three API engines, which are billed per image and framed by their provider.
Not in Settings — it lives on the dataset, in the 🖥️ Klein tuning block of the generation panel and next to the ✨ Upscale & improve action itself. One setting, deliberately: generation and improve drive the same loader in the same workflow, so two near-identical model dropdowns would only be a lasting source of confusion. If you ever need to improve with a heavier model than the one you generate with, say so — splitting one stored value into two is additive; merging two back into one would have to throw one of your answers away.
Default is Auto, and Auto is exactly what every dataset did before this setting existed: Studio resolves the model itself (the canonical download first, then the first loadable Klein file it finds). Choosing nothing changes nothing.
The list is detected, never typed. It comes from the same scan ComfyUI itself would do — models/unet, models/diffusion_models, every root declared in extra_model_paths.yaml, and a relocated models folder (comfyui.models_dir) — in a klein-named subfolder or loose at the root. The one real constraint is that the model must be nameable as Klein: either the file name or its folder name has to contain klein. See Where the Klein model can live in the README.
What it applies to: every piece of Klein work that dataset starts — the single ✨ improve, the 🔄✨ re-run, the whole improve batch, Klein generation (variations and regenerations), the reference edit on the Klein engine, the rescue of scraped images under 768 px, and the 🧽 watermark clean on the Klein engine (bulk and per-image).
The one exception is a bank, which has no dataset and therefore nothing to inherit: its 🧽 Klein inpaint resolves the model automatically, and the panel says which one. Naming the model and choosing it are separate questions — a bank can be told, it just has nowhere to store an answer.
When there is only one model, the picker does not appear — there is no choice to make — but the line naming the model still does. Not knowing which model produced an image was the actual complaint; a dropdown with one option was never the answer.
If the model you chose is later moved or deleted, the run refuses by name and tells you which file is gone. It does not quietly fall back to another model: that swap produces a result that looks perfectly fine and is not the one you asked for.
Coming from an older version: the generation picker used to save to your browser (editPage_flux2KleinModel_v1), which improve never read — that is why improve had no model option anywhere. That browser value is still honoured for generation and is now offered, once, to be saved onto the dataset. Nothing is adopted behind your back: until you accept, improve keeps resolving Auto exactly as before.
Feature request by @bbsorry (雨田壹). Every generated variation is prefixed by a hidden identity lock — a block of text that tells the engine to keep the subject's exact identity and take the pose and setting from the description, not the reference photo. These used to be baked in and invisible; now you can read and edit them. They are stored under identity_prompts.*.
One set per subject type. (Reported by ashish.sinha.) The three identity locks are scoped to the dataset's subject type — Human, Animal, Creature, Object, Other, Anime. Pick the type with the chips at the top of the card; a small dot marks every type you have already customised. A prompt you write for Animal applies to animal datasets only and never to a human one. Before this, the override was one global text: someone who adapted it for animals then saw their human variations come back with tails, extra limbs and odd footwear.
Storage follows that split, and nothing was renamed or migrated: the Human overrides stay on the original flat keys (identity_prompts.face_single, .face_multi, .klein_identity), which is where every override written before this change was stored, so yours keeps applying to your human datasets. The other types live under identity_prompts.by_subject.<type>.<kind> and never fall back to the flat key. identity_prompts.klein_improve and its toggle stay global on purpose — "add texture and detail" means the same thing for a person, a dog or a car. It does not mean the same thing for a drawing, and that is a known rough edge: the shipped text asks for photographic detail, so on an Anime dataset the improve pass works against the very art style every other prompt in the app protects. Until that is settled, edit the box (or turn it off) for drawn datasets — see Troubleshooting → "Upscale & improve" makes my anime look realistic.
If you had adapted the identity prompt for a non-human subject before this change, your text is now sitting in the Human set (that is where it was saved). Open the card, check the Human boxes, and move the text to the right subject type — nothing was discarded.
One box, already filled. Each prompt is a single editable box that already contains the exact text in use — the built-in default when you have not overridden it. Put your cursor in it and change a word; there is no "load the default first" step, and no second read-only copy of the text below (the old two-box layout is gone).
Nothing is stored while you match the default. As long as the box still holds the built-in text (surrounding whitespace ignored), the setting is saved as blank, which is what makes blank = use the shipped default work. This is not cosmetic: if merely opening a prompt persisted a copy of it, you would be pinned to that wording forever and every later improvement to the built-in prompt would stop reaching you, silently. The line under the box always tells you which state you are in — Following the built-in default or Custom override. Reset to default appears only in the second case and clears the value back to blank.
Reproducibility guarantee: with nothing overridden, generation is byte-identical to before this setting existed — you only change behaviour if you deliberately edit the text away from the default.
Edit the improve instruction from the ✨ button itself. identity_prompts.klein_improve is the one prompt on this card you never have to come here for: the improve note carries ✎ Edit this instruction here, which opens the same box and the same on/off tick in place. In the dataset and generated-image lightboxes that note is the settings screen of the window the ✨ button opens (press ✨, read, edit, then Generate); on the bulk toolbar it stays inline, because a batch shows its instruction before launching a lot. It edits this setting — there is no per-image and no per-run copy, so what you write there is what this card shows and what every later improve sends, in every dataset. The panel says that in as many words before you type, because a control sitting inside one dataset's screen otherwise reads as belonging to that dataset. The rest of the contract is identical to the box here: the field is pre-filled with the text actually in force, text matching the built-in default is stored as blank, Reset to default shows up only once you really have an override, and edits save as you type (the last keystroke is saved even if you close the lightbox on it). What is not duplicated there, on purpose: the per-subject identity locks, the other prompt parts and the four strength knobs — the note links here for those.
Shortcut from the workspace. The multi-reference instructions are also reachable from Add images ▸ Extra refs ▸ ✎, without opening Settings. That modal shows both of them — face_multi (Nano Banana / ChatGPT) and klein_identity (Klein and Krea 2 Edit) — because the two engine families do not share one text, and badges every prompt your ticked engines actually use (a Klein + ChatGPT run really uses both). It edits the prompts of the open dataset's subject type, and says which one in its title and intro; edits made there are the same settings as here, for that subject.
-
API engine — identity lock (single reference) →
identity_prompts.face_single. Prepended to Nano Banana / ChatGPT variations built from one reference photo. -
API engine — identity lock (multiple references) →
identity_prompts.face_multi. The same, for variations built from several reference photos — it tells the model every reference is the same person and to use them together. -
Local engines — restage & face-identity block →
identity_prompts.klein_identity. The instruction block both local engines — Klein and Krea 2 Edit — use to restage the shot (pose, framing, outfit, expression) while keeping the face identical. They share one prompt assembly, so this single text drives them both; it was labelled "Klein" until a user asked on Discord whether it reached Krea 2 at all. -
Klein upscale & improve prompt →
identity_prompts.klein_improve, with an on/off toggleidentity_prompts.klein_improve_enabled(default on). The fixed instruction the manual Klein upscale & improve action sends to add texture and detail. The shipped text isadd detailed texture, add sharp details, add candid shot, add soft focus effect— read it before you blame the model: those four clauses describe a photograph, and they are applied to every dataset, drawn ones included. Turn the toggle off to run that action with no prompt at all — a pure upscale with no added styling. Both the text in force and these two levers are now quoted and editable from the ✨ Upscale & improve button itself (the settings window it opens in the lightboxes, the inline note on the bulk toolbar) — see Edit the improve instruction from the ✨ button itself above — so you no longer have to know this setting exists to find it, nor leave your images to fix it. -
Upscale & improve — strength →
klein.improve_megapixels,klein.improve_base_lora_strength,klein.improve_consistency_strength,klein.improve_steps. The output resolution, and how much that pass is allowed to change the image. Until these were exposed the whole profile was hardcoded — both LoRA strengths pinned to 0, so the enhancement LoRA baked into the workflow never applied at all, and the size was fixed at 2 MP whatever the source was worth. Defaults are those same historical values (2 MP / 0 / 0 / 4 steps), so leaving them alone keeps today's result exactly. These are read at each run, so to try a new value on an image you already improved, use the 🔄✨ button on that tile: it re-runs the pass on the same source image with the settings as they are now, and replaces the result in place. (The ordinary 🔄 stays hidden there — it would restart from the dataset's reference photo and make an unrelated image.)- Output size (MP) (0.5–8, default 2) — the source is rescaled to this pixel budget before sampling, so it is the result's resolution. This is the knob that makes "Upscale" actually upscale.
-
Enhancement LoRA (0–2, default 0) — the workflow's own detail LoRA. At 0 it does nothing; try 0.5–0.8. It needs its weights file (
klein/realistic.safetensors): when that file is missing the node is skipped entirely and this value changes nothing. Setup ▸ Install everything downloads it with the other Klein assets (from dx8152/Flux2-Klein-9B-Enhanced-Details, Apache-2.0) — run it first if the slider seems inert. -
Consistency LoRA (0–1.5, default 1) — anchors the composition and background, not identity. Deliberately high here: an improve pass should add detail without redrawing the shot, so the resistance to editing that makes 0.8–1.0 a poor choice for restaging is exactly what you want. (Shipped briefly as
improve_character_lora_strength, a misnomer; a value saved under that name is still honoured.) - Steps (1–50, default 4) — more steps is slower and usually cleaner.
- Out-of-range or malformed values are clamped, never rejected: a bad config weakens the pass instead of failing your click.
- A strength you raised is never silently dropped. If a LoRA's weights file is missing while its strength is above 0, the pass reports the missing asset (which is what triggers its download) instead of running without it and leaving you to wonder why nothing changed. At strength 0 it stays a quiet skip — nothing is lost by not loading a LoRA you did not ask for.
Each field is a plain textarea; there's no Test button — you see the effect on your next generation. If an override ever makes results worse, hit Restore default.
The identity lock is only one of six sources a local-edit prompt is assembled from. The other five shipped hardcoded and invisible until this wave — including the one that caused a live incident, where a hold order that listed what to preserve ("tattoos, scars, moles…") had the model painting tattoos on subjects who have none. All of them follow the same contract as the locks above: blank means the shipped default, non-blank wins, Restore default on every box.
Two of them follow the subject type chips, because their text genuinely differs per type:
-
Rendering tail (SFW) →
identity_prompts.render_tail_sfw. The last thing Klein and Krea read on a safe-for-work shot: the medium and the clamp. For photographic subjects this isProfessional realistic photograph, SFW.; for Anime it asks the model to stay a drawing in the reference's art style. -
Rendering tail (uncensored) →
identity_prompts.render_tail_nsfw. The same position on an uncensored shot: the SFW clamp is dropped and anatomically correct forms are requested. Only the local engines ever see it — the API engines refuse this content. -
Shot detail per framing →
identity_prompts.framing_face/.framing_bust/.framing_body/.framing_back. Klein and Krea under-fill a short tag prompt and invent the rest, so each shot carries a concrete description of the framing — this is where "85mm portrait lens look" and "the ENTIRE body visible from head to toe" live. If your full-body shots keep coming back cropped, this is the box.
Non-human overrides for those six live under identity_prompts.by_subject.<type>.<kind> like the locks; Human keeps the flat key.
Four more are global — one text for every subject type, because they have no per-subject meaning (the two directives are only ever injected into human shots):
-
Hold the skin (Krea) →
identity_prompts.markings_lock. Sent with every Krea prompt: it forbids adding marks to the skin, and forbids redrawing, restyling, moving or removing the ones the reference already has.⚠️ This is the delicate one. Naming a body feature in this box is enough to make the model paint it — that is exactly what the first version did. Describe what not to do, without naming a single feature. -
Outfit directive →
identity_prompts.outfit_vary. Added to every human shot that does not already name a garment, so clothing comes from the description instead of being copied off the reference (which teaches the LoRA that the person owns one outfit). Note that Krea replaces it with a concrete garment from the palette below, so editing this text does not change what Krea sends. -
Expression directive →
identity_prompts.expression_neutral. Added to every human shot that does not already name an expression, so the reference's smile does not ride on all 40 variations. -
Concrete garments →
identity_prompts.outfit_palette, one garment per line. Krea preserves anything it is not positively ordered to change, so "a different outfit" is a no-op on it; each shot is handed a real garment from this list instead.⚠️ The garment is chosen from the shot's name by position in the list, so adding or removing a line reshuffles which garment every shot gets — same shots, different clothes. Editing the wording of one line only affects that line. Clear the box entirely to go back to the shipped list (an empty list never produces a prompt with no outfit in it).
Both directives are baked into the shot catalog and stored with each variation, so the override is applied when the prompt is sent rather than when the shot is created — which means an edit reaches datasets you built before you made it, on their next generation.
At the bottom of the card, a live preview of the composed prompt: pick an engine, a framing and SFW/uncensored, and it shows the full ~1000 characters a real catalog shot would be sent, assembled from every box on the card, including edits you have not saved yet. It is composed by the server through the same functions generation uses, so it cannot drift from reality — and it generates nothing: no model is loaded, no GPU is touched, nothing is billed.
It is also the fastest way to answer "why did it do that": read the prompt, find the sentence, edit the box it came from.
Credentials for the built-in web scraper. All of these apply immediately — no restart — because sources read their key at request time.
None of these has a Test button; you find out they work on your next scan.
-
Reddit client ID →
REDDIT_CLIENT_ID(secret). Optional. Reddit scans work out of the box using a shared public client ID, but that ID is rate-limited across everyone who uses it, so you can hit "rate limiting requests, retry in Ns" (429) before your first scan of the day. Your own free ID gives you a private quota and clears those. Trap: on reddit.com/prefs/apps, create the app as type installed app — a web app or script comes with a client secret, and Reddit then rejects the anonymous login this app uses (every scan fails with 401). The field has a built-in step-by-step guide. -
Civitai API key →
CIVITAI_API_KEY(secret). Optional. One key, three uses: without it Civitai scans return SFW results only (add it to reach adult content you're entitled to use); it reads the prompts in the 🌐 Civitai browser; and it is what 📤 Publish to Civitai signs with (a model page created from a checkpoint, an image posted under it). Free accounts have one: civitai.com → Account settings → API Keys. -
Pexels API key (required for Pexels) →
PEXELS_API_KEY(secret). Required for any Pexels search — there's no shared fallback. The free quota is 200 requests/hour and 20,000/month. Create one here. Note the standing warning: an API key alone does not authorize dataset or machine-learning use — configure this only if Pexels has explicitly authorized your use case.
Where you point the app at the local programs that unlock the full pipeline: ComfyUI (Klein generation and Test Studio), Ollama (the vision model behind captioning and framing) and ai-toolkit (training and JoyCaption). Each card has a Test button that tells you immediately whether the app can see the tool.
-
Local generation queue limit →
comfyui.local_queue_limit. Default 1,000, range 1–10,000 unfinished local images per dataset. Local engines can queue long unattended runs; images still run one at a time on the GPU. Existing unfinished local images count towards the limit, including in mixed runs. API images have no fixed batch limit and do not consume this local budget. The generation multiplier offers up to 20 images per selected shot. -
Generation time limit (minutes) →
comfyui.generation_timeout_minutes. Default 15, range 0–1,440, before the processing multiplier below. Increase this for slow hardware, or use 0 to wait without an elapsed-time limit. It applies when a new ComfyUI job starts; cancellation and checks for a disconnected worker remain active. If a finite limit expires while ComfyUI is still working, LDS holds the queue until the uncertain job is reconciled rather than submitting overlapping work.
Time limits in Settings → Local tools keeps the existing defaults and lets you adjust them for slower hardware or competing workloads:
-
Klein repair and watermark cleaning (
comfyui.repair_timeout_minutes): 5 minutes per image, including queue wait. Covers box and brush repairs in Bank, Dataset and Test Studio. Set 15 for a repair that takes eight minutes; set 0 for no elapsed-time limit. -
Bank upscale and improve wait (
comfyui.improve_timeout_minutes): 30 minutes per image, including queue wait; 0 means unlimited. A custom repair/improve allowance also reaches the ComfyUI worker, so a shorter general generation limit cannot cut it off. The worker's historical generation limit remains in effect when all settings are at their defaults. -
Processing timeout multiplier (
timeouts.processing_multiplier): 1×, configurable from 0.1× to 100×. Scales the generation and repair limits above, local inference workers, captioning, encoder startup/queries, service startup and subprocess preparation budgets. Batch-size-based budgets keep growing with the batch and are then multiplied. Already unlimited waits remain unlimited. -
Network timeout multiplier (
timeouts.network_multiplier): 1×, configurable from 0.1× to 100×. Scales LDS HTTP connection/read waits, downloads, API calls and service-health checks, including the ComfyUI unhealthy-worker grace. AI completion/model-load responses use the processing multiplier for reading and the network multiplier for connecting. For ordinary requests, both use the network multiplier.
Changes apply to new waits, without restarting LDS. For example, a processing multiplier of 2× gives the default five-minute repair ten minutes; the repair setting and multiplier combine. Stop/cancellation checks, polling intervals, retry counts, lock leases and cleanup grace periods keep their existing behavior. Cloud training idle limits remain in Cloud settings. Independently implemented plugin waits are controlled by that plugin; shared LDS worker and HTTP helpers honor these settings.
-
ComfyUI API URL →
comfyui.api_url. The HTTP endpoint of your running ComfyUI. Defaulthttp://127.0.0.1:8188. Test confirms it answers. -
ComfyUI install directory →
comfyui.base_dir. The folder that containsmodels/,output/,input/. Default empty. This is what lets the app scan your checkpoints and LoRAs — set the API URL alone and there's nothing to scan. If you point it at a..._windows_portablefolder, the app auto-corrects to theComfyUIsub-folder inside it. In the Setup wizard this field is checked as you type: a wrong, empty or missing folder gets a specific reason, and pointing at the launcher/parent folder offers the real ComfyUI inside it in one click. The wizard additionally checks that the app can actually put a file in that install'sinput/folder (honouring aninput_diroverride if you set one) — the half it used to certify without testing. A failure there is a warning, never a blocker: configuring the app before mounting your volumes is a perfectly normal order of operations. -
Advanced: ComfyUI folder overrides →
comfyui.output_dir,comfyui.input_dir,comfyui.models_dir,comfyui.loras_dir. All default empty, and empty is what you want unless ComfyUI runs on folders of its own — a ComfyUI started with--output-directory,--input-directoryor--models-directorydoes not keep its files under the install directory, so without an override here the app reads and writes in the wrong place. Each field shows the folder it falls back to while empty, computed with the very function the app uses at runtime, so the effective path is never something you have to work out; a path that isn't on disk is flagged in amber rather than failing silently mid-generation. If ComfyUI is running, the app asks it which folders it was launched with (it reports its own command line via/system_stats) and offers them in one click — nothing is ever guessed from a folder layout, so no suggestion appears when ComfyUI is unreachable, predates that field, or was started with no custom folders.Each field is also checked for usability, not just existence: the app writes into
input/(and into the LoRA folder when it installs a trained LoRA), so a folder that is there but cannot be written to from the app's process is flagged in amber with the reason. This is the case that used to be invisible — a ComfyUI in a separate container, in WSL, or on another machine answers on its URL while itsinput//output/folders are not shared, so everything looked configured and the first generation died on a detail-free500(reported on Discord by nofaceman). The app hands ComfyUI its source images through the filesystem, not over the API:input/andoutput/must be visible to both sides at the same path. See Troubleshooting → ComfyUI runs in another container.For
input/there is a third check, and it catches what neither of the other two can: whether the folder you configured is the one ComfyUI actually reads. Present and writable is not the same as ComfyUI's — a second install, a portable copy, or a--input-directory/--base-directoryon ComfyUI's command line all leave the app writing happily into a folder nobody reads. Nothing on disk shows it, so if ComfyUI is running the app simply asks it: it writes a marker into the folder and requests that file back over ComfyUI's own/view. A "no" is amber here, with the folder you configured and what ComfyUI reported about its own (quoted from the command line it echoes in/system_stats) — instead of the generation that used to die on ComfyUI's "Invalid image file" with nothing in the app to read (GitHub #64). -
ComfyUI response timeout →
comfyui.object_info_timeout_s. How long ComfyUI may take to answer the one heavy question the app asks it: list every node class and every model file you have (/object_info). Default 45 s, clamped to 5-300. That list grows with every custom-node pack and every weight you install, so this is one of the very few settings whose right value genuinely depends on your install: it was a hardcoded 8 s until a user measured ~15 s on his own ComfyUI (j_o_e_l., Discord) and found that Krea 2 generations were being refused with "ComfyUI isn't running" — on a ComfyUI that was running. Raise it if you ever see "ComfyUI is answering too slowly"; the app now says that instead of blaming a stopped server, and names the number. A high value costs you nothing when ComfyUI is off: the connection attempt and the answer have separate budgets, and a ComfyUI that is not listening fails the connection in about 3 seconds no matter what you set here — so background checks never sit waiting for a server that isn't there. If the enumeration does fail, the app remembers that for about 20 seconds instead of letting every screen re-ask for the same multi-megabyte answer; pressing Refresh models (or saving Settings) re-asks immediately, which is what you want right after starting ComfyUI. -
Hugging Face token →
HF_TOKEN(secret, no Test button). Only needed to auto-download license-gated models — notably the Klein fp8 weights, and the gated training bases (Krea 2, FLUX.1-dev, FLUX.2 Klein). Read access is enough for accepted gated models. This token is handed to the local training subprocess explicitly, so what you save here is what training authenticates with. If you leave it empty, a login already on the machine (hf auth login, i.e. a token file under~/.cache/huggingface,$XDG_CACHE_HOMEor$HF_HOME) is found and used instead — the Hugging Face cache override below relocates the cache without logging you out. When a download is refused, read the status code, not the sentence:401means Hugging Face saw no valid token (paste one here),403means the token is valid but that account has not accepted the model licence yet (open the model page and accept it). The raw Hugging Face message says "you must have access to it and be authenticated" in both cases; the failure block in the training panel separates them for you.
Overrides and extra_model_paths.yaml. These two mechanisms stack rather than compete. comfyui.models_dir/comfyui.loras_dir set the app's default model roots; extra_model_paths.yaml is read in addition, from <comfyui.base_dir>/extra_model_paths.yaml — the same place ComfyUI itself looks. The yaml is therefore always located from the install directory, never from a models override, so the two can't end up pointing at different trees. For models and LoRAs the yaml usually already does the job; the override is for the case where the models folder itself moved.
Where a deployed LoRA lands. Reading a LoRA can span every root; installing one has to pick exactly one folder, and the app picks it in this order: the comfyui.loras_dir override if you filled it (you said where your files go — the yaml cannot take that back), otherwise the first LoRA root in ComfyUI's own priority order, otherwise <install>/models/loras. In yaml terms that first root is a declared loras: folder only when its profile carries is_default: true — that flag is exactly how ComfyUI is told look here first, and a plain extra root stays a secondary location for ComfyUI, so it stays secondary here too. The open LoRA folder button opens that same folder, by construction. Reported by Geekswordsman (GitHub #25), whose deploys were landing in <install>/models/loras while his yaml declared another folder. LoRAs deployed before this changed are still listed, still loadable and still deletable where they are — nothing on disk is moved.
Continuing without ComfyUI. Leaving the install directory empty in the Setup wizard is a deliberate choice: it shows what turns off (local Klein generation including the NSFW lane, Klein watermark cleaning, the Test Studio, training on your own ComfyUI base models, and the on-disk LoRA preset picker) versus what stays on (scraping, curation, captioning, the API image engines, ai-toolkit/cloud training, Hugging Face publishing), then remembers the skip (comfyui.setup_skipped) so it stops nagging. Entering a directory at any point cancels the skip automatically and turns those features back on — the flag never hides a real problem with a ComfyUI you have configured.
Models outside models/? If your ComfyUI uses an extra_model_paths.yaml (portable builds and Stability Matrix installs commonly do), the app reads it the same way ComfyUI does, so bases that live elsewhere are found. This isn't a setting — it follows automatically from your install directory. Without such a file, nothing changes.
This now includes the training bases, which were the last exception: an SDXL checkpoint and a Z-Image merge declared in the yaml are listed in the base picker, accepted at launch, and handed to ai-toolkit as a real path. When the same file name exists in two roots, the one a running ComfyUI would load wins (is_default first), so you train on the same weights you generate with. Capitalisation of folders and file names doesn't have to match what the picker stored. And when a base genuinely cannot be found, the app says so naming the file, instead of passing a bare name down to ai-toolkit and letting it fail on a path you never typed.
-
Local LLM provider →
local_llm.provider:ollama(default) orlmstudio. One local model server does captioning, framing auto-classify, auto head-crop, Test Studio Describe & Enhance and the bank's natural-language filter; this picks which. The Setup wizard asks it too, on its local-LLM step, whichever provider is currently selected — an install that only has LM Studio should not have to find this page to say so. Nothing changes for an existing install — the default is Ollama and the second provider only ever adds a door.
Both cards below stay editable whichever provider is selected, so you can configure the other one and press Test before switching. Background status checks use only the selected provider. In Settings, each card lists models from its own server at the URL currently entered, including an unsaved URL. Refresh reloads that list after a download or server restart. Listing models does not save settings, load a model or change the selected provider; use Save to apply a selection. An unavailable server or a missing model never clears your saved choice. For an authenticated LM Studio server, save the API key before refreshing.
A note on the stored value ollama. It is unchanged, everywhere it is written: in captioning.backend, in a dataset's saved Captions ⚙️ options, and in the caption-origin column of every image already captioned. Those strings live in databases people already have, and renaming one would silently alter what their saved options mean. What it MEANS has widened: ollama now reads as the configured local provider.
The labels follow that, so nothing on screen names a server you are not running. Settings ▸ Captioning offers LM Studio vision when LM Studio is selected; the per-dataset picker reads Local LLM only (Ollama / LM Studio); the caption-origin badge reads Local LLM; and the model pickers, the ✨ Enhance popover and the run window's tooltips all name the provider whose list they are actually showing. Only the stored strings stayed still.
The card shows Ollama's live state and, when the binary is installed but the server isn't running, a ▶ Start Ollama button that launches it for you — no terminal needed.
-
Ollama URL →
ollama.url. Where Ollama is listening. Defaulthttp://127.0.0.1:11434.
Sharing one Ollama with another tool. A single local Ollama runs one model at a time, so if another app has one loaded, LDS refuses to start its own rather than evict work that isn't its. When that happens, ✨ Enhance and 🔎 Describe say so and then wait: Ollama unloads an idle model by itself after a few minutes, and your action restarts on its own the moment the model is free — no clicking, nothing retyped. If you would rather not wait, Unload it and continue frees the other model and resumes; that click is the only thing that ever unloads a model LDS did not load. Pointing LDS at a second Ollama instance on another port (or another machine) removes the contention entirely. A remote URL is never probed or unloaded — it isn't sharing this machine's GPU.
In Docker, choose the deployment only from Setup → Ollama: none disables it, host uses the existing host service at the authoritative http://host.docker.internal:11434, and docker uses the isolated companion at the authoritative http://ollama:11434. The managed URL is read-only. Neither launcher downloads a model: use the explicit Pull button in LDS to see progress and cancel or resume the transfer.
-
Docker deployment mode →
ollama.deployment_mode:none,host, ordocker. This setting is selected by Setup and applies only to Docker; native installs keep their normal URL-based behavior. -
Ollama vision model →
ollama.vision_model. Select from the installed models reported by Ollama. Choose a vision-capable model for auto-captioning, framing auto-classify, head-crop and watermark detection; the list can also contain text-only models. Defaulthuihui_ai/qwen3-vl-abliterated:8b-instruct— the abliterated (uncensored) build, so it captions adult datasets instead of refusing them. Trap: keep the-instructtag. The plain:8btag is the Thinking variant, which reasons out loud instead of captioning and produces garbage here. -
Images analysed at once →
ollama.vision_concurrency. How many images a bank pass sends to Ollama at the same time. Default 4. The passes that read every image in a bank — watermark scan, framing, captions — spend most of each request waiting on the round-trip rather than on the GPU, so overlapping them roughly halves a long pass (measured 2.0× at 4). Going higher gains little: 6 and 8 buy single-digit percentages unless your Ollama is configured for more parallel requests (OLLAMA_NUM_PARALLEL), and they make Stop take a few seconds longer because it waits for the calls already in flight. Set it to 1 to get the old strictly-one-at-a-time behaviour back. Any value the app can't read falls back to 4, and anything above 16 is clamped — a bad value costs you speed, never the pass. -
Keep the vision model warm →
ollama.vision_keep_warm_seconds. How long a one-off vision job may leave the model loaded once it's done. Default 120 s (0 = off, capped at 600). Loading the model costs about 13 s; describing an image once it's loaded costs about 0.5 s — so a cold call is roughly 25× a warm one, and the old behaviour (unload after every single image) made cropping five reference photos in a row pay that load five times. The catch is memory: the vision model really occupies about 7.5 GB, and a loaded ComfyUI already sits near 19 GB of a 24 GB card, so they don't both fit — on Windows nothing errors out, the driver just pages silently and a vision pass measured 13.5× slower in that state. Keeping it warm is therefore conditional and revocable: the app only leases it when neither a training run nor its own generation queue wants the card, and it hands the memory straight back the moment a generation is submitted or a training starts. If the app can't tell what's using the GPU, it unloads — the old behaviour. Bank passes (watermark / framing / captions) are unaffected: they already keep the model warm for their own duration and unload at the end. Set it to Off on a card that's tight on memory, or if you run generations from ComfyUI's own interface (work LDS never sees, so it can't revoke the lease for it — the exposure is bounded by this value). If you want ComfyUI and the vision model to genuinely coexist rather than take turns, the lever is on ComfyUI's side, not here: it accepts a--reserve-vram <GB>launch flag ("the amount of VRAM in GB to reserve for your OS/other software"), which defaults to a mere 0.7 GB on Windows — that default is exactly why a loaded ComfyUI leaves no room. Raising it caps ComfyUI and frees the headroom, at the cost of heavier video workflows. LDS never launches ComfyUI, so it can't set this for you. -
Continue without Ollama →
ollama.setup_skipped. Ollama is optional and the Setup wizard no longer holds you on its step. If JoyCaption is installed, captioning already works — JoyCaption writes the same captions the vision model would, prose or booru tags depending on what you train — so the step is simply a recommendation. With neither installed, the wizard offers an explicit Continue without Ollama that lists what turns off (auto-classify framing, auto head-crop, Test Studio Describe & Enhance, the bank's "Describe filter", the vision route of watermark detection, short captions) versus what stays on, then remembers the choice so it stops asking. Starting Ollama at any point cancels the skip automatically — the flag never hides the state of an Ollama that is running, which is why an Ollama that answers but has no model pulled still shows that gap.
Test checks end-to-end: that Ollama is reachable and the configured model is actually pulled.
LM Studio is a local model server with its own desktop app. Two differences from Ollama shape everything here:
-
Its server can be started from here — if LM Studio has been opened once. LM Studio installs a small command-line tool the first time it runs, and LDS uses it: when that tool is found, a stopped server shows ▶ Start LM Studio in Settings ▸ Local tools and on the Setup step. A model you had loaded stays loaded when only the server was stopped (measured, not assumed); if LM Studio itself was closed, the server comes back with nothing loaded and you load a model in its Developer tab. Either way the server is started on the port Settings names rather than whichever one it last used. When the tool is not found the card says where the switch is instead (LM Studio ▸ Developer ▸ Start Server) — a button that could not work would be worse than a sentence.
-
It only serves a model that is already loaded — so LDS loads it. JIT loading is off by default, and a freshly installed LM Studio answers every list request while refusing every generation. LDS therefore loads the model itself: automatically the first time captioning, framing or a prompt helper needs it, or from the ⏬ Load the vision model button (Setup and Settings ▸ Local tools). It loads the configured model, else the downloaded vision model; a model LDS loaded is one LDS may also unload later to hand the GPU to ComfyUI, while a model you loaded is never touched. Models can be downloaded from here too: the LM Studio card (and the Setup step) has a download field — give it a model id (
qwen/qwen3-vl-4b) or a huggingface.co model URL. The download job runs inside LM Studio itself, so closing the page or restarting LDS does not stop it, and the same field re-attaches to a running download. Readiness still reports a model is loaded, never the server answered. -
LM Studio URL →
lmstudio.url. The server root. Defaulthttp://127.0.0.1:1234. LM Studio's own Developer tab advertises it ashttp://localhost:1234/v1; either form is accepted — the/v1is stripped before use, because left in place it would both build wrong request paths and make the GPU arbitration refuse every call. -
LM Studio model →
lmstudio.vision_model. Select a model reported by LM Studio, or keep Automatic — let LDS choose (stored as an empty value). Automatic selection prefers loaded models, prioritizing known vision models, then falls back to an available model. An explicit selection keeps LDS on that model. The list does not load models; existing loading behavior applies when you run a task. Choose a model that supports images for vision work. -
Images analysed at once →
lmstudio.vision_concurrency. Same meaning as the Ollama dial, kept separate because the two servers do not take the same load: LM Studio serves as many parallel requests as its own Parallel setting allows, and going wider here than that gains nothing. -
Keep the vision model warm →
lmstudio.vision_keep_warm_seconds. Honoured differently, and worth knowing: Ollama takes a per-request keep-alive, LM Studio has no TTL at all and holds a loaded model until something unloads it. So under LM Studio this value is how long LDS waits before actively unloading — and unlike Ollama, that unload genuinely frees the VRAM. -
LM Studio API key (optional) → the
LMSTUDIO_API_KEYsecret, set from the card itself, and only needed if you turned on authentication in LM Studio. It is a secret, not a config field: like every other credential here it lives in the app's secret store rather than inconfig.json, so it never comes back out ofGET /api/settingsand never appears in a pasted diagnostic. Empty by default, and most local setups never need it.
Test checks end to end: reachable and a usable model is loaded.
-
ai-toolkit directory →
aitoolkit.dir. The folder containing ai-toolkit'srun.py. Default empty. Test validates it and unlocks training + JoyCaption captioning. Test also runsimport torchon the chosen interpreter — a folder that looks right but is paired with a Python that has no training dependencies used to pass this check and then fail every run. ⚠️ This is ai-toolkit's folder and ai-toolkit's Python, not the Studio's. The app you are using has its own.venv; the ai-toolkit folder has its own venv, the one carryingtorch. The Next.js UI shipped inside ai-toolkit (ui/, port 8675) is unrelated — this app never launches or reads it. See What is running on your machine.-
Python interpreter (optional) →
aitoolkit.python. Default empty = auto-detect avenv/or.venv/next torun.py. Fill this with the full path to the interpreter ai-toolkit should run with whenever there is no venv folder for the app to find — conda, uv, the system Python, or a portable / embedded build that ships its ownpython_embeded\python.exe(several community install scripts do exactly that). Examples:C:\miniconda3\envs\aitk\python.exe,C:\ai-toolkit\python_embeded\python.exe. A venv is one way to give ai-toolkit a Python, not a requirement — when Setup finds a plausible interpreter inside the ai-toolkit folder, it offers to fill this in for you in one click. Nothing ever fills this field on your behalf: it is set by that one-click offer, or typed here. A value here always wins over auto-detection, so a wrong path silently shadows a perfectly good venv — which is why a training launch now refuses, naming the path, when the interpreter set here cannotimport torch, and offers the working venv it found next torun.pyinstead (reported by strouder, GitHub #19). On Windows, watch out for…\AppData\Local\Microsoft\WindowsApps\python.exe: that is usually the Store alias, not a real Python. See also the supported Python versions — ai-toolkit wants 3.11.
Under Advanced: ai-toolkit overrides, three optional path overrides (all default empty → derived from the ai-toolkit directory):
-
Datasets directory override →
aitoolkit.datasets_dir(defaults to<dir>/datasets). -
Output directory override →
aitoolkit.output_dir(defaults to<dir>/output). -
Hugging Face cache override →
aitoolkit.hf_home(defaults to a cache under the ai-toolkit folder). Point this at an existing HF cache to avoid re-downloading base models. It moves the cache only: ahf auth logintoken stored in your default Hugging Face home stays in use, so relocating the cache never de-authenticates you on gated bases.
Settings for how captions are produced and how the quality tools behave.
What happens to a photo the moment it enters a dataset. The default now keeps the source as the master file; a training launch creates its own disposable working copies. This follows Qeeyana (Reddit) asking: "Images added to 'dataset' are automatically normalized to 1024. Why? Let me choose not to."
-
Stored encoding →
dataset_import.encoding. Defaultpreserve.Value What is written preserve(default)An un-cropped JPG/JPEG, PNG, WebP or BMP is kept byte-for-byte with the matching extension. A HEIC/HEIF photo or a static AVIF cannot be kept as it is (the trainers do not read them): it is stored as a lossless PNG of the same picture. max_sideis ignored.standardOpt-in normalization to WebP quality 92, with the selected maximum side. highOpt-in normalization to WebP quality 100, with the selected maximum side. Still lossy. losslessOpt-in normalization to lossless WebP, with the selected maximum side. preserveis for a dataset that is also your archive. It does not ask thetrainer to consume an arbitrary source file: at training start LDS writes temporary PNG + caption pairs for ai-toolkit, then leaves the imported master untouched.
Import safety limit — every mode: Before preserve, WebP normalization, or auto head-crop can decode the source, it must fit the Image size budget below (shipped default 64 Mi-pixels and 16384 px per side). A larger file is rejected, and the message names the setting. WebP normalization does not bypass this admission limit.
-
Stored resolution →
dataset_import.max_side. Used only by the three WebP normalization modes. Choose1024,1536,2048,4096, or0= original size. The aspect ratio is always preserved (no square padding) and an image is never enlarged. This output setting takes effect only after the source passes the import safety limit above; normalized output also clamps the longest side to 8192 px — that ceiling bounds what is written by a WebP mode and is deliberately independent of the input budget.
How large a source image any part of the app may decode. Not an import-only rule: dataset import, ZIP and scrape ingest, Bank scan and thumbnails, edits, ComfyUI staging and Ollama vision captioning all read these two keys, so an image you can import is an image you can look at.
-
Maximum total pixels →
image_input.max_pixels. Default67108864(64 Mi-pixels).0= no limit. -
Maximum side →
image_input.max_side. Default16384px.0= no limit.
This is a memory budget, not an encoder limit: a decoded RGB pixel costs 3 bytes (RGBA 4), and an edit or analysis pass can hold a second copy at the same time. So 64 Mi-pixels is about 192 MiB for one decoded RGB buffer and roughly 384 MiB while a working copy exists. It admits every current phone and 35 mm camera master (a 61 MP 9504×6336 frame is 57 Mi-pixels) and ordinary stitched panoramas — the previous fixed 16 Mi-pixels / 8192 px refused both.
What 0 disarms. No limit means a corrupt or hostile file can be decoded
until it fills memory: a few hundred header bytes can claim billions of pixels,
and the app has no second guard behind this one — an unlimited budget also lifts
Pillow's own decompression-bomb threshold, so the choice is real in both
directions. It is offered because "let it through" is a legitimate answer for a
panorama you produced yourself.
Inference workers get it handed down, one lane at a time. Face, aesthetic and
NSFW scoring, semantic indexing and watermark inpainting run in a separate Python
interpreter that cannot import the app or its config, so their shared guard
(backend/infer/bank_image_guard.py) enforces its own fixed 16 Mi-pixel /
8192 px ceiling unless the lane that launched it passes this budget down as
environment.
JoyCaption captioning does. It was the lane where the split actually hurt: an image this budget accepts imports, displays and trains normally, but the captioner refused it one by one — a 89-image dataset of camera masters came back with 37 captions and 52 refusals reading "bank image rejects images above 8192 px per side or 16777216 pixels". Captioning now enforces the budget above (the model sees a 384 px copy either way), and a pass that still has to refuse an image counts it and repeats the engine's own reason in its result line instead of just returning a smaller number.
The other lanes still keep the fixed ceiling. Scoring, embedding and watermark work hold a model and a full decode at once, and nobody has measured what a 64 Mi-pixel input costs them — so they keep the conservative number rather than inherit an unmeasured one. An image above it imports, displays and trains normally; those workers skip it.
Auto head-crop is deliberately different. It changes the picture into a
square head shot, so it creates a derived WebP even when preserve is selected.
The same is true of later edits such as crop, rotate and watermark clean: a
transformed image is not the original master any more.
Changing this is not retroactive. It applies to images imported from now on, so a dataset can hold mixed formats and sizes. That is harmless for training (every trainer buckets and downscales on its own). Existing WebPs cannot be reconstructed into the source files that were discarded by older versions.
What it does NOT touch: generated images, the ≤2048 px copies handed to an image API, and any image you have already curated — those lanes keep their own fixed sizes on purpose.
-
Captioning backend →
captioning.backend. Which captioner writes your captions. Defaultauto.
| Value | Behaviour |
|---|---|
auto (default)
|
Prefer JoyCaption (via ai-toolkit), fall back to the Ollama vision model. |
joycaption |
JoyCaption only. |
ollama |
Ollama vision model only. |
none |
No auto-captioning — you write them yourself. |
auto is a chain, not a coin toss: JoyCaption captions the images it can in one
batch, the Ollama vision model covers whatever is left, and on a Concept dataset
Ollama rewrites JoyCaption's drafts. The two engines write in different styles, so one
batch can come back in two voices. Every caption pass now reports which engine wrote
what — in the toast, and on a line under the caption buttons (Captions ▸ Generate
captions), e.g. “8 by JoyCaption · 4 by Ollama”. That line describes the last pass
of the current session only; nothing is stored per image. Pick a single value above
(or per dataset, in Captions ▸ ⚙️ Options) if you want one voice across a set.
-
Processing device →
watermark.device. Where LaMa inpainting runs. Defaultauto. Options:auto(GPU when available, otherwise CPU),cuda(force GPU — pauses ComfyUI while cleaning),cpu(keep the GPU free). This only affects the LaMa engine. -
Allow automatic crop →
watermark.allow_crop. Default on. When on, a watermark sitting in an outer border band is cropped off (a pure pixel crop — it invents nothing). Turn it off and such a mark is repainted instead (with LaMa or Klein per the chosen engine) rather than cropped. The exact same preference is editable inline in the workspace's Clean bar — it's one shared value, so changing it in either place changes both.
Honest note on engine choice. The LaMa (fast) vs Klein (quality) engine is not a Settings toggle — it's a per-batch picker in the Clean bar and a per-image choice in the review lightbox. watermark.device above governs LaMa only; Klein cleaning runs through ComfyUI.
And the two do different things to your file. LaMa repaints the marked zones and leaves every other pixel exactly as it was. Klein works in two steps: the detected zones are erased on the photo (LaMa, or OpenCV when LaMa is not installed), and then the whole photo is re-rendered with the instruction remove watermark. The erasing is what stops the model handing your mark back; the whole-photo pass is what also clears a mark tiled across the frame, or one the detector boxed in the wrong place. It regenerates the rest of the image at the same time — the frame travels at 2 MP at most by default, and comes back at your file's own dimensions unless you say otherwise (both are dials now, see below). There is no zone-only Klein clean any more; ↩ Restore original is the way back from a render you don't like. The ✦ Repair action is unaffected — a repair you aim at a drawn box still leaves everything outside it byte-identical.
The Klein clean has three dials of its own, and they are not on this screen: they sit next to the Klein model choice in the Bank's Level 3 panel and the dataset's Clean bar, because that is where you judge the result. Both surfaces edit the same stored values, so setting one arms the other — and the review lightbox's per-image Clean uses them too.
-
Prompt sent to Klein →
watermark_clean.klein_prompt. Defaultremove watermark. The instruction Klein is actually given when it re-renders the photo — visible now, and editable, so a mark that survives leaves you something to turn. Enter saves; Reset to default puts the three shipped words back, and an empty box means the default rather than an empty instruction. Short beats long here: the long reconstruction sentence belongs to ✦ Repair, and asked to clean a whole photo Klein answers the short instruction measurably better. This is not the Repair prompt and never becomes one — a prompt typed here can never turn your clean into a zone-only repair. -
Processing size →
watermark_clean.klein_max_mp. Default2.0MP, offered as 1 / 1.5 / 2 / 3 / 4 and clamped to 0.5–4.0. How large the photo is scaled to before Klein re-renders it. Higher means finer regenerated detail, and more VRAM and more time — roughly with the pixel count, so 4 MP is about twice 2 MP's work per step, on a card that is probably also running ComfyUI. 4 MP fits a 24 GB card; on a smaller one, or with a training run alongside, stay at 2. A photo already smaller than the setting is sent as it is: nothing is ever enlarged, so raising this changes nothing for small images. This dial belongs to the 🧽 clean only — the ✦ Repair lane keeps its own fixed 2 MP, because it composites its render back and gains nothing outside the mask. -
Write back →
watermark_clean.klein_output. Defaultoriginal.originalresamples the render back to your file's own dimensions, which is what has always shipped and what keeps a clean from changing the shape of a dataset image.renderwrites the render as it came back, with no second resample — it keeps that detail, and the file changes dimensions: it comes out smaller than your original whenever the photo was above the processing size above. On a photo already under that size the two modes produce the same file. ↩ Restore original is the way back either way.
What actually ran is in the log. Because the prompt is editable, the source no longer answers "which words cleaned this batch?" — so every clean writes one line to 🪵 Server log (Settings ▸ Maintenance) naming the prompt it sent, the size the frame travelled at and the write-back mode, before the render, so a run that fails or times out still says what it was asked to do.
Klein removal is not guaranteed, because it is a generative pass rather than a mask. On the shipped settings a wall-to-wall tiled mark scored 0.69 before and 0.16 after, with its twelve detected zones down to none, and a photo carrying seven distinct logos came back with all seven gone. What can still survive is a mark nothing detected — no zone means nothing erased it, and the model is free to keep it. Lowering the denoise does not help — at 0.7 and 0.4 nothing at all is removed, which is why that value is fixed at 1.0 and is not offered as a dial.
Why the erasing step exists, measured. Without it — the whole photo alone, no zones erased — the model does not always delete a mark it can still see: it redraws it. On that test photo a round logo came back as a plausible moon in the sky, twice in one pass, in three runs out of three. The watermark detector then counts zero zones on the result, so the app reports the image cleaned and nothing downstream disagrees. Erasing the zones first removed every trace of it. Klein cleaning therefore needs the ML extras (the same install LaMa uses) — without them the pass reports itself unavailable and skips the row rather than rendering blind.
Two thresholds on the 0–1 face-similarity score (InsightFace), which badge each image against your reference.
-
Face score — green threshold →
face_scoring.green. At or above this, the image is a strong match (green). Default0.50. -
Face score — orange threshold →
face_scoring.orange. At or above this but below green, it's borderline (orange); below it, red. Default0.45.
Raise them for a stricter set, lower them if good shots are being flagged too harshly.
These thresholds do nothing on an Anime dataset. InsightFace is trained on photographs and cannot read a drawn face, so face similarity is refused outright when the dataset's subject type is Anime — the 🎭 Analyze faces button, 🎯 Auto-triage, Best epoch and the Test Studio's face scoring all say so instead of producing numbers nobody could measure. There is no override, on purpose: the subject type is the switch. If the dataset really is photographic, set it back to Human and everything scores again — scores from an earlier pass are never deleted. Head-cropping a reference is unaffected: it runs on the vision model (Qwen3-VL), which reads a drawn head fine.
Read-only source is set per bank, in the bank creation form or beside an existing bank's name. It blocks deletion and imports into its source folder, while keeping curation and copies into datasets available. Existing banks default to off. See Read-only bank sources.
Thresholds for the 🗃️ Bank quality flags. Every scanned image stores its raw scores, and the flags are recomputed against these values on every read — so changing a threshold re-sorts an already-scanned bank instantly, with no rescan. (The two exceptions are noted below.)
The same twelve values are editable from the Bank itself — open 🎚 Filter thresholds above the grid, under the filter chips they decide. It is one setting seen in two places, not a copy: editing either one writes
config.bank.<key>and therefore applies to every bank. The Bank panel additionally previews how many images a candidate value would flag before you save, and groups the controls by intent. See Using the app → Tune the Bank filter thresholds.
-
Sharpness minimum →
bank.sharpness_min. Variance of the Laplacian (the classic focus measure) under this = flagged 🌫 blurry. Default100. Raise it to be stricter about focus, lower it if artistic soft shots get flagged. Measured region by region, and the score kept is that of the sharpest regions — so a bokeh portrait (sharp subject, creamy background) is not flagged just because most of the frame is deliberately out of focus. A bank scanned before that change carries the old whole-frame scores, which are lower: re-run the Quality pass on it to compare like with like. -
Noise maximum →
bank.noise_max. High-frequency residual (RMS vs a Gaussian blur) over this = flagged 📺 noisy. Default15. Heavily textured images (foliage, fabric) score high by nature — this is a flag to review, not a verdict. -
Uniformity minimum →
bank.uniformity_min. Grayscale spread under this = flagged ⬜ flat (solid colors, black frames, empty screenshots). Default12. -
Minimum side (px) →
bank.min_side. Smaller image side under this = flagged 📐 small. Default768— the same bar as the dataset import guard, because trainers only ever downscale. -
Real-detail minimum →
bank.detail_min. Share of the stored size (0–1) that still carries real picture, under which an image is flagged 🫧 soft detail. Default0.72— on a real 36 000-image bank that selects the softest ~3%, and it sits below the 10th percentile of images measured to be genuinely full-resolution, so a sharp photo does not trip it. What it measures: the scan shrinks the image and rebuilds it at a ladder of sizes; the smallest size that still reconstructs it is where the picture actually stops. An image enlarged from 512 to 2048 rebuilds perfectly from a quarter-size copy, so it reads ~0.5 and the bank says "2048 px stored · ~512 px of real detail". What it does NOT measure: which of the possible causes it was. Motion blur, an out-of-focus background and aggressive denoising remove the same detail and read the same way — treat it exactly like the sharpness score, as a shortlist to look at, never as proof an image was enlarged. It also cannot see nearest-neighbour enlargement (blocky pixels are real detail) and it under-states large enlargements, so the pixel figure is a rank, not a measurement of the original file. -
Black-bar maximum →
bank.bars_max. Share of the frame (0–1) that may be flat black letterbox/pillarbox before an image is flagged 🎞 black bars. Default0.04; it caught ~4% of the reference bank (screenshots of videos, stills padded into a square). Those bars survive a training crop, so they are worth seeing. -
Duplicate distance →
bank.dup_distance. How many of the 64 perceptual-hash bits two images may differ by and still be grouped as ≈ near-duplicates. Default8(the same hash and distance the dataset import dedup uses). Applies at the next quality scan (groups are rebuilt then). -
Same-person similarity →
bank.face_threshold. Cosine similarity at or above which two faces cluster as the same person in 👥 Group by person. Default0.45. Raise it if different people get merged into one cluster; lower it if the same person splits into several. Applies at the next face pass (embeddings are cached, so re-clustering is fast). -
Aesthetic minimum →
bank.aesthetic_min. LAION aesthetic score (~1–10) under which an image is flagged 💔 low aesthetic — the "keep the nice ones" cut. Default5. Only images the ✨ Score pass reached carry a score; an unscored image is never flagged. The score also drives "keep best" on duplicate groups (the nicest-looking copy wins). -
NSFW maximum →
bank.nsfw_max. NSFW probability (0–1) over which an image is flagged 🔞 NSFW, to split a mixed SFW/NSFW dump. Default0.5. Set by the ✨ Score pass; a review flag, not a verdict. -
Same-style similarity →
bank.style_threshold. Cosine similarity on the CLIP image embeddings at or above which two images share a visual 🎨 style (screenshots/memes cluster apart from photoreal) in the ✨ Score pass. Default0.6. Applies at the next scoring pass — the embeddings are cached, so re-clustering at another threshold costs no inference at all: the pass does not even load the model. It is not instant, though, and the cost is the grouping itself, which compares every image with every other: ~8 s over 5 000 images, ~3 min over 23 000 (measured). Stopping the pass during that phase leaves the previous grouping in place rather than writing half of a new one — the ids are one numbering of the whole bank, so half of them would collide with the other half. A measured limit, on a big single-subject bank: the grouping is transitive (A groups with B and B with C puts A with C, even if A and C look nothing alike), so on a bank whose images are all of one person a chain of near-neighbours can merge everything into one group. Measured on a 25 058-image bank at the default0.6: one group holding 25 056 of them — and raising the threshold does not open a middle ground so much as move the cliff (0.8 → one group of 24 735; 0.9 → one of 15 066; 0.95 → the grouping shatters into 19 716 groups, 17 137 of them single images). The end-of-pass line now states the size of the biggest group against the total, so this is visible without opening the database. If your bank is varied, the default behaves; if it is one subject shot over and over, expect the 🎨 style chip to be close to useless whatever the threshold, and use ✂ Find crops & variants (a much tighter, per-pair comparison) for the grouping you probably wanted. -
Semantic duplicate similarity →
bank.semantic_dup_threshold. CLIP cosine similarity at or above which two images are grouped as a ✂ semantic near-duplicate — a crop or re-compressed variant of the same shot that the perceptual-hash ≈ Duplicates (stage 1) misses. Default0.96. With CLIP selected it needs ✨ Score first. SigLIP 2 uses the separate conservative starting valuebank_semantic.siglip2_semantic_dup_threshold; it deliberately does not inherit the CLIP cutoff, and should be reviewed/calibrated on your Banks. Re-running the grouping reuses the selected cache; no image inference. Re-running it over an untouched Bank does not even do that: the pass compares a signature of everything it reads — the embedding cache, the rows, their style blocks, the threshold — and answers already up to date — N group(s), nothing changed in a second. The launch window also shows when it last ran and what it found. The limit of that shortcut, stated plainly: the signature is made of state, not pixels, so an image replaced on disk at the same path while its cached embedding stayed behind is the one change it cannot see. That cache is stale by then and the full pass is what detects it — POSTforce: trueon/api/bank/<id>/semantic-dedupto skip the shortcut and redo the work. -
Which Python runs ✨ Score →
bank_scoring.python. Setup prepares a dedicated environment with CPU-only PyTorch and records it when no external choice must be preserved. Open Bank → ⚙ Passes → Manage Score Python… to inspect the effective path, select the managed environment or reuse another Python. The management button remains available even when CUDA is detected; changing Python is disabled during a pass. Discovery checks the required imports (torch,open_clip,transformers,timm,numpy, Pillow). CUDA detected · calculation not tested means PyTorch reports CUDA availability, not that cuDNN or model execution works. The separate Test calculation button runs a small convolution and attention check without downloading models or starting a pass; a success does not verify every model or the whole scoring pass. A missing dependency is named with a command you may run yourself; the app never installs into an external environment. You can also enter an interpreter or its environment folder, including paths with spaces and accents. Install / ↻ Reinstall always targets the managed environment and preserves an external selection. After a repair, use ↻ Check again, then Use managed environment to switch explicitly. Back to the app default only clearsbank_scoring.python, which falls back to the app Python; it does not select the managed environment. See Using the app ▸ Make Score use a GPU Python you already have.
Not a setting, but it lives with them: the 🎨 Pick diverse popover in a
bank carries a Skip the odd ones out slider (0–100%, default 50%) next to
its How many. It is a per-click control — chosen where you use it, not stored
in config.json — because it is a property of the selection you are asking for,
not of the bank. "Most diverse" is farthest-point sampling, which by construction
favours the most isolated images; the slider discounts an image for being alone
in the bank so memes, screenshots and stray photos of someone else stop winning
the first picks, while anything as typical as the median of the bank is left
untouched (it cannot pull the selection towards look-alikes). 0 reproduces the
pure-coverage behaviour this button had before the slider existed — the change
of default does change what a given bank returns. See Using the app ▸ Curate down
to the right subset.
-
Watermark detection →
watermark_detect.backend. Which engine 🧽 Find watermarks uses, on datasets and banks.auto(default) takes the optional watermark-detector extra when it is installed — ~0.14 s per image and it returns a score — and the local vision model (Ollama or LM Studio, whichever is configured) otherwise; that is the behaviour that shipped, so leaving this alone changes nothing. Both Find-watermarks windows now carry a Detection engine selector for this key, with a line naming exactly what the next scan will run.detectorandvisionpin one engine. A pinneddetectorwith the extra not installed does not fail the scan: the vision model runs it and the app says so, naming Setup ▸ Quality tools ▸ Watermark detector. The two engines disagree at the margins, and only the detector can flag an image without a position — those are counted apart and 🧽 Clean leaves them for 🔍 Review instead of marking them failed. Applies at the next scan; images already dismissed as false positives are only re-judged by ⟲ Rescan incl. dismissed. -
Watermark detector sensitivity →
watermark_detect.threshold. The score (0–1) at or above which 🚩 Find watermarks flags an image, when the optional watermark-detector extra is installed (Setup ▸ Quality tools). Default0.94. Applies at the next scan. This number is measured, not chosen, and it is deliberately nowhere near the 0.5 a probability normally implies: the classifier's scores sit hard against 1. On a 110-image sample drawn from a real 29 759-image bank and labelled by eye (2026-08-03, CPU), a threshold of 0.5 would have flagged 52 of the 55 clean images; 0.94 flagged none of them and still caught 54 of the 55 marked ones. The clean images topped out at 0.939 and the marked ones bottomed out at 0.929, so the two populations overlap by about 0.01 — there is no setting that is perfect, only a knee. Lower it towards 0.92 to catch the faintest marks and hand-check a few clean images; raise it towards 0.96 to miss a mark rather than crop anything by mistake. -
Watermark detector interpreter →
watermark_detect.python. Written by the installer, and now also pickable from the Bank's 🚩 Watermarks panel (⚡ Use a GPU Python I already have — same dialog family as ✨ Score and SigLIP 2, see the Guide). The installer pins its own CPU-torch environment here, so on a machine with a card the scan stays on the CPU until you point this at a CUDA Python (ComfyUI's, ai-toolkit's). Empty means "use the Bank scoring interpreter, then the app's own". -
Watermark detector weights →
watermark_detect.models_root. Empty meansdata/models/watermark_detect(~0.9 GB, downloaded once at install). -
Watermark detector device →
watermark_detect.device(auto|cuda|cpu). The extra installs CPU-only torch, so this is CPU unless you pointwatermark_detect.pythonat an environment with a CUDA torch. The pass only takes the exclusive-GPU window (which unloads ComfyUI and blocks a training start) when it will actually use the card. -
Locate the mark →
watermark_detect.locate(default on). Runs the second model on the images the first one flagged, to record where the mark sits. Off means images are flagged with no position, and neither ✂ Auto-crop nor 🧽 Inpaint can route on them — only worth turning off on a bank you intend to filter rather than clean.
The ✨ Score pass (aesthetic · NSFW · style) needs the Bank scoring extra (Setup ▸ Quality tools). 🚩 Find watermarks runs one of two ways: the watermark detector extra when it is installed (~0.14 s per image, and it does not need Ollama at all), otherwise the vision model from Captioning (~1.7 s per image). Installing the extra only ever adds the faster route — with nothing installed the pass behaves exactly as it always has. Both are detection-only: the bank never edits your source files.
Defaults for new local runs. Cloud training settings belong to its plugin under Plugins.
-
Default training family →
training.default_family. The model family preselected when you start a new run. One ofzimage,sdxl,krea,flux,flux2klein,anima. Defaultzimage. Purely a starting point — you can switch family per run.animatrains the open Anima anime model on its public base (no gated download); it is local-only for now (needs an up-to-date ai-toolkit + diffusers — cloud training arrives once the GPU pod image is verified). Anima is the one family with hybrid prompting: booru tags and natural language are both first-class on it, so the caption-style guard says nothing there — prose is merely the preselected default, and a booru-captioned Anima dataset trains without being flagged or forced. Every other family keeps its single expected form (SDXL = booru tags, the rest = prose).
Advanced options ▸ Speed. The recipes are calibrated so a 12 billion parameter model fits in 24 GB. Fitting is not free, and until now the price was not yours to refuse. Four controls, all of them defaulting exactly where they were:
- Batch size — images per optimizer step. Default 1, which is what fits. 2 or 4 trains more images per step and is strictly faster per image when the card has the room. It is not the same as gradient accumulation, which fakes a bigger batch without the memory and without the speed.
-
Quantisation backend — only meaningful while quantisation is on above.
qfloat8(the default),float8andint8quantise the weights only: the weight is promoted back to the compute type before every matrix multiply, so they buy memory and cost a little speed.convrot8quantises the activations too and performs the multiply in int8 on the tensor cores, which is the one that can be genuinely faster. It needs an Ampere card or newer. - Gradient checkpointing — on by default. It recomputes activations instead of keeping them: that is what makes a 12B model fit, and it costs compute on every backward pass. Switching it off gives the time back and spends VRAM.
-
Compile the model (experimental) — passes
compileto ai-toolkit. It is what makes an 8-bit path pay, because the compiler can fuse those kernels. Treat it as experimental here: ai-toolkit removed compilation from its own Krea 2 model file because it fights gradient checkpointing and adapter swapping. If a run dies at the first step, this is the first thing to turn off.
A run records which of these it used, so two runs of the same dataset that differ only by speed settings stay comparable afterwards.
The Base and variant chosen in Advanced training options belong to the model family they were chosen under, not to the dataset as a whole. Switching LoRA type hands you that family's own base — the official one the first time you pick it — and switching back restores what you had. Nothing is discarded: each family's choice is remembered on the dataset.
This matters because the choices are not interchangeable. A Z-Image base is a
ComfyUI merge name that gets converted to the diffusers layout first; Krea 2,
FLUX.1 and FLUX.2 Klein take an absolute path to a .safetensors of their own
architecture. Before this, one dataset held one base for every family, so a base
picked for Z-Image stayed attached to a Krea 2 run — where it was silently ignored
in favour of the official base, while the panel's summary line and the cloud
dialog both went on advertising it.
A base that provably belongs to another family (found on datasets created before this change) is reported as such in the panel, is not used, and is not offered for upload to Hugging Face by the cloud dialog.
The Base dropdown under LoRA type = Krea 2 offers the official base and
every Krea 2 checkpoint found in your ComfyUI unet / diffusion_models folders,
including the roots declared in extra_model_paths.yaml — the same scan the Test
Studio uses. That is how a full model one of your own runs delivered, or a
community Krea 2 build, becomes something you can keep training on.
The value stored is the absolute path of the file, not the ComfyUI folder name the Studio uses, because the trainer identifies a custom base by its being an absolute path and a cloud pod has to receive the actual file. A checkpoint the app can list but cannot resolve to a file on disk is left out of the dropdown rather than offered as a name a run would ignore.
Each entry says what its format costs before you pick it:
- no tag — full precision, nothing to report;
-
· fp8 cast— the weights are stored in fp8 under the tensor names a full-precision file already had, with nothing extra. The trainer up-casts it as it loads, and selecting it shows how many of the file's tensors are quantized and how many significand bits that leaves. The lost precision does not come back. The tag reads the packing, not the architecture: a file can be tagged this way and still be refused at load for carrying a tensor the model family does not declare; -
· packed export— a ComfyUI scaled-fp8 /comfy_quant/ int8 repack. It carries decompression tables as extra tensors that a trainer's strict load rejects, so the load fails outright. Selecting it is refused, and training is blocked until another base is picked. Use the bf16/fp16 master instead — a full-model run keeps it next to its fp8 twin and the Checkpoints panel lists it.
With no ComfyUI folder configured the list falls back to the official base alone, and says so.
Used only by Concept datasets that switched Mask faces on in Advanced training options (see the dataset guide, §8). It re-weights the training loss over detected faces so the concept learns the act rather than the identities in your photos — it never alters your images. Both knobs are exposed rather than frozen because no published measurement exists for the right value; preview the effect on your own images from the training panel.
-
Head coverage (face box ×) →
face_mask.expand. Face detection returns a box running from the eyes to the chin; this grows it around its centre (biased upward, to catch hair) into a head. Default 2.0, clamped to 1.0–3.0. Higher covers hair and jaw, lower stays tight on the face. 2.0 matches the only published default for this detector/mask combination. -
Loss weight kept on faces →
face_mask.min_weight. How much the masked area still counts, from 0 (nothing) to 1 (unmasked). Default 0.1, clamped to 0.05–1.0. Lower pushes identity out harder. It deliberately cannot reach zero: an area worth nothing isn't ignored, it's unpenalised — the model may render anything there at no cost, degraded anatomy is reported right below this floor, and a fully masked close-up would divide by zero in the trainer's own mask normalisation and kill the run.
Changing these does not affect the person-masking used by Character datasets; that keeps its own historical weight.
It needs face detection installed, and that is optional. The detector is
InsightFace, the same optional extra face-similarity scoring uses — it is filed
under the face_scoring key (hence face_scoring.python and
face_scoring.models_root in Settings ▸ Local tools), but nothing installs it
for you. When it is missing, the Mask faces option says so and offers a
one-click install right there (~400 MB, a few minutes); the rest of the app is
unaffected and works exactly as before. If you launch a run with Mask faces
still on and the detector absent, the pre-launch report warns that the run would
train unmasked and asks you to confirm — it never blocks, and never trains
unmasked without telling you first. On a Python outside 3.10–3.12 InsightFace
publishes no wheels: the option explains that instead of offering an install that
could only fail, and points at face_scoring.python so you can aim it at a
separate 3.10–3.12 interpreter.
The built-in Krea 2 Raw · LoKr likeness preset is deliberately narrow: it is shown only for a Character dataset on a compatible Krea 2 Base/Raw variant. It turns a reported Krea 2 Raw LoKr recipe from the Stable Diffusion community into a named, inspectable starting point — not into a promise that a different person, image set, captioning style or checkpoint will match at the same step.
The post linked a full Pastebin configuration, but that Pastebin has since been
deleted. LDS therefore records only the values the post actually reports:
LoKr factor 16, 768 px, Automagic2 with initial learning rate
1e-4, Sigmoid timestep weighting, Balanced content/style mode,
Differential Guidance at scale 3, and a checkpoint/preview cadence of
250 steps. The Krea-only Expert controls show those values plainly, and the
run snapshot carries the factor, content/style mode and Differential Guidance so
you can compare an experiment later instead of trusting a remembered recipe.
The post does not publish the LoKr linear rank or alpha. LDS keeps its existing Krea Character 32/32 choice rather than inventing a rank/alpha pair and presenting it as sourced. Likewise, the reported 3000 total steps are not forced by the preset: LDS keeps its adaptive step policy so a small dataset is not silently overcooked. To reproduce that target intentionally, type 3000 into the Steps box for that run; leave it empty to use the adaptive policy. Treat the intermediate saves — including the early ones — as candidates to compare in Test Studio, not as proof that a specific step will be best.
One rule applies to all of them: they are stored per DATASET, not per family. Switching LORA TYPE keeps every advanced setting you had — which is what you want for rank, optimizer or resolution, and what you do not want for the two settings below, whose right value is different on every family. Those two are handled explicitly:
-
Memory saving carries over, and is now said out loud.
quantize/quantize_te/low_vramare a statement about your card, and your card doesn't change when the family does — so the values follow you. What changes is whether the card still suffices: switching them off on Anima or SDXL (2B, where off is the calibrated default) and then moving to Krea 2, FLUX.1, FLUX.2 Klein or Z-Image used to build an unquantised 12B run in complete silence. Both the panel and the pre-launch check now name which saver is off, what that family needs without it (see the estimates below) and what your card reports. It stays a warning, never a blocker — a big card legitimately runs unquantised — and it is deliberately not dropped on the cloud lane, where the mistake bills rented GPU-hours. The warning is also provenance-blind: unticking a box directly on Krea 2 with a 24 GB card gets the same sentence as inheriting it from Anima, because it is the same danger. -
Timestep weighting is remembered per family instead.
sigmoidis Z-Image's and FLUX.1's canonical flow-matching schedule,linearis Krea 2's,weightedis FLUX.2 Klein's and Anima's — the value has no meaning outside a family, and carrying it over changed the LoRA that came out with nothing at all to observe afterwards. Each family now keeps its own choice: switching hands the incoming family its own value (or Auto, its canonical default, if you never set one there), and coming back finds yours exactly where you left it. Nothing is destroyed and nothing is asked. Existing datasets are untouched — a dataset that never changes family keeps every setting it has, byte for byte. -
Resolution stays global on purpose. 768 and 1024 mean the same thing on every family, so remembering it per family would mean silently raising your 768 back to 768+1024 on a switch — a new silent change to fix an old one. The one combination that costs you (1024 on a 12B with a small card) is a pre-launch row instead, and that row no longer tells you to "drop the resolution to 768" when you are already at 768.
-
Memory saving — three switches (
quantize,quantize_te,low_vram) that used to be hard-coded. The defaults have not changed: Z-Image, Krea 2, FLUX.1 and FLUX.2 Klein quantise the base model and the text encoder toqfloat8and keep the base model in system RAM while it loads and quantises (Krea 2 and FLUX.2 Klein park the text encoder there too), moving it to the card only for the run itself (low_vramis a loading strategy, not block streaming during the steps), which is what makes a 12B model train on a 24 GB card; Anima and SDXL are small enough to run without any of it. Turning them off trades VRAM for precision and speed — worth it only if your card is bigger than the target. As a rough order of magnitude with the savers off: Z-Image ≈ 18 GB, FLUX.2 Klein 4B ≈ 14 GB, FLUX.2 Klein 9B ≈ 24 GB, Krea 2 / FLUX.1 ≈ 30 GB (estimates: bf16 weights plus headroom, not a measurement on your exact card). The panel detects your GPU and says which side of that line you are on; if it can't (no NVIDIA card,nvidia-smimissing), it falls back to a generic note and blocks nothing.⚠️ The failure mode is slowness, not a crash. On Windows there is no clean out-of-memory error: the driver silently pages to system RAM and the run creeps along for hours. If a run that used to take 40 minutes is still going after three, put the switches back. The setting also works the other way — a small card can turn quantisation on for Anima or SDXL. It's recorded in each run's snapshot and in the ⎘ Share config, so two runs can be compared honestly. -
Masked training (background at 10%) — on by default, and since the 28/07 wave it is stored on the dataset instead of in the browser you set it in. A person mask is generated for every image (rembg, CPU) and the background only weighs 10% of the loss, so the LoRA binds the identity to the subject rather than to the room. What changed and why it matters: the toggle used to be a
localStoragepreference (trainMasked_v1) that reached the server only as a launch parameter, so the readiness badge could not warn about it, opening the app from a phone or another machine silently reverted to the default, and the value appeared in no run snapshot — two runs differing only by masking looked identical when compared. All three are fixed: it is patched like any other advanced option, stamped into every run's snapshot (local and cloud), and the pre-flight badge now carries a Masked training ready row that says rembg is not installed — this dataset is set to masked but the run trains unmasked before you open the launch dialog. It is a warning, never a blocker (a run without masks is a valid run), and it is not filtered out on the cloud lane: the masks are generated locally and uploaded with the images, so rembg missing here means the paid run trains unmasked. Concept and Style datasets force it off (a person mask erases the recurring concept, and a style must be learned across the whole frame), and slider mode ignores masks entirely — the panel says so instead of hiding the control. Existing datasets do not change behaviour: an untouched dataset resolves to the historical default (on). A browser that had explicitly turned masking off is asked once, in the training panel, whether to carry that choice onto the dataset or keep masking on — nothing is written until you answer, in either direction. -
Dual captions (long + short) — off by default. When on, the run uses ai-toolkit's native
short_and_long_captions: every image trains with both its full caption and a short one (text-side augmentation, so the LoRA leans less on any single wording). The short variant is derived from the long caption the next time you (re-)caption — text-only, via the local vision model, honouring the same kind rules (no trigger; the identity/concept/aesthetic stays omitted) — and you can edit it per image in the ⛶ caption editor. Local training only for now: the cloud pod's dataset upload doesn't carry the JSON caption file the short is read from, so cloud runs train on the long caption alone. Not available on Krea 2 or Anima: those families pre-cache their text embeddings and unload the text encoder, so no second caption can be encoded — the toggle is reported as ignored on the training panel and in the pre-launch check, and the run trains on the long caption alone (issue #22, reported by 1Tomber).
Everything about the disk in one place: which folder holds what, how big it is, and how to put any of it on another drive. Before this tab those answers were scattered across three screens, and the two directories that actually fill a drive — the cloud run staging and the trained checkpoints — were hard-coded and invisible.
Sizes are measured only when you ask. Walking a hundred gigabytes of datasets takes seconds, so 📏 Measure everything is a button, never something the tab does while you read it. Until you press it a folder reads not measured, which is deliberately not the same as empty.
- What lives where — one row per category: dataset images, image banks, bank source images, cloud run staging, the checkpoint store, the trash, the run image archive, backups, the ai-toolkit install, the Hugging Face cache and the app's own build. Each row shows the effective path, what it holds, the free space on that drive, and a movable tag when it can be relocated from here.
Four roots can be pointed anywhere: Dataset images root
(paths.dataset_images_root), Cloud run staging (paths.cloud_runs_dir),
the Checkpoint store (paths.checkpoints_dir) and Video datasets
(paths.video_datasets_dir). All four default to empty →
a folder inside the app's data directory; the field's Reset to default gives
that implicit state back rather than writing today's path in.
Type a path, press Check folder, and the app proves it can write there — by actually writing a probe file, because permission bits lie on Windows. A relative path, an uncreatable folder, or a target inside the folder it would replace is refused with the reason. Then you choose, and nothing happens until you do:
- Move what is already there — every file is copied to the new folder first, and the old one is only removed once the last byte has landed. A progress bar shows files and percentage. If the destination drive has less free space than the folder needs, the button is disabled and says both numbers.
- Start using it empty — the new folder is used from now on. Nothing is copied and nothing is deleted: the old folder keeps its files and the app simply stops looking at them. On a full C: this is often the only choice that fits.
The setting is saved after the files have moved, so an interrupted move never leaves the app pointing at a half-filled folder.
-
Dataset images root →
paths.dataset_images_root. Where dataset images are stored. Default empty →<data dir>/datasets. This folder (and every dataset folder under it) is refused as an image bank source: a bank points at a live folder and can delete from it, so the two must never share files — see Using the app → A bank and a dataset never share files. Moving this root onto a folder an existing bank already uses is not blocked here, but that bank will say so the next time you open it, and its 🗑 Delete rejected will be refused. -
Cloud run staging →
paths.cloud_runs_dir. The working area of cloud training runs: the exported dataset copy, the sample images and the mirrored log, onerun_<id>folder per run. Default empty →<data dir>/cloud_runs. This is the folder that grows to tens of gigabytes, and the one a cleanup empties. -
Checkpoint store →
paths.checkpoints_dir. Where the.safetensorsyour cloud runs produce are kept, onerun_<id>folder per run. Default empty →<data dir>/checkpoints. No cleanup in the app ever removes a file from here — only you can, from the Checkpoints panel or by emptying the trash. -
Video datasets →
paths.video_datasets_dir. The flat folders of.mp4clips (plus their homonym.txtcaptions) that a video bank produces when you promote a selection. Default empty →<data dir>/video_datasets. This is where the video lane actually uses disk. A video bank itself stores almost nothing — only timestamps and one small thumbnail per detected shot — because cutting a clip means re-encoding it, and that is paid once, at promotion, for the clips you kept. - Video banks (working data) → no path setting; it follows the data directory. Holds the shot thumbnails only: never your source videos, which a bank references in place and never writes to, and never the clips, which do not exist until you promote them.
They did not, and it cost weights. "Clean finished runs" trashed the whole staging directory while advertising checkpoint duplicates already imported — but a checkpoint that had never been deployed to ComfyUI had no duplicate: staging was its only copy. Emptying the trash afterwards destroyed it.
Trained files now land in the checkpoint store from the moment they are downloaded, and no cleanup path can reach them. An install that trained before this change is swept once at startup; Move stray checkpoints into the store re-runs that sweep on demand and is safe to press at any time.
- Trash — Open folder and Empty trash. Everything the app deletes goes here first; emptying is the one destructive action, and it asks for confirmation. It lives on the same disk as your data, so a cleanup gives space back only once you empty it.
-
Run image archive — its size, its ceiling, and Clear archive. When a training run is launched, a deduplicated copy of the images it trains on is kept so that comparing two runs can still show an image you have since deleted from its dataset. Copies are content-addressed: relaunching an unchanged dataset stores nothing the second time, and only images that were added or re-edited cost anything. Clearing it keeps your runs, their settings and their caption text — you only lose the ability to look at images that are no longer in their dataset. The ceiling is
provenance.archive_max_gb(see Config-file-only settings); past it, nothing more is stored and the compare panel says the picture is unavailable instead of showing a wrong one. -
Back up everything — not on this page but on the Datasets library: one button archives every dataset, its training history and your settings into a single file (⬇ download or 📂 open folder), and the library's Import backup restores it — datasets come back under Trained, not "Not trained yet". Tick Include trained LoRAs to bundle the (large) trained
.safetensorstoo. API keys and tokens are never included — re-enter them on the new install. See Using the app → Back up everything.
How the app binds and who can reach it. These are the settings that need a restart — the card shows a Running vs Saved banner and a Save & restart to apply button that does it in one click.
-
Port →
server.port. The port the app listens on. Default5050. Change it if something else owns the port (on macOS, port 5000 is taken by AirPlay Receiver). -
Available on the local network — a toggle that flips the bind host between
127.0.0.1(this machine only, the default) and0.0.0.0(reachable from your LAN — phone, tablet, another PC). The token and phone controls below only appear once this is on; withLDS_REQUIRE_LOCAL_TOKEN=1the token controls are always there. -
Require an access token →
server.require_token. Default off — a home LAN is treated as trusted, so LAN access is open and there's no token to type on a phone. Turn it on to demand a token from remote devices. Localhost normally bypasses this gate;LDS_REQUIRE_LOCAL_TOKEN=1requires authentication there too and cannot be turned off from this setting: the switch is forced on and locked, with that reason shown. WhenLDS_PUBLIC=1this is forced on and locked — the switch is disabled with that reason shown, and a public bind is served with the gate on unlessLDS_ALLOW_UNAUTHENTICATED=1is set (see the environment variables above). -
Access token →
server.access_token. Shown only when the token gate is on: a read-only field with Generate new token and Copy. It's persisted, so it survives restarts. Openhttp://<machine>:<port>/?token=<token>once from the remote device and a signed session cookie takes over.
With local token protection enabled, use the token link printed by the launcher,
the browser tab opened by start.bat / start.sh, or the token shown on this
card; its phone links and QR code carry the token the running server accepts.
The browser remembers the authenticated session; the initial token is removed
from the address bar. Scripts must send X-LDS-Token or Authorization: Bearer
on their requests. Generate new token applies as soon as you save, no restart:
the tab that saved stays signed in, and every other session needs the new link. A
token set with LDS_ACCESS_TOKEN stays in charge: the card shows it and offers no
Generate new token; change the variable, then stop and start LDS.
This protects against anonymous local HTTP calls, not a compromised
process that can read your token, .env, or session signing key. Use a separate
account with restricted file permissions or stronger isolation for that threat.
- Open it on your phone — a card with a scannable QR code and copyable URLs built from this machine's real LAN IP (and Tailscale IP, if present). No guessing which address to type.
Trap: if you launched via start.bat with LDS_PORT set, that variable can override the port in your config. The in-app Save & restart pins host and port for the relaunch, precisely so the restart lands on the port you chose rather than the one the script forced.
Keeping the app itself healthy: updating it, and getting a bug report out of it. No setting lives here, only actions. Everything about the disk — the trash, the run image archive, the dataset root and the folders that fill a drive — moved to Storage above, where those questions are answered together.
-
Updates — Check for updates and Update & restart, plus a see what's in this update compare link. The button adapts to how you installed. A git checkout fast-forwards to the latest commits. A packaged (ZIP) install announces the release and its size (Update to vX — download ~XX MB) and shows a live progress bar while it downloads and installs (a release ZIP is far larger than a git pull), then backs up the current files and swaps in the new ones — keeping
data/,config.json,.envand your.venvuntouched — and restarts. A mid-way failure rolls back automatically, so a broken download never leaves you with a half-updated install. If the app can't identify a downloadable release (no ZIP asset, or offline), the button steps aside and links to the releases page instead of promising an update it can't perform. -
Back up everything — not on this page but on the Datasets library: one button archives every dataset, its training history and your settings into a single file (⬇ download or 📂 open folder), and the library's Import backup restores it — datasets come back under Trained, not "Not trained yet". Tick Include trained LoRAs to bundle the (large) trained
.safetensorstoo. API keys and tokens are never included — re-enter them on the new install. See Using the app → Back up everything. - Diagnostic report — a one-click, paste-safe report for bug reports: it carries the version, capability status and a log tail, with no secrets and file paths reduced to booleans (present/absent). Safe to drop into Discord or a GitHub issue.
- Server log — a live tail of the server log, with Copy all, for when you need to see what just happened.
Settings → Maintenance → Optional usage statistics. This is entirely optional and off by default, including on existing installations. LDS sends no usage statistics until you choose Share usage statistics. Choose No thanks to save a refusal and dismiss the invitation; LDS remains fully usable either way. If sharing is unavailable on your installation, the settings card says so and no invitation appears.
The statistics help the LDS maintainer understand which features people return to and which operations fail. When enabled, the allowed information is:
| Information | Purpose |
|---|---|
| Random installation ID and days of activity | Count participating installations and returns over time. |
| Event timestamps and first active day | Group activity by date and measure returns. |
| Coarse feature names, such as Datasets, Bank or Training | See where development effort is useful. |
| Supported operation outcomes, error categories and failure stages | Find reliability problems without uploading an error message or log. |
| Model family, captioning engine and local/cloud category | See which technical combinations need attention, without model filenames, server addresses or hardware details. |
| LDS version and operating-system family | Identify version or platform differences. |
| Approximate duration ranges | Spot slow operations without recording their contents. |
| Production or test environment | Exclude the maintainer's synthetic tests from product statistics. |
Expanded sharing requires a new choice, even if you previously opted in. A saved refusal stays saved. With the new choice, supported outcomes include local and cloud training, plus Studio generation after its image has been saved. Operations recovered after a server restart are not counted; earlier activity and missing context are not reconstructed.
Not collected: images or videos, prompts, captions, dataset names or IDs, file names or paths, account names, tokens or API keys, log contents, key presses, screen recordings or complete page addresses. Plugin names and private routes are not sent. The browser only reports a coarse feature after a real pointer or keyboard interaction in the visible app; leaving a tab open does not create an activity heartbeat. The action or text entered is never reported.
These are pseudonymous installation statistics, not a count or directory of individual people: one person can use several installations, several people can share an installation, and people who decline are absent from the figures. The choice applies to the whole LDS server installation, including other browsers connected to it.
Statistics are sent in the background by the LDS server to PostHog Cloud EU for the maintainer's private product dashboard. There is no browser analytics SDK, automatic click capture or session replay. A network request necessarily exposes the sending server's network address to the receiving service; LDS does not add an IP address or location to the event properties. Loss of connectivity does not block your work.
Retention: PostHog's free plan lists one year of event retention. The provider is rolling enforcement out by project; while it is not enforced, older events remain stored. LDS therefore does not guarantee an automatic deletion date. See PostHog's retention policy. Pending local events expire after seven days and are limited to 500 entries.
Turn off sharing takes effect as soon as the choice is successfully saved: it stops new collection and clears statistics waiting to be sent. It does not recall data already delivered. The buttons save independently of the general Settings Save bar, and a failed save is shown so you can retry. You can change your choice here at any time; returning to an open browser tab rechecks it.
Separate from everything above: these live per dataset, in the ⚙ Dataset settings modal you open from the workspace. They travel with that one dataset and don't touch the global Settings page.
-
Name — the dataset's display name. Display only for Character and Concept datasets: it never appears in a file name, so changing it touches nothing on disk (the trigger word names produced files — see below). On a Style dataset it means more: a Style is always-on and has no visible trigger, so its name is its only editable identity — renaming it also renames the LoRAs, run folder and export it already produced (and is refused while a run is live, same as a trigger change).
-
Dataset kind (🧑 Character / 💡 Concept / 🎨 Style) — the nature of the LoRA, chosen at creation but changeable here. It is the disruptive setting, so picking a different pill reveals a confirmation block that spells out what changes and what is kept before you save:
- What changes — the caption strategy (Character leaves out identity; Concept leaves out the recurring concept; Style leaves out the aesthetic), which panels show (Reference photo, Generate variations and Face analysis are Character-only — they appear when you become a Character and are hidden otherwise), the trigger's role (Style has none; switching to Character/Concept brings the field back, prefilled), and Character-only settings such as face/body fidelity. Switching to Concept requires a concept description.
- What is kept — nothing is deleted. Every image, its caption text, keep/reject status, face scores, watermark work and training history stay exactly as they are (past runs are named by the model family and trigger, never the kind). A concept description is remembered so switching back restores it.
- Existing captions were written for the old kind and are not rewritten automatically — use 🔄 Re-caption in the Captions section to apply the new strategy. The switch is refused while the dataset has work in progress (generation, captioning or a quality pass) — wait for it to finish.
-
Trigger word — the word you put in prompts to summon this LoRA (Character and Concept datasets). Safe to change anytime — it's added at export, so existing captions don't need redoing. It is also the name everything this dataset produces carries (the deployed LoRA, the training run folder, the export, the job config), so changing it renames all of them to match and repoints the Test Studio history and cloud runs at the new names — a toast tells you how many files moved. Two guards: if the new trigger is already used on disk by another dataset, nothing is renamed (never half a set) and the old names are kept; and the change is refused while a training run is live, because that run folder is what training resumes from — stop it or let it finish first. Style datasets don't have one: Style is always-on, and the modal shows a note reminding you to control the effect with the LoRA weight instead.
-
Concept description (Concept datasets only) — the thing the LoRA learns, i.e. exactly what captions must omit. Editing it rebuilds the caption avoid-list, so re-caption afterwards to apply the new list to images already captioned.
-
Subject type (Human / Animal / Creature / Object / Other / Anime) — what your reference actually is, chosen right in the 🎬 Generate variations panel (it travels with the dataset). It is orthogonal to the dataset kind: a specific dog is Character + Animal, "dogs in general" is Concept + Animal. Anything other than Human switches two things so the generated shots stop assuming a person: the shot catalogue (an animal gets head / half-body / full-body / rear shots, an object gets front / angle / detail / rear views — the group headers relabel to match) and the identity lock the engine is given (a dog keeps its breed, coat and markings; a product keeps its shape, material and logo — instead of "same face, jawline, skin tone"). Each type ships its own balanced preset. Human is the default and existing datasets are unchanged. (NSFW body shots stay Human-only.) First-draft prompt sets — refine per subject as needed. Inspired by a community request.
Anime is the one type where the rendering is part of the subject, so it behaves differently from the other five on purpose:
- Its identity lock protects a character design rather than a photographed body — hair colour, hairstyle and silhouette, eye shape and iris colour, the signature outfit and its colours, the accessories (ribbon, hairpin, glasses, ears, tail) and the distinctive marks — plus the art style itself (line work, cel shading, palette). It then does what no other lock does: it explicitly forbids turning the character into a photograph, a 3D render or a real person (no skin texture, no pores, no film grain). Every other type ends its prompt with "professional realistic photograph"; for a drawing that instruction destroys the subject, so for Anime the engine is asked for an anime illustration instead — in the opening command and in the closing style tag alike.
- The signature outfit counts as identity. For a Human dataset the app deliberately varies clothing on every shot (so a jacket never binds to the person); for a character the costume is half of what makes them recognisable, so the shots keep it. Two explicit alternate outfit cards let you vary it when you want to.
- Its shot catalogue (55 shots) uses the vocabulary of the medium — bust-up, cowboy shot (knee-up), full body, a full expression sheet (smile, laughing, angry, surprised, blushing, eyes closed) and a front / side / back character-sheet turnaround on a plain background that no other type offers. Four presets: Balanced, Face & expressions, Full body focused and Character sheet.
- It pairs naturally with the Anima training family (see Default training family above), though the two settings are independent — you can build an anime dataset and train it on any family.
-
Prompt suffixes (collapsible — optional creative direction) — free text appended to generated variations at generation time, to steer a global look without rewriting anything:
-
All shots →
prompt_suffix— one global suffix (e.g. "shot on 35mm film, warm tones"), up to 300 characters. -
Face / Bust / Body / Back shots →
prompt_suffixes— one suffix per framing, up to 300 characters each. A framing suffix applies to that shot type first, then the global one.
Key behaviours: these are applied at generation time and never stored into a tile's own prompt (so a regenerate can't double-apply them), the identity lock always comes first — a suffix can't override it, clearing a field removes that suffix, and existing images stay as they are until you regenerate to apply.
You can also edit the very same suffixes inline in the generation panel (the collapsible ✨ Prompt suffixes row under the shot picker), which is handy for tuning them per batch without opening this modal — both surfaces read and write the one dataset value, and an edit made there is saved the moment you press Generate.
-
All shots →
These have no UI control — they're for advanced users editing config.json by hand (copy config.example.json to config.json first). Most people never touch them; the defaults are tuned. Values below are the shipped defaults.
(The four ComfyUI folder overrides used to live here. They are now editable in Settings → Local tools → ComfyUI → Advanced: ComfyUI folder overrides — see that section above. Values set by hand in config.json are unaffected: the same keys, read the same way, now simply shown in the app.)
Engines:
| Key | Default | Role |
|---|
Shot detection (video bank): the boundary detector that cuts a long source into individual shots. No UI control yet — the defaults are the reference implementation's, and neither of the two numbers below has been measured against this app's material, so they are stated as adjustable rather than tuned.
| Key | Default | Role |
|---|---|---|
shot_detect.python |
'' |
Interpreter that runs the detector. Empty means reuse the Bank scoring environment, which already carries torch — a second copy would cost you ~2.5 GB for nothing. Written by the installer; you rarely set it by hand. |
shot_detect.threshold |
0.5 |
Cut probability above which a frame is treated as a shot boundary — the app-wide default. A bank can override it, and a single file inside that bank can override the bank; both live in the bank's own 🎬 Find shots panel, where a preview says how many shots each value would give before anything is cut. 0.5 is the detector paper's own number and the paper never justifies it, so it is a convention rather than a measurement: on single-take footage 0.6–0.7 invents fewer cuts, on tight edits a lower value finds more. |
shot_detect.min_shot_seconds |
0.6 |
Shortest shot worth offering, as a duration — converted to frames through each file's own rate, so it means the same thing on a 25 fps rush and a 60 fps one. |
shot_detect.min_shot_frames |
(unset) | The old floor, in frames. Still honoured, and still never renamed: if you set this and not min_shot_seconds, yours wins. Left unset, min_shot_seconds decides. It shipped at 5, which is 0.08 s at 60 fps — nobody chose that, which is why the setting moved to seconds. |
shot_detect.short_shot_policy |
drop |
drop | merge. What happens to a shot below the floor. drop loses the footage; merge glues it onto the previous shot (onto the next one when it is the file's first). A merge deletes exactly one boundary and moves none of the others, so the cuts you keep are always a subset of what the detector drew. drop is the default because it is what shipped. |
shot_detect.dissolve_min_frames |
5 |
At or above this width, a boundary is labelled dissolve rather than cut on the shot's tile. Read from the detector's second output, which the app used to compute and throw away. Not measured on real footage — it follows from how the network was trained, and it is a setting precisely so a future calibration is a number rather than a patch. Advisory: the label changes nothing about the cut. |
shot_detect.trim_dissolves |
false |
Pull a shot's bounds inside the dissolves at its ends (half the fade on each side), so a clip does not open on a cross-fade of the previous shot. Off by default, and it should stay off until the width rule above has been checked on real footage: turning it on moves every dissolve boundary in every bank you detect afterwards. |
shot_detect.device |
auto |
auto | cuda | cpu. The network runs on 48×27 frames and is never the bottleneck — decoding is. CPU is a perfectly reasonable choice, and it leaves the GPU free for captioning and training. |
Video bank quality cuts: the thresholds behind the video bank's amber flags.
The cuts that describe your footage default to empty = no cut — that is a
decision, not an omission: published thresholds measurably do not transfer between
collections, so the app never ships one. watermark_max is the exception and the
reason is worth knowing: it does not measure your footage, it reads a classifier's
probability, which is calibrated with the model rather than with your material — so
the image lane's measurement transfers where a motion floor does not. The three
🔳 Safe zone rows follow the same rule: they measure your
footage, so they ship empty and quote their published references instead. The
three 🩻 Defects rows are the newest, and they are empty for a reason one step
further along: block_max and blur_max are raw ffmpeg filter outputs whose
absolute value depends heavily on what is in the frame, so there is no
published figure worth quoting either — the signal is in the spread inside one
bank, which is what Preview is for. motion_irregularity_floor is the last
row and the odd one twice over: it is the only cut here whose low side is the
suspect one, and the only one whose method publishes no threshold at all —
rank metrics need no absolute scale, so there is nothing to borrow even in
principle. Set them from
Video bank → 🎚 Quality cuts, where Preview shows how many shots each value
would flag before you apply it. Raw scores stay stored, so changing any of these
re-sorts every bank instantly, without rescanning.
| Key | Default | Role |
|---|---|---|
video_bank.min_duration_s |
(empty) | Flags shots shorter than this, in seconds (brief). The only cut here that needs no measuring pass — it reads the shot bounds, so it works straight after detection. Not the same thing as the promotion's too short refusal, which is your target profile's own arithmetic and no setting moves it; this one only decides what gets flagged for your eyes. |
video_bank.motion_floor |
(empty) | Flags shots whose average motion falls below this (still). |
video_bank.motion_ceiling |
(empty) | Flags shots whose busiest moments exceed this (agitated). |
video_bank.luma_floor |
(empty) | Flags shots whose darkest frame falls below this brightness (black). |
video_bank.freeze_max |
(empty) | Flags shots where more than this share of frames do not move (freeze). |
video_bank.sharpness_floor |
(empty) | Flags shots whose sharpest stretch stays below this (soft). |
video_bank.watermark_max |
0.94 |
Flags shots whose watermark score exceeds this (watermark), after the 🔖 Watermarks pass has scored them. This model's scores are compressed hard against 1, so 0.94 is the measured cut and not the 0.5 a probability normally implies — on a 110-image hand-labelled sample it flagged none of the 55 clean images and still caught 54 of the 55 marked ones. Lower it toward 0.92 to catch the faintest marks and hand-check a few clean shots. A shot the pass has not judged carries no score and is never flagged — that is "not evaluated", not "clean". Set it to empty to flag nothing. |
video_bank.aesthetic_floor |
(empty) | Flags shots the LAION aesthetic head rates below this (low_aesthetic) — the same model and the same ~1–10 scale the image bank's ✨ Score puts on a still. Shots are rated by 🔎 Find scenes, which reads the frame vectors it already caches, so the rating costs no decode and no GPU; re-run it on an already-embedded bank and the whole thing is rated in seconds. Empty on purpose, unlike watermark_max: a watermark score is a classifier's probability, calibrated with the model, while this is a taste rating whose useful cut depends on the corpus. The published LAION references — 4 casual, 4.75 strict — were chosen to filter a web crawl, and deliberately-shot rushes sit well above both, so Preview 4 against your own bank before applying it. A shot with no rating carries no score and is never flagged. |
video_bank.bars_max |
(empty) | Flags shots where more than this share of the frame is a flat band — letterbox, pillarbox, or a vertical video padded into a wide container (letterboxed), after the 🔳 Safe zone pass has measured them. The share is the larger of (top + bottom) and (left + right), the same arithmetic — and the same meaning — as the image bank's bank.bars_max, so a number you calibrated on stills carries over. Empty on purpose even though the image lane ships 0.04: a 2.35:1 film honestly carries about 0.12 of bands and is not a defect, it is a crop waiting to happen. The pass calls a band structural only when it is present on all three frames it looks at, so a fade-out never creates one; a genuinely flat sky or the surround of a dark-themed screen recording still can, which is the other reason there is no default. A shot the pass has not measured is never flagged. |
video_bank.text_coverage_max |
(empty) | Flags shots where burned-in text covers more than this share of the frame (burned_text). Only text that stays put across the shot's three frames counts — a subtitle, a chyron, a lower third, a text watermark — so a shop sign or a newspaper someone holds up is scene content and is never counted. 0.01 is already a full subtitle line, so this cut lives at small values. Reading text needs the Burned-in text extra from Setup (RapidOCR, CPU only, ~16 MB of weights inside the wheel). Without it the pass still measures bands, records safe_zone_state: bars_only, and stores no text reading at all — so this cut flags nothing rather than clearing every shot. |
video_bank.safe_area_min |
(empty) | Flags shots where cropping away both the bands and the burned-in text would leave less than this share of the frame (small_safe_zone). This is where text in the MIDDLE of a frame lands: it is small, so the share above never catches it, and there is no crop that removes it — the rectangle collapses and this number goes with it. HunyuanVideo 1.5 keeps only clips whose crop leaves 60 % or more of the frame; below roughly 50 % there is not enough picture left to be worth the trouble. Both figures were set for a web-scale crawl, which is why they are quoted here and shipped nowhere. Needs the text extra, like the row above. |
video_bank.dup_frames_max |
(empty) | Flags shots where more than this share of frames is a near-copy of the one before it (dup_frames), after the 🩻 Defects pass has swept the file. This is what a 24 fps source uploaded as 30 fps looks like — one frame in five is a repeat, so 0.15 catches it — and it is not freeze_max under another name: that one reads the codec's motion vectors and says nothing moved, this one says the same picture was delivered twice. A moving shot run through a frame-rate conversion trips this and leaves freeze_max completely untouched. Measured over the shot's own window, so a shot straddling a frozen stretch reports the share it actually holds. |
video_bank.block_max |
(empty) | Flags shots where the 8×8 macroblock grid shows through a hard squeeze (blocky), read as the worst tenth of the shot's sampled frames. Empty on purpose, and here there is not even a published figure to quote: the score depends on what is in the frame nearly as much as on the damage. Measured on this machine, the same scene from a good encode to a ruined one moved from 13 to 43, while four different scenes at one fixed quality spanned 1 to 25 000. So the signal is in the spread inside one bank, never in the number — use Preview, look at what a value caught, and move it. Worth reading next to the file card's bits per pixel, which predicts the same damage from the container header alone. |
video_bank.blur_max |
(empty) | Flags shots whose edges stay wide even at their sharpest (blurry), measured at full resolution. This is the cut that catches footage upscaled from something smaller, and sharpness_floor above structurally cannot: that one computes its Laplacian on a 160-pixel-wide analysis copy (deliberately — a Laplacian over a full frame costs more than the decode), and at 160 pixels a 480p upscale and the genuine 1080p are the same picture. Measured on three files carrying the same footage: sharpness_p90 read 354.35 / 353.69 / 353.72 for native, 480p-upscaled and 320p-upscaled, while this score read 5.56 / 6.62 / 7.35. Typical values are 4–6 for clean footage and 7–12 for upscaled or heavily squeezed material. Deliberately read from the sharpest tenth of the shot rather than the blurriest, so a fast pan or a shallow depth of field is not called blurry — the same false positive sharpness_floor chose its own aggregation to avoid. |
video_bank.motion_irregularity_floor |
(empty) | Flags shots whose motion is suspiciously smooth (maybe_generated), after the 🤖 AI check pass has measured them. The only cut in this table whose low side is the suspect one — it is a floor, so raising it flags more, and reading it as a _max would flag every handheld shot in your bank while clearing every generated one. The score is the spread of the change in frame-to-frame change over two contiguous seconds: real footage is erratic (a hand shakes, a subject accelerates, the sensor is noisy) and generated footage tends to be smoother than the world. How reliable, honestly: about three shots in four. The SAFE Challenge evaluated detectors blind and the best system scored 0.86 balanced accuracy on untouched video against 0.74 on post-processed video, with re-compression alone moving AUC from 0.88 to 0.77 — and scraped material is re-compressed by construction. The method behind this was measured only against 2023–24 generators (ModelScope, Gen2, Pika, LaVie, Sora, CogVideoX…), says nothing about Sora 2, Veo 3, Kling or Wan 2.5, and is worst on the cheapest fakes: on one generator whose output is incoherent it scores below chance, because chaotic generation reads as more real than clean generation. So this is advisory, and nothing in the app rejects or deletes on it. There is no value to type and no published one to borrow: the method reports only rank metrics (AUC, average precision) and its reference implementation carries no threshold at all, while the score's scale moves with the encoder and the frame count. Preview against your own bank. Shots shorter than about 2.4 s hold no window, carry ai_check_state: too_short and no score, and are never flagged. Not the same claim as the image bank's origin: ai, which reads generator metadata and is proof when present; this reads pixels and infers. |
video_bank.camera_shake_max |
(empty) | Flags shots whose camera wobbles more than this (shaky), after the 🎥 Camera pass has read them. The number is the high-frequency part of the movement — what a local average of the trajectory cannot explain — as a percentage of the frame width, so unlike every other cut in this table it is comparable between banks: it does not move with resolution, content or encoder. For scale, a smoothly-moved or locked-off shot measures under 0.10 and strong handheld tremor measures about 1.16, so a cut around 0.3 separates them. Empty on purpose, and for a reason none of the rows above have: the number is calibrated, but which side of it you want is the whole question — filtering for the wobble is exactly how you build a handheld-look training set, and a default would pick a side this app does not have. Not the same threshold as the Handheld camera label, which fires at a fixed internal floor: the label says what the shot is, this cut says what you reject, so a shot can be labelled handheld without being flagged and the reverse. A shot the pass has not read is never flagged. |
video_bank.coherence_floor |
(empty) | Flags shots whose first and last embedded frames have drifted far enough apart that the shot probably holds a cut the detector missed (missed_cut) — one "shot" that is really two scenes. The number is the CLIP cosine between those two frames, so 1.00 is the same picture and lower means the scene changed. It is produced at the tail of 🔎 Find scenes from vectors that pass already cached: no decode, no model, no GPU, no button — and re-running 🔎 Find scenes on an already-embedded bank reads the whole thing in seconds. Empty on purpose, and here the reason is neither "it is your footage" nor "there is no published figure" — it is that the measured accuracy does not earn a default. Calibrated on this app's own encoder against 362 forged missed cuts (two adjacent detected shots fused into one) and 337 real shots, controlled for duration: AUC 0.719, and a cut at 0.80 catches about a third of the missed cuts while flagging about one honest shot in seven; 0.75 catches a fifth for one in ten. That is worth sorting a bank by and not worth deciding anything on. Why long shots score lower: the cosine falls with elapsed time whether or not anything was cut (measured: Spearman −0.41 against duration), so a twenty-second locked-off take can read 0.84 with no cut in it — lower the floor if your bank is mostly long takes. Panda-70M's own 1.0 does not convert into a default here: that is a Euclidean distance over ImageBind features, which is cosine 0.5 on unit vectors, and 0.5 sits below the first percentile of even completely unrelated CLIP ViT-L/14 frame pairs (measured: p1 0.501, median 0.720) — it would flag nothing, ever. Not a stillness cut, deliberately: the other half of Panda's rule ("nothing changed, so nothing moved") was measured against this app's own motion readings and refuted — the number tracks shot length far more than motion, genuinely motionless shots read no higher than ordinary ones, and stillness stays with motion_floor and the Slideshow camera label. Shots with no vectors, and shots under a second (too short for the embed pass to take more than one frame), carry no reading and are never flagged. |
video_bank.duplicate_threshold |
0.96 |
Cosine similarity at or above which two shots are grouped as near-duplicates by the ✂ Duplicates pass, comparing them at their closest pair of embedded frames. Not a read-time cut like the rows above: changing it means re-running that pass — which is instant and costs no GPU, since it re-reads the frame vectors 🔎 Find scenes already cached. Where the number comes from: it is inherited from the image bank's bank.semantic_dup_threshold, measured over the same CLIP space, and no video-pair calibration exists yet. Comparing shots at their closest frame pair also reaches any given value more easily than a single-image comparison does, so raise it if your bank over-groups. |
Imported shot catalogs — written by the workspace, not meant to be hand-edited (see Using the app → Your own shot catalog), but this is where they live so you know what to back up:
| Key | Default | Role |
|---|---|---|
custom_shots |
{} |
{subject_type: [{id, label, prompt, framing, nsfw?}]} — the shots you imported from JSON, one list per subject type. Kept here rather than in the browser so they survive a cache wipe, follow you to another device and ride along in the backup. Entries are re-checked on read: a bad framing, a missing field, or a label that already belongs to a built-in shot is dropped (two shots sharing a label would resolve to the wrong prompt when one is regenerated). |
Cloud (vast.ai) internals — knobs for after the real-world smoke test; the UI-exposed cloud settings above are the ones you'll normally want:
| Key | Default | Role |
|---|
Quality-tool interpreters and models:
| Key | Default | Role |
|---|---|---|
face_scoring.python |
'' |
Interpreter for the InsightFace subprocess (empty = current interpreter). |
face_scoring.models_root |
'' |
Where InsightFace weights are stored/downloaded. Empty means data/models/insightface — except on an install that already downloaded them to ~/.insightface, which keeps using that folder. |
face_scoring.device |
'auto' |
Device for BOTH face passes — the Image bank's person grouping and a dataset's 🎭 Analyze faces. auto uses the GPU when the face interpreter exposes CUDA (needs onnxruntime-gpu installed in it) and falls back to CPU otherwise; cpu forces CPU (never touches the GPU); cuda requests the GPU but still falls back to CPU when unavailable. A GPU run is serialized through the GPU-exclusive window so it never competes with a training/scoring pass. |
masks.python |
'' |
Interpreter for the rembg (person-mask) subprocess. |
bank_scoring.text_search_idle_minutes |
10 |
How long the selected 🔤 Find by text encoder stays warm after its last query (capped at 120). Set 0 to unload after every query. Text caches are separated by engine/model key. |
bank_semantic.python |
'' |
Interpreter for SigLIP 2 image/text workers and capability probes. Setup installs into the managed Bank environment and preserves an existing external selection without changing Score’s Python. Use Bank → ⚙ Passes → Semantic engine → Manage SigLIP 2 Python… to inspect the effective path, test a small calculation or explicitly Use managed environment. Discovery checks PyTorch, Siglip2Model, NumPy and Pillow; successful imports and CUDA detection do not verify model execution. Empty inherits bank_scoring.python, then the app Python, so Back to the app default can select an external interpreter. |
bank_semantic.models_root |
'' |
SigLIP 2 model cache (empty = data/models/bank_semantic). Setup downloads only the pinned files after an explicit click; inference is local-files-only. |
bank_semantic.device |
'auto' |
Device for the SigLIP 2 image index (auto, cuda, cpu). Resolved against bank_semantic.python: auto only reaches the GPU if THAT interpreter's torch sees a card, which is what the Semantic engine panel's device line reports. A GPU run uses the same exclusive window as other Bank ML work. |
bank_semantic.siglip2_semantic_dup_threshold |
0.97 |
SigLIP 2 cosine threshold for ✂ Find crops & variants. Separate from CLIP because the two spaces are not numerically interchangeable. |
watermark.python |
'' |
Interpreter for the LaMa watermark subprocess. Auto-managed: leave it empty and the Install inpainting button builds a dedicated Python 3.10-3.12 environment for you (simple-lama-inpainting needs Pillow<10, so it can't share the app's own Python) and fills this in automatically. Set it yourself only to point at an environment you already have — a manual value is always respected and never overwritten. |
Klein consistency LoRA:
| Key | Default | Role |
|---|---|---|
klein.consistency_lora |
klein/Flux2-Klein-9B-consistency-V2.safetensors |
The structure-anchoring LoRA on the Klein edit graph, relative to ComfyUI's LoRA folder. |
klein.consistency_strength |
0.5 |
Its strength (0–1). Its own guide warns 0.8–1.0 can stop edits applying; 0 disables it entirely. |
Z-Image text encoder & VAE:
Both are blank by default, and blank is the right value — the app finds them itself. It scans every registered vae / text_encoders folder (including the ones your extra_model_paths.yaml adds), sub-folders included, ignoring capitalisation and separators: z_ae, z ae, z-ae, and ComfyUI's own ae.safetensors all resolve, and qwen_3_4b.safetensors is found whether it sits at the root of text_encoders/ or inside a folder called Z image, Z Image or z-image. It never picks a .gguf (the loader nodes cannot open one) and never picks Krea's qwen3vl_4b or Klein's qwen_3_8b, which live in the same folder and would fail at sample time.
Set one of these only to override that search — for instance if your ComfyUI is shared with FLUX.1, whose VAE is also called ae.safetensors, and the app picked the wrong one. A value you set here is used exactly as written and is never second-guessed; if the file isn't there, the error names your file rather than silently substituting another.
| Key | Default | Role |
|---|---|---|
zimage.vae |
'' |
Pin the Z-Image VAE, relative to ComfyUI's VAE folder (e.g. z_ae.safetensors). Blank = auto-resolve. |
zimage.text_encoder |
'' |
Pin the Z-Image text encoder, relative to ComfyUI's text-encoders folder (e.g. Z image/qwen_3_4b.safetensors). Blank = auto-resolve. |
Updates:
| Key | Default | Role |
|---|---|---|
updates.repo |
perfectgf/lora-dataset-studio |
The GitHub repo the update checker reads its release feed from. |
Run provenance:
| Key | Default | Role |
|---|---|---|
provenance.archive_max_gb |
5 |
Ceiling of the run image archive (Settings → Maintenance): the deduplicated copies that let a two-run comparison still show an image you deleted afterwards. Copies are content-addressed, so a whole training history usually costs well under a gigabyte — on a real 20-dataset, 1471-image library, every distinct version of every image ever trained came to about 0.5 GB. Past the ceiling nothing more is stored and the compare panel says so. Set it to 0 to turn archiving off entirely; the run records, settings and caption text are kept either way. |
A flat cheat-sheet of the main config.json keys, for quick lookup or hand-editing (copy config.example.json to config.json first — it's git-ignored, in your data directory). Every key here is documented in full, with defaults and traps, in the sections above; this table is the index. Secrets (GEMINI_API_KEY, OPENAI_API_KEY, OPENROUTER_API_KEY, HF_TOKEN, VAST_API_KEY, optional scraper credentials) live in .env, not here.
| Key | Meaning |
|---|---|
server.host |
Interface the Flask server binds to (default 127.0.0.1, local-only). |
server.port |
Port the server listens on (default 5050). |
server.require_token |
On a non-loopback bind, require remote clients to present an access token (default false — a trusted LAN needs none). Toggle and token also live in Settings → Server & access. Overridden to always-on by LDS_PUBLIC=1 and by LDS_REQUIRE_LOCAL_TOKEN=1. |
paths.dataset_images_root |
Where dataset images are stored. Empty string defaults to <data dir>/datasets. |
paths.cloud_runs_dir |
Working area of cloud training runs (dataset copy, samples, logs). Empty string defaults to <data dir>/cloud_runs. |
paths.checkpoints_dir |
Durable store for the checkpoints cloud runs produce. Empty string defaults to <data dir>/checkpoints. No cleanup ever removes a file from it. |
paths.video_datasets_dir |
Where promoted video datasets are written — a flat folder of .mp4 clips with homonym .txt captions per dataset. Empty string defaults to <data dir>/video_datasets. |
dataset_import.max_side |
Longest side for opt-in WebP normalization (default 1024; 0 = original size). It is ignored by the default preserve mode; ratio is always preserved, never enlarged, and normalized paths clamp at 8192 px. Every source must still fit image_input.* below; a larger one is rejected and must be resized before import (or the budget raised). Not retroactive. Editable in Settings → Captioning & quality. |
dataset_import.encoding |
How an un-cropped imported image is written: preserve (default; original JPG/JPEG, PNG, WebP or BMP bytes with the matching extension, HEIC/HEIF and static AVIF as a lossless PNG), or the opt-in WebP modes standard (q92), high (q100), and lossless. Auto head-crop is always a derived WebP. The image_input.* budget applies to every mode. Editable in Settings → Captioning & quality. |
image_input.max_pixels |
Largest source image any lane may decode, in pixels (default 67108864 = 64 Mi-pixels; 0 = no limit). A memory budget: ~3 bytes per decoded RGB pixel, and an edit or analysis pass can hold a second copy. Read by dataset import, ZIP/scrape ingest, Bank scan and thumbnails, edits, ComfyUI staging and Ollama vision. Bank inference workers keep their own fixed 16 Mi-pixel guard. Editable in Settings → Captioning & quality. |
image_input.max_side |
Largest side of a source image, in px (default 16384; 0 = no limit). Separate from max_pixels because a wide panorama can sit inside the pixel budget and still exceed a side limit. Editable in Settings → Captioning & quality. |
comfyui.api_url |
Base URL of your ComfyUI instance (default http://127.0.0.1:8188). |
comfyui.base_dir |
ComfyUI install directory, used to derive output/input/models/loras dirs if those aren't set explicitly. |
comfyui.output_dir |
Explicit override for ComfyUI's output folder. Set it when ComfyUI runs with --output-directory. Editable in Settings → Local tools. |
comfyui.input_dir |
Explicit override for ComfyUI's input folder. Set it when ComfyUI runs with --input-directory. Editable in Settings → Local tools. |
comfyui.models_dir |
Explicit override for ComfyUI's models folder (used to scan available checkpoints/UNETs). extra_model_paths.yaml is still read on top of it. Editable in Settings → Local tools. |
comfyui.object_info_timeout_s |
Seconds ComfyUI may take to enumerate its nodes and model files (default 45, clamped to 5-300). Raise it on an install with many custom nodes; a ComfyUI that is simply stopped is still detected in ~3 s regardless. Editable in Settings → Local tools. |
comfyui.loras_dir |
Explicit override for ComfyUI's LoRA folder — where trained LoRAs are installed. Wins over extra_model_paths.yaml; left empty, the deploy folder follows the yaml's is_default LoRA root, else <install>/models/loras. Editable in Settings → Local tools. |
ollama.url |
Base URL of your Ollama instance (default http://127.0.0.1:11434). |
ollama.deployment_mode |
Docker-only deployment selected in LDS Setup: none, host, or docker. Host and companion modes force http://host.docker.internal:11434 and http://ollama:11434 respectively; their URL is not user-editable. |
ollama.vision_model |
Ollama vision model used for auto-classify and auto head-crop (default huihui_ai/qwen3-vl-abliterated:8b-instruct, the uncensored abliterated build — use the Instruct, not Thinking, variant). |
ollama.vision_concurrency |
How many images a bank vision pass (watermark / framing / captions) sends to Ollama at once (default 4, clamped to 1-16). Higher overlaps more waiting; 1 restores the old one-at-a-time behaviour. |
ollama.vision_keep_warm_seconds |
How long a one-off vision job (auto head-crop, Describe) may leave the model loaded when nothing else wants the GPU (default 120, 0 = always unload, capped at 600). The lease is revoked as soon as a generation or a training starts. |
ollama.setup_skipped |
You chose Continue without Ollama in the Setup wizard (default false). Presentation only — it makes the Setup step read as a neutral skip instead of asking again, and gates nothing. A reachable Ollama cancels it automatically. |
local_llm.provider |
Which local model server serves captioning, framing, head-crop and the prompt helpers: ollama (default) or lmstudio. |
lmstudio.url |
LM Studio server root (default http://127.0.0.1:1234). A pasted .../v1 is accepted and trimmed. |
lmstudio.vision_model |
Model id LDS insists on. Empty (default) = use whichever model LM Studio has loaded. |
lmstudio.vision_concurrency |
How many images a bank pass sends to LM Studio at once (default 4, clamped 1-16). |
lmstudio.vision_keep_warm_seconds |
How long a one-off job may leave the model resident before LDS unloads it (default 120). LM Studio has no TTL of its own, so this is what ends the residency. |
aitoolkit.dir |
ai-toolkit install directory. |
aitoolkit.datasets_dir |
Override for ai-toolkit's datasets folder (defaults to <aitoolkit.dir>/datasets). |
aitoolkit.output_dir |
Override for ai-toolkit's output folder (defaults to <aitoolkit.dir>/output). |
aitoolkit.hf_home |
Override for the Hugging Face cache directory ai-toolkit uses. |
aitoolkit.python |
Full path to the Python interpreter to run ai-toolkit with. Empty = auto-detect a venv//.venv/ next to run.py; set it for conda/uv/system-Python and portable/embedded (python_embeded) installs that have no venv folder. |
engines.default |
Default image-generation engine selected in the UI (nanobanana, chatgpt, openrouter, or klein). |
engines.enabled |
List of engines shown as options in the UI. Doubles as the engine catalogue: an engine added by an update is merged into a stored list on read, so a new engine reaches installs that already have saved settings. An engine you removed yourself is never added back. |
engines.known |
Not a setting — the ledger of which engines the app was offering the last time this list was saved. It is what tells "this engine did not exist yet" apart from "I unticked it". Written automatically; [] (or absent) means the app assumes the pre-OpenRouter trio. Delete it to be re-offered every engine. |
engines.chatgpt_image_model |
Image model the ChatGPT engine requests on the API-key lane. Free text; blank = the CHATGPT_IMAGE_MODEL environment variable if set, else gpt-image-2 (the only model that needs no OpenAI organization verification). Must accept reference images. The subscription lane ignores it. |
captioning.backend |
Caption backend: auto (prefer JoyCaption, fall back to Ollama), joycaption, ollama, or none. |
training.default_family |
Default model family preselected for new training runs (zimage, sdxl, krea, flux, flux2klein, or anima). |
| (not a setting) free-disk floor | Derived, never configurable: the pod must hold the master, its fp8 twin and the download cache (~86 GB for a 26 GB model). Offers with less free disk are excluded from the search, because vast refuses an ask larger than the machine's disk — and the cheapest listing is exactly where free disk runs out. |
face_scoring.python |
Python interpreter used to run the InsightFace subprocess (empty = current interpreter). |
face_scoring.models_root |
Directory where InsightFace model weights are stored/downloaded. Empty resolves to data/models/insightface, so a Docker install keeps them on its mounted volume instead of re-downloading them whenever the container is recreated (~350 MB downloaded, ~750 MB on disk — InsightFace keeps the zip next to what it extracted). |
face_scoring.green |
Similarity score threshold (0–1) above which an image is flagged "green" (strong match). |
face_scoring.orange |
Similarity score threshold (0–1) above which an image is flagged "orange" (borderline match). |
masks.python |
Python interpreter used to run the rembg subprocess (empty = current interpreter). |
video_caption.style |
Which PROMPT writes the captions: standard (default, the shipped wording) or plain. Measured to matter more than the checkpoint: asked the standard way, even an uncensored model describes around explicit footage, while the base model asked plainly named things precisely and wrote the best action description of the four combinations tried. plain adds explicit permission to state what is visible and what occurs, forbids the two evasive words the test caught models hiding behind — and forbids replacing the scene with an invented harmless one, the failure a second bench (2026-08-07) measured on abliterated checkpoints asked politely: they do not soften explicit footage, they swap it for a different scene entirely, stably. It matters because a caption that talks around its subject teaches the trained model to look away, and the captions read perfectly well either way. Anything unknown falls back to standard — never to plain. Also pickable per run, next to the 🗣 Describe shots button; every caption records the style that produced it. |
video_caption.model |
Which model writes the 🗣 Describe shots captions (empty = the shipped default, Qwen/Qwen3-VL-4B-Instruct). Any checkpoint of the same architecture is a drop-in; a different architecture fails loudly at load rather than silently misbehaving. Worth changing when the default talks around what your footage shows — a caption that names things evasively teaches the trained model to do the same, and nothing in the output reveals it. Pointing this at a model the machine does not have is allowed and downloads it on the first run, but never in silence: the pass says so in its own progress line before captioning anything. Every caption records which model wrote it, so a bank captioned across a change stays readable. |
video_caption.tokenizer_dir |
Folder holding umT5's spiece.model — the tokenizer behind every Wan 2.x text encoder — so 🗣 Describe shots can measure each caption in the encoder's own tokens instead of guessing from words (empty = look in this machine's Hugging Face caches for any umT5 snapshot: ai-toolkit's umt5_xxl_encoder mirror or a Wan repo's google/umt5-xxl folder; nothing is downloaded). It matters because Wan's encoder drops everything past 512 tokens in silence: with the count measured, building a training set knows which prompts would be cut and writes those in the caption's short form (its Subject / Motion / Setting / Style lines). Without a tokenizer the count is an estimate (1.4 tokens per word, rounded up) and the export says so. |
| Video bank → 🎚 Quality cuts → Maximum length (s) | Flags shots LONGER than this (lengthy). The mirror of the minimum, and the one that answers "what happens to my fifteen-second shot?": a shot longer than the clip length your target ingests is exported as its first N frames and the rest never trains. Stored per bank with the other cuts; it flags and sorts, it never rejects. |
video_caption.backend |
Which engine writes the 🗣 Describe shots captions: empty = auto — LDS's own local worker (Hugging Face Transformers in the ✨ Score interpreter) when that interpreter can run it, else the local LLM you already operate (Ollama or LM Studio, whichever local_llm.provider says, with its own configured vision model). transformers or local_llm forces a side. The transformers worker stays the default when both are possible because it holds what an HTTP server cannot offer — real per-frame timestamps, bf16 weights and the measured umT5 token count; through a local LLM the frame times ride in the prompt as text and the token gauge falls back to its labelled estimate. Every caption records which engine wrote it (ollama:<tag> / lmstudio:<id>), and the empty-answer guard applies to every engine: an empty response is stored as an error, never as a caption. |
quantize.python |
Python interpreter that runs the fp8 conversion and the LoRA→base merge (empty = the one ✨ Score uses, then ai-toolkit's, then the app's own). Both need torch, which this app deliberately does not install — it is gigabytes and nothing else here needs it — so they run in a subprocess, like the scoring and masking passes. One setting governs both on purpose: "the Python on this machine that has torch" is one fact, and saying it twice is how the two drift apart. The chosen interpreter is probed while the plan is drawn: one that lacks the packages disables the button with the reason and the pip install line, instead of failing after the click (or after a 26 GB download). torch is the only module either of them needs: both read and write the safetensors format themselves rather than memory-mapping it, so an environment with torch alone is enough. |
bank_scoring.python |
Python used by ✨ Score. Empty falls back to the app Python. Bank → ⚙ Passes → Manage Score Python… shows the effective and managed paths and provides an explicit calculation check. Setup repairs the managed environment while preserving an external choice; select Use managed environment to run there. Import detection alone is not a runtime guarantee. |
bank_semantic.python |
Python used by SigLIP 2. Bank → ⚙ Passes → Semantic engine → Manage SigLIP 2 Python… remains accessible regardless of CUDA detection. Setup repairs the managed Bank environment and preserves an external choice. Empty inherits bank_scoring.python, then the app Python; use Use managed environment to select the managed runtime explicitly. |
watermark.python |
Python interpreter used to run the LaMa watermark-inpainting subprocess (empty = reuse masks.python, then the current interpreter). |
watermark.device |
LaMa processing device: auto (CUDA when available, otherwise CPU), cuda, or cpu. |
watermark.allow_crop |
When true (default), a border watermark is cropped off; when false, it is repainted instead. Also editable in the Clean bar. |
watermark_clean.klein_prompt |
The instruction the 🧽 Klein clean sends when it re-renders the photo (default remove watermark). Empty = that default. Edited next to the Klein model choice on both the Bank panel and the dataset Clean bar; never routed into the ✦ Repair lane. |
watermark_clean.klein_max_mp |
Megapixels the photo is scaled to before Klein re-renders it (default 2.0, clamped 0.5–4.0). Higher = finer regenerated detail, more VRAM and more time. Never enlarges a smaller photo. The ✦ Repair lane keeps its own fixed cap. |
watermark_clean.klein_output |
What size the cleaned file is written at: original (default — resampled back to your file's own dimensions) or render (written at the render's size, so the file changes dimensions). |
zimage.vae |
Pins the Z-Image VAE (blank = the app resolves it itself, any spelling, any sub-folder). |
zimage.text_encoder |
Pins the Z-Image text encoder (blank = the app resolves it itself). |
variations.output_megapixels |
Pixel budget every generated variation is rendered at, in megapixels, on the shot card's ratio — shared by both local engines (Klein and Krea 2 Edit). Default 2.0 = Klein's historical hardcoded value; 0.5–2.0. Not the improve pass (klein.improve_megapixels), and not the ✦ reference edit (source frame, never upscaled). |
klein.consistency_lora |
Filename of the Klein consistency LoRA, relative to ComfyUI's LoRA folder. |
klein.consistency_strength |
Strength (0–1) applied to the Klein consistency LoRA. |
klein.generation_steps |
Sampler steps for Klein generation (variations, regenerate, small-image rescue). Default 5 = the value hardcoded in the shipped workflow; 1–50. Not the improve pass (klein.improve_steps). |
klein.edit_base_lora_strength |
Strength of the enhancement LoRA (klein/realistic.safetensors, node 139) on Klein edits — reference edit, variations, regenerate, small-image rescue. Default 0 = off, the render before that LoRA became a Setup download; 0–2. Not the improve pass (klein.improve_base_lora_strength). |
klein.generation_lora_presets |
Named generation-LoRA stacks (default empty) picked per run in Klein tuning; each has a name and up to 8 {file, strength} rows. Built in Settings → Image engines; the strengths of the picked preset are also editable from the ✨ Upscale & improve window (same values, app-wide). |
klein.default_generation_lora_preset |
Which of klein.generation_lora_presets the 🖥️ Klein tuning panel STARTS on. Default '' = None, the behaviour before this key existed. A starting point only — the run panel still offers None and every other preset for that run, and picking there does not rewrite this. Fail-closed: a name matching no preset behaves as None. |
krea.default_generation_lora_preset |
The same, for krea.generation_lora_presets and the 🧬 Krea 2 Edit tuning panel. A SEPARATE key on purpose: the two preset lists are independent and one name can designate two different chains. Default ''. |
identity_prompts.markings_lock |
Krea's “hold the skin” order — forbids inventing or redrawing marks. Blank = shipped default. Naming a body feature here summons it. |
identity_prompts.outfit_vary |
The outfit directive injected into every human shot with no named garment. Blank = shipped default. |
identity_prompts.expression_neutral |
The neutral-expression directive injected into every human shot with no named expression. Blank = shipped default. |
identity_prompts.outfit_palette |
Krea's concrete garments, one per line. Blank (or nothing but blank lines) = the shipped list. The list's LENGTH decides which shot gets which garment. |
identity_prompts.render_tail_sfw / .render_tail_nsfw
|
The Klein/Krea rendering tail, SFW and uncensored. Per subject type (by_subject.<type>.<kind> for non-human). Blank = shipped default. |
identity_prompts.framing_face / .framing_bust / .framing_body / .framing_back
|
The per-framing shot-detail block for Klein/Krea. Per subject type. Blank = shipped default. |
identity_prompts.by_subject.<type>.<kind> |
Identity-lock overrides for a non-human subject type (animal, creature, object, other) × kind (face_single, face_multi, klein_identity). Human overrides stay on the flat identity_prompts.<kind> keys. Blank/absent = the shipped default for that subject. |
klein.small_image_prompt |
Optional shared instruction for scraper rescue and single/bulk image improvement (empty = reference image only). |
updates.repo |
GitHub repo the update checker reads its release feed from (default perfectgf/lora-dataset-studio). |
Additional config-file-only keys (ComfyUI folder overrides, cloud internals, quality-tool interpreters, Klein consistency LoRA) are documented in Config-file-only settings above.