Commit graph

774 commits

Author SHA1 Message Date
Daniel Han
b28793418d Speed up + shrink SDXL LoRA training (precompute text embeds, 8-bit AdamW)
SDXL re-encoded every caption with both CLIP text encoders on every step (pure
waste, since captions are constant) and kept the encoders resident. Precompute
each unique caption's embeddings once, then free the text encoders before the
loop: numerically identical (embeddings are deterministic and this consumes no
torch RNG, so the noise/timestep stream is unchanged) but faster and ~1.5 GB
lighter. Default the optimizer to 8-bit AdamW (bitsandbytes) with an fp32
fallback, halving optimizer state with no meaningful LoRA quality cost. Env
toggles (UNSLOTH_DIFFUSION_NO_PRECOMPUTE / _FP32_OPTIM) let the accuracy guard
A/B the paths.
2026-07-02 15:25:55 +00:00
Daniel Han
fa2cb600ee Add flow-matching DiT LoRA trainers (FLUX.1-dev, Qwen-Image, Z-Image)
Extends diffusion LoRA training beyond SDXL to the three popular DiT families
via a single shared flow-matching loop parameterised by small per-family specs
(loading, prompt/latent encoding, transformer forward, save). Verified against
diffusers 0.38.0:

- FLUX.1-dev: 2x2 latent packing + image ids, guidance-embed forward, on-the-fly
  nf4 QLoRA of the 12B transformer (the dev repo is gated, so training needs the
  user's HF token).
- Qwen-Image: 5D VAE latents normalised by the per-channel latents_mean/std,
  img_shapes forward, prequant nf4 base by default (on-the-fly nf4 for the bf16
  base).
- Z-Image: list I/O with the reversed timestep convention and a negated
  prediction, bf16 only.

The registry (get_trainer) and DiffusionFamily.trainable / train_base_repos now
route these families to the DiT trainer; the SDXL blocklist guard is replaced by
a positive family resolution that also rejects GGUF repos (inference-only) and
still-unsupported families. Per-family defaults + labels + VRAM notes are exposed
via family_train_infos for the Train UI.

Memory: caption embeddings are precomputed once and the text encoders freed
before the loop; gradient checkpointing (non-reentrant, required for bnb 4-bit)
and 8-bit AdamW are on by default.
2026-07-02 15:25:43 +00:00
Daniel Han
76520bb553 Retain diffusion training loss history and expose it in status
The training service kept only the latest loss, so a live loss chart could show a
single point. Fold each progress event into bounded (step, loss, lr) history arrays
(capped at 4000 points, decimated when full) plus the latest throughput and peak VRAM,
and record the family / base model / catalog path on completion. The status endpoint
returns these as a nested metric_history object the UI can chart directly, and the
start request accepts an optional model_family override.
2026-07-02 14:55:17 +00:00
Daniel Han
7f0a9ebd2f Refactor diffusion LoRA training into a family-aware platform
Split the SDXL trainer into a shared, architecture-agnostic layer so more model
families can be trained without duplicating the plumbing:

- New core/training/diffusion_train_common.py holds the config + validation, dataset
  discovery, event emission, stop protocol, adapter publishing, and a lazy trainer
  registry (get_trainer). diffusion_lora_trainer.py keeps the SDXL-specific loop and
  re-exports the moved names so existing imports are unchanged.
- The SDXL-only base-model blocklist becomes a positive check: the family is resolved
  from the base model (or an explicit model_family) via the diffusion family registry,
  and a known-but-not-yet-trainable family is refused with a clear message. Unknown
  custom names still default to the SDXL trainer.
- DiffusionFamily gains a trainable flag and train_base_repos; SDXL is marked trainable.
  DiT families flip on when their trainers land.
- Trained adapters now write a <name>.json metadata sidecar (family, base model, rank,
  trigger prompt, ...) that the LoRA scanner reads to family-gate the adapter in the
  picker instead of showing it as unknown for every model.
- The training base-model trust allowlist adds the official FLUX.1-dev, Z-Image-Turbo,
  and Qwen-Image repos (safetensors-only, no remote code).
2026-07-02 14:55:09 +00:00
Daniel Han
c5a0ad59cf Merge remote-tracking branch 'origin/diffusion-lora-training' into diffusion-lora-training-api 2026-07-02 09:56:56 +00:00
Daniel Han
e1f82b4446 Refuse non-SDXL base models at diffusion training start
The trainer only supports the SDXL U-Net, but a FLUX / Qwen-Image / Z-Image
repo or a GGUF filename passed as base_model was accepted and then failed
minutes later inside StableDiffusionXLPipeline.from_pretrained with an
unrelated-looking error. Add a name-based guard in normalized() so known
DiT-family names and .gguf checkpoints are rejected up front, which the API
start route surfaces as an immediate 400 with a message that says exactly
which bases are trainable. Unrecognisable names still pass through so custom
local SDXL checkpoints keep working.
2026-07-02 09:56:49 +00:00
Daniel Han
1df030e325 Merge remote-tracking branch 'origin/diffusion-lora-training' into diffusion-lora-training-api 2026-07-02 06:42:34 +00:00
Daniel Han
0c68c402d0 Merge remote-tracking branch 'origin/diffusion-lora-ux' into diffusion-lora-training 2026-07-02 06:42:32 +00:00
Daniel Han
138b796218 Merge remote-tracking branch 'origin/diffusion-controlnet' into diffusion-sdxl 2026-07-02 06:41:44 +00:00
Daniel Han
47a64f3819 Merge remote-tracking branch 'origin/diffusion-lora' into diffusion-controlnet
# Conflicts:
#	studio/backend/core/inference/sd_cpp_backend.py
2026-07-02 06:40:54 +00:00
Daniel Han
38aa96a19c Merge remote-tracking branch 'origin/diffusion-image-workflows' into diffusion-lora 2026-07-02 06:39:42 +00:00
Daniel Han
8bfa236798 Merge remote-tracking branch 'origin/diffusion-image-workflows' into diffusion-lora
# Conflicts:
#	studio/backend/core/inference/sd_cpp_backend.py
2026-07-02 06:38:47 +00:00
pre-commit-ci[bot]
ffed150070 [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
2026-07-02 06:37:36 +00:00
Daniel Han
eaf968bfc1 Merge remote-tracking branch 'origin/image-generation' into diffusion-image-workflows
# Conflicts:
#	studio/backend/core/inference/diffusion.py
#	studio/backend/core/inference/diffusion_families.py
#	studio/backend/routes/inference.py
#	studio/backend/tests/test_diffusion_backend.py
#	studio/frontend/src/features/images/images-page.tsx
2026-07-02 06:36:45 +00:00
Daniel Han
691bad30c4 Address further Codex findings on the image-workflows PR
- Persist the actual output image size in the gallery recipe instead of the
  request sliders: Transform/Inpaint/Edit derive the size from the uploaded
  image, Extend grows the canvas, and Upscale resizes it, so the sliders
  recorded (and later restored) the wrong dimensions for those workflows.
- Reject a remote '*-GGUF' repo loaded as a full pipeline (no single-file
  name) in validate_load_request, so the unloadable pick fails before chat is
  evicted rather than deep in from_pretrained.
- Only publish an image-conditioned from_pipe wrapper to the shared aux cache
  when the load is still current: from_pipe runs under the generate lock but
  not the state lock, so an unload racing its construction could otherwise
  cache a wrapper over torn-down modules that a later load would reuse.
- Verify the Windows CUDA runtime archive checksum before extracting it, like
  the main sd-cli archive, so a corrupt or tampered runtime is rejected rather
  than extracted next to the binary.
2026-07-02 06:21:07 +00:00
Daniel Han
048d0422c7 Harden ControlNet resolve, gallery metadata, and the control-type picker
Check cancellation immediately after a ControlNet from_pretrained and before
any device placement, so an unload/eviction that raced the download does not
allocate several GB onto the GPU after the load was already cleared.

Require a loadable weight or shard index (not just config.json) before a local
ControlNet folder is advertised, so an interrupted copy is hidden instead of
failing deep in from_pretrained as a generic 500.

Do not record a strength-0 ControlNet in the gallery recipe: it is treated as
disabled and skipped, so the image is unconditioned and the metadata must not
claim a ControlNet was applied.

Build the control-type picker from the selected ControlNet's advertised
control_types instead of a hardcoded passthrough/canny pair, so a union model
with a precomputed depth or pose map sends the correct control_mode.
2026-07-02 05:54:22 +00:00
pre-commit-ci[bot]
3cedcfe781 [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
2026-07-02 05:48:24 +00:00
Daniel Han
7227978978 Harden diffusion LoRA handling on the diffusers and native paths
Reject LoRA on a torch.compile'd diffusers transformer (Speed=default/max):
diffusers requires the adapter loaded before compilation, so applying one to
the already-compiled module fails with adapter-key mismatches. The status
gate now hides the picker and generate raises a clear message instead.

Convert a cancelled Hub LoRA download (RuntimeError Cancelled) to the
diffusion cancellation sentinel in resolve_specs, so an unload/superseding
load during resolution maps to a 409 instead of a generic server error.

Drop weight-0 LoRA rows before the native support gate so a request carrying
only disabled adapters stays a no-op on families where native LoRA is
unsupported, matching the diffusers path.

Reject duplicate LoRA ids in the request model: both apply paths suffix
colliding names, so a repeated id would stack the same adapter past its
per-adapter weight bound.

Strip all user-typed <lora:...> prompt tags on the native path (only the
selected adapters are materialized in the managed lora-model-dir, so an
unselected tag can never resolve), and restore saved LoRA selections from a
gallery recipe so restore reproduces a LoRA image.
2026-07-02 05:48:06 +00:00
Daniel Han
c2b25feaee Guard inference loads and worker lifetime against diffusion training
Teach the chat and image load guards about an active diffusion (SDXL) LoRA
job: a chat load is refused (its footprint cannot be fit-checked against the
trainer) and an image load is refused outright, mirroring the existing LLM
training guards, so a load can no longer allocate GPU memory alongside the
trainer and undo the pre-start cleanup.

Bind the diffusion trainer subprocess to the parent's lifetime and scrub the
native path lease secret from it by running the child through
run_without_native_path_secret, matching the inference/export/LLM workers, so
a Studio crash or kill no longer leaves the trainer holding the GPU.

Reset in_model_load on the complete and error terminal events: a stop or
failure during model loading otherwise leaves the status reporting a stale
loading indicator after the job has ended.
2026-07-02 05:47:50 +00:00
Daniel Han
8772f81e75 Address Codex review findings on the image-workflows PR
Keep diffusion.py importable without torch: the compile/arch patch modules
import torch at module level, so import them lazily at their load/unload
call sites instead of at module load. This restores the torchless contract
so get_diffusion_backend() works on a CPU/native sd.cpp install.

Match family reject keywords and aliases as whole path/name segments, not
raw substrings, so an unrelated word like edited, edition, or kontextual no
longer misroutes or hides a valid base image model, while supported edit
families (Qwen-Image-Edit, FLUX Kontext) still resolve. Mirror the same
segment matching in the picker task filter.

Route FLUX.2-dev native guidance through --guidance like the other FLUX
families rather than --cfg-scale. Reject native upscale requests that have
no input image. Read image header dimensions and reject over-limit inputs
before decoding pixels, so a crafted small-payload image cannot spike
memory. Reject an upscale that would shrink the source below its input
size. Validate the model_kind against the filename extension before the
GPU handoff. Estimate a local diffusers pipeline's size from its on-disk
weights so auto memory planning does not skip offload and OOM. Report
workflows: [txt2img] from the native backend status so the Create tab
stays enabled for a loaded native model. Clamp the outpaint canvas to the
backend's 4096px decode limit.

Adds regression tests for segment matching and kind/extension validation.
2026-07-02 05:46:50 +00:00
Daniel Han
f58c3ddb07 Count LR scheduler warmup/decay in optimizer steps, not micro-steps
lr_sched.step() runs once per outer optimizer step (after the gradient
accumulation inner loop), for train_steps total. The scheduler was
configured with num_warmup_steps and num_training_steps multiplied by
gradient_accumulation_steps, so with accumulation > 1 a warmup or
non-constant schedule stretched past the run and never reached the
intended decay. Count both in optimizer steps.
2026-07-02 05:46:48 +00:00
Daniel Han
18f9510d11 Address a further round of Codex review findings on the image PR
Backend:
- validate_load_request rejects a non-.gguf single-file name before the GPU
  handoff, so a family-looking repo paired with README.md no longer evicts the
  chat model and only fails in the background load.
- detect_family scopes the edit/kontext/inpaint keyword check to the model id
  or filename basename, not arbitrary parent directories, so a valid
  text-to-image file under a folder named edit is no longer rejected.
- the images gallery listing skips records that fail schema validation, so one
  corrupt or hand-dropped PNG can no longer 500 the whole endpoint.
- _terminate reaps the killed sd-cli child so cancellation and timeout paths do
  not leak zombie process-table entries.
- the images load route gates the chat-eviction handoff on the resolved device
  being non-CPU, so a CPU-only diffusers fallback no longer evicts a resident
  chat model for a load that cannot use the GPU.

Frontend:
- treat Images as a chat-like full-height route (no outer padding or scroll) so
  its picker is not pushed down and the gallery is not clipped.
- allow /images under the chat-only guard so the native CPU/MPS image path is
  reachable on the no-GPU hosts it was built for.
- roll the optimistic quant label back when a same-repo swap fails after the
  load started, so the selector never advertises a quant that is not loaded.
2026-07-02 05:41:23 +00:00
Daniel Han
2b3d75df0b Merge remote-tracking branch 'origin/diffusion-lora-training' into diffusion-lora-training-api 2026-07-02 04:34:50 +00:00
Daniel Han
e3d8e5ef0f Merge remote-tracking branch 'origin/diffusion-lora-ux' into diffusion-lora-training 2026-07-02 04:34:48 +00:00
Daniel Han
939ba33b1d Merge branch 'diffusion-sdxl' of https://github.com/unslothai/unsloth into diffusion-sdxl 2026-07-02 04:34:21 +00:00
Daniel Han
5987caf940 Align the VAE to the denoiser's first FLOATING dtype, not its first parameter
A GGUF-quantized transformer's leading parameters are packed uint8 storage,
so reading next(parameters()).dtype handed nn.Module.to() an integer dtype
and every image-conditioned generation on a GGUF model (Qwen-Image-Edit)
failed with a 500. Probe the parameters for the first floating dtype, treat
an all-integer module as a no-op, and also catch TypeError so an unexpected
dtype can never break generation. Regression test included.
2026-07-02 04:34:21 +00:00
Daniel Han
d773ce00fa Merge remote-tracking branch 'origin/diffusion-lora-training' into diffusion-lora-training-api 2026-07-02 04:01:45 +00:00
Daniel Han
3e4469bb9f Merge remote-tracking branch 'origin/diffusion-lora-ux' into diffusion-lora-training 2026-07-02 04:01:44 +00:00
pre-commit-ci[bot]
57c6e11c36 [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
2026-07-02 04:01:09 +00:00
Daniel Han
9e3aa584c6 Merge remote-tracking branch 'origin/diffusion-controlnet' into diffusion-sdxl
# Conflicts:
#	studio/backend/core/inference/diffusion.py
2026-07-02 04:00:09 +00:00
Daniel Han
23b2c13c75 Merge remote-tracking branch 'origin/diffusion-lora' into diffusion-controlnet
# Conflicts:
#	studio/backend/core/inference/diffusion.py
2026-07-02 03:57:51 +00:00
Daniel Han
89ff99475d Merge remote-tracking branch 'origin/diffusion-image-workflows' into diffusion-lora 2026-07-02 03:56:32 +00:00
Daniel Han
c58856709a Merge remote-tracking branch 'origin/image-generation' into diffusion-image-workflows
# Conflicts:
#	studio/backend/core/inference/diffusion.py
2026-07-02 03:55:35 +00:00
Daniel Han
a4277a01e4 Honor memory_mode over legacy cpu_offload and prefetch dense-quant transformer shards
plan_diffusion_memory only applies the legacy cpu_offload override when no
memory_mode was supplied, matching the documented API contract that
memory_mode overrides cpu_offload when set; an explicit fast request now
stays resident even if the old flag is also enabled.

The transformer-quant dense path fetches the base repo's transformer/
shards inside the locked finalize phase, where unload and cancellation
cannot preempt the multi-GB download. The load worker now widens the
preemptible prefetch to include those shards when that path can actually
run: quant requested and supported for the device, scheme resolvable, and
no pre-quantized checkpoint shortcutting the dense build.
2026-07-02 03:51:53 +00:00
Daniel Han
d9a118af95 Merge remote-tracking branch 'origin/diffusion-lora-training' into diffusion-lora-training-api 2026-07-02 03:38:54 +00:00
Daniel Han
370544c8ee Merge remote-tracking branch 'origin/diffusion-lora-ux' into diffusion-lora-training 2026-07-02 03:38:52 +00:00
Daniel Han
4dbd3e538d Merge remote-tracking branch 'origin/diffusion-controlnet' into diffusion-sdxl 2026-07-02 03:38:49 +00:00
Daniel Han
d06b68c83a Merge remote-tracking branch 'origin/diffusion-lora' into diffusion-controlnet 2026-07-02 03:38:46 +00:00
Daniel Han
ecb797ab42 Merge remote-tracking branch 'origin/diffusion-image-workflows' into diffusion-lora 2026-07-02 03:36:58 +00:00
Daniel Han
c800e89206 Merge remote-tracking branch 'origin/image-generation' into diffusion-image-workflows
# Conflicts:
#	studio/backend/core/inference/diffusion.py
#	studio/frontend/src/features/images/images-page.tsx
2026-07-02 03:36:50 +00:00
pre-commit-ci[bot]
dd792c6312 [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
2026-07-02 03:30:16 +00:00
Daniel Han
a9e5a80654 Address the round of Codex review findings on the merged diffusion phases
Memory planning and dense-quant path: size a local diffusers base's
resident companions from its on-disk VAE and text-encoder weights instead
of folding them to zero, feed the distilled variant hint into the runtime
headroom estimate so turbo and schnell models are not over-reserved, place
group-offload companions resident before attaching the transformer hooks
so a failed placement falls back to whole-module offload instead of
crashing, and bail out of the dense transformer download before it starts
when the requested quant scheme is unsupported so the load falls back to
GGUF cleanly.

sd.cpp stack: scrub the native path lease secret from sd-cli child env,
redact native load-progress errors, forward the resolved accelerator when
auto-installing a forced-native binary, release stale diffusion GPU
ownership on CPU-native loads, and remove the sd.cpp install tree on
uninstall.

Prequant and scripts: reject prequant artifacts missing base_model_id
when a base is requested, expanduser before checkpoint existence checks,
record and validate the int8 exclusion filter and fp8 fast-accum in
checkpoint metadata, make verify_prequant_backend allowlist its local
checkpoint and fail on missing or bad LPIPS and on load-peak regressions,
average only finite PSNR values in diffusion_quality, and reset the
process-wide attention backend between perf probe variants.

API and UI: normalize attention_backend casing before Literal validation,
close hidden popovers when leaving the Images page, and clear the stale
quant label when loading a direct local GGUF file.
2026-07-02 03:29:18 +00:00
Daniel Han
b33418e14a Merge remote-tracking branch 'origin/diffusion-image-workflows' into diffusion-lora 2026-07-02 02:39:00 +00:00
Daniel Han
2aa379d9cc Merge remote-tracking branch 'origin/image-generation' into diffusion-image-workflows 2026-07-02 02:37:52 +00:00
Daniel Han
22f49b5ac2 Merge remote-tracking branch 'origin/main' into image-generation
# Conflicts:
#	scripts/scan_packages_baseline.json
2026-07-02 02:36:18 +00:00
Daniel Han
7c080f3222 Merge remote-tracking branch 'origin/diffusion-lora-training' into diffusion-lora-training-api 2026-07-02 02:32:10 +00:00
Daniel Han
2ce0bdbdf6 Merge remote-tracking branch 'origin/diffusion-lora-ux' into diffusion-lora-training 2026-07-02 02:31:18 +00:00
Daniel Han
e6bf4c4cd6 Merge remote-tracking branch 'origin/diffusion-controlnet' into diffusion-sdxl 2026-07-02 02:28:33 +00:00
Daniel Han
c3196cb8bd Merge remote-tracking branch 'origin/diffusion-lora' into diffusion-controlnet 2026-07-02 02:27:40 +00:00
Daniel Han
b9b80a4c83 Merge remote-tracking branch 'origin/diffusion-image-workflows' into diffusion-lora
# Conflicts:
#	studio/backend/core/inference/diffusion.py
2026-07-02 02:26:46 +00:00