Commit graph

2,855 commits

Author SHA1 Message Date
Daniel Han
7e1bc5cbde Tighten fault-path comments added by the video/diffusion hardening pass 2026-07-13 17:33:01 +00:00
Daniel Han
133f6fecf7 Studio: harden video/diffusion cache, attention, and CFG-parallel fault paths
- diffusion_attention: arch-gate FlashAttention 2 to Ampere (SM80)+ in both the
  primary selector and the heterogeneous-replica guard (it crashed on pre-Ampere).
- diffusion_cfg_parallel: convert boolean attn masks to additive bias before the direct
  cuDNN op so partial masks match F.scaled_dot_product_attention; make proxy disable_cache
  transactional (clean both branches, mark broken, surface a reload-required error).
- diffusion_cache: fail closed when a magcache step-count resize or below-threshold
  disable cannot remove the old cache; surface a failed enable+cleanup instead of a false
  uncached None.
- video: roll back earlier experts when a later expert raises in the all-or-none step-cache
  loop; fail the load when the primary-only cache cannot be re-engaged through the
  CFG-parallel proxy; validate transformer_cache_quality and cfg_parallel before the worker.
- scripts: place the fp8 ablation pipeline on CUDA; fail closed on a failed magcache resize
  in the speedmem bench; label OOM distinctly in the SDPA mask probe.
- tests: regressions for the FA2 arch gate, transactional proxy disable, all-or-none
  exception rollback, magcache fail-closed transitions, and enable+cleanup failure.
2026-07-13 09:46:17 +00:00
Daniel Han
9eadfc822d Merge remote-tracking branch 'origin/image-generation' into r7021
# Conflicts:
#	studio/backend/routes/inference.py
#	studio/backend/routes/video.py
2026-07-13 09:28:04 +00:00
Daniel Han
b127256eb4 Studio: surface local pipeline scan roots, tag single-file checkpoints by filename, mark companion-only pipelines partial
- _scan_models_dir: admit a scan folder that is itself a diffusers pipeline
  (root model_index.json, weights in transformer/ vae/ subdirs). _is_model_directory
  rejects such a root, so the child scan would list the component subdirs as bogus
  models and hide the real pipeline; treat the root as one model via _local_pipeline_index.

- _local_is_diffusers / _local_model_task: include the sole checkpoint filename in the
  family-detection needles (_local_family_needles, resolved via resolve_local_single_file).
  A generically named folder holding one loadable qwen-image-*.safetensors / ltx-*.safetensors
  identifies its family only from the filename; the load route already resolves that file, so
  tag it or the task-scoped picker (which rejects task=null) hides the on-device model.

- list_cached_models: mark a companion-only base snapshot partial. A GGUF image load prefetches
  the base repo's VAE / text-encoder / model_index.json but skips the transformer (the GGUF
  supplies it); the snapshot has a pipeline manifest yet is not a loadable BF16 pipeline, and
  _cached_repo_partial misses it. _repo_pipeline_missing_denoiser flags a pipeline snapshot whose
  transformer/ or unet/ component carries no weight, so the picker drops it instead of advertising
  it as fully on-device.
2026-07-13 08:26:43 +00:00
pre-commit-ci[bot]
11330b8de5 [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
2026-07-13 06:31:51 +00:00
Daniel Han
899465ed80 Studio: close arbiter load-registration race and surface native progress + local pipeline folders
Publish native sd.cpp generate progress (_gen) before LoRA resolution so a reload probe reads active during setup, matching the diffusers path.

Register the diffusion/video GPU load under the arbiter lock (acquire_for now takes a register callback) so a competing acquire cannot evict an owner before its load is marked in-flight and let two loaders allocate VRAM at once.

Admit local diffusers pipeline folders (root model_index.json, weights in component subdirs) in the local model scan so they reach task tagging and the On Device picker.
2026-07-13 06:31:00 +00:00
Daniel Han
e0ef488f47 Tighten comments and docstrings added by the image-generation fixes 2026-07-13 05:29:09 +00:00
Daniel Han
dabb12e198 Studio: gate dataset uploads on the symlink check and surface local video single-file checkpoints 2026-07-13 04:21:36 +00:00
Daniel Han
a3388820a2 Reflow verbose diffusion comments to fewer lines 2026-07-13 03:30:48 +00:00
Daniel Han
da1770bb44 Refuse sd.cpp install into unowned non-empty target dir
When the install target already exists, is non-empty and lacks the
.unsloth-studio-owned marker (a user's own stable-diffusion.cpp checkout,
or unrelated files beside a custom Studio root), install() previously still
extracted the release into it. Skipping the ownership marker only stopped the
uninstaller from deleting the directory; extraction still merged binaries into
the user's working tree and could overwrite same-named files.

Fail up front with a clear message pointing the user at a fresh/empty location
before any download or extraction, leaving their directory untouched. Update
the ownership test suite to assert the refusal.
2026-07-13 03:17:49 +00:00
Daniel Han
31c8ab776a Register static video compile-cache shapes before save
Under speed=max the video DiT compiles dynamic=False, so inductor
artifacts are per (width, height, frames). The save path never called
compile_cache.register_shape, so after a bundle hit ctx.saved stayed
true and later resolutions/frame counts never re-dirtied the bundle,
leaving those shapes to recompile on every restart. Register the actual
generation shape before saving, gated on the static tier, mirroring the
image backend.
2026-07-13 02:12:13 +00:00
Daniel Han
5a17614b51 Reject native batch seeds outside the JSON-safe range 2026-07-13 02:09:15 +00:00
pre-commit-ci[bot]
30d3cd91dc [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
2026-07-13 01:31:35 +00:00
Daniel Han
6e2e8c846c Harden video diffusion cache, CFG-parallel replica, and layerwise-fp8 rollback
- diffusion_attention: clear the HunyuanVideo-1.5 null-mask flag with an always_call
  post-hook so it is scoped to one hooked forward and never latches across an
  exception; add attention_backend_supported_on_device to arch-gate an
  already-resolved backend on a specific (heterogeneous) CUDA device.
- video: make the explicit MagCache resize transactional via _step_cache_all_or_none
  (refuse to stack a fresh cache over one that could not be disabled; roll a mixed
  resize back and report the true state); raise on a failed all-or-none rollback
  instead of falsely reporting an uncached pipeline.
- diffusion_cfg_parallel: re-validate the attention backend on the replica device
  and pin native there when unsupported; mirror the primary's max tier on the
  replica (max-autotune compile + direct QKV fusion) via a new speed_mode arg;
  prefer a viable heterogeneous secondary GPU over an unusable identical one; clear
  the const cache at each plan_generation.
- diffusion_vae_quant / diffusion_precision: detect a partial diffusers
  layerwise-fp8 mutation (leftover casting hooks the torchao detector cannot see)
  and fail the load closed, while a clean failure still falls back to dense.
- video_speedmem_bench: engage the dual-expert cache all-or-none like the loader.
- frontend video api: add text_encoder_quant / vae_quant and the auto/off literals
  to VideoLoadRequest so typed callers match the backend contract.
2026-07-13 01:30:22 +00:00
pre-commit-ci[bot]
499bada598 [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
2026-07-13 01:23:46 +00:00
Daniel Han
7ec9e77a5d Studio: fix diffusion install ownership, dataset upload atomicity, gallery pagination, and teardown races
install_sd_cpp_prebuilt: only write the .unsloth-studio-owned marker when the
install created the target directory or it was empty. Adopting a pre-existing,
unowned, non-empty directory (a user's own stable-diffusion.cpp checkout) made
it eligible for the uninstaller's recursive delete.

routes/training upload: make the multi-file promotion transactional. Back up
each displaced original and roll every destination back on any failure, so a
mid-loop rename error can no longer partially overwrite the live dataset.

routes/training _resolve_dataset_folder: reject a symlinked dataset directory
and prove the resolved folder stays under the datasets root, so image
read/caption/delete cannot escape the root through a link.

routes/training delete: escape glob metacharacters in the thumbnail filename so
deleting an image named like [ab].png removes only its own thumbnails.

image_gallery / video_gallery listing: filter records against the response
schema inside the pager via a valid callback, so offset/limit/has_more all count
over accepted records. A leading schema-invalid record no longer returns an
empty page with has_more=true and stalls infinite scroll at offset 0.

image_gallery / video_gallery save: publish via a temp file plus atomic rename
(the sidecar is the video pair's commit marker) and clean up on failure, so a
partial write never surfaces a truncated PNG or strands an orphan MP4.

diffusion_train_common discovery: treat an empty caption sidecar as a metadata
tombstone that still falls through to the dreambooth instance prompt, so
clearing every metadata caption no longer fails with no captioned images found.

diffusion backend unload: wait for an in-flight denoise to exit before tearing
down process-wide patches and state, mirroring the load path.

diffusion_engine_router: serialize the whole check/unload/publish transition so
a concurrent selection cannot return the engine being unloaded.

uninstall.ps1: gate the default sd.cpp process stop on the owner marker so a
user's own sd-server is not terminated for a directory we then keep.
2026-07-13 01:22:54 +00:00
Daniel Han
e727b9e82f Merge branch 'image-generation' into video-diffusion-improvements 2026-07-12 13:59:40 +00:00
Daniel Han
21052db120 Publish image generation active state before pre-denoise setup
generate() assigned self._gen only at the pipe() call, after deferred
compile, LoRA resolution/application, and ControlNet download/build had
run. Across that setup window generate_progress() reported inactive even
though _generate_lock was held, so a reloaded page's mount probe showed
idle and let a second generate queue behind the first.

Publish an active step-0 _GenState the moment the generation lock is
acquired, before the setup work, and clear it in the outer finally so a
setup-time error cannot leave the UI stuck active. Mirrors the video
backend's queued phase and the training start guard.
2026-07-12 13:42:24 +00:00
pre-commit-ci[bot]
7776148463 [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
2026-07-12 12:44:19 +00:00
Daniel Han
23a71b1c2c Close video single-file, training reservation, and image mount-resume gaps
Route on-device single-checkpoint video folders through the single_file loader:
a bare local .safetensors directory (no model_index.json) is advertised as a
pipeline with no filename, so validation rejected it before it could load.
Reinterpret the pick as a single_file load of the sole checkpoint, mirroring the
image load route.

Treat a reserved-but-not-yet-spawned LLM training start as active in
is_training_active() so /images/load, /video/load, and /diffusion/start cannot
race the reserved run for VRAM during the pre-spawn free window. Mirrors the
diffusion training service reservation.

Resume an in-flight image generation on the Images page mount: probe
generate-progress, re-enter the poll loop, and refresh the gallery on completion
so a run started elsewhere is reflected and its saved image appears without a
manual refresh. Seed resident image defaults from the resolved base_repo rather
than a possibly path-shaped repo_id so the first resident generation uses the
right recipe.
2026-07-12 12:41:21 +00:00
Daniel Han
4cc35aa87a Tighten comments in the image stack tests and scripts 2026-07-12 12:21:14 +00:00
Daniel Han
0017674664 Tighten comments in the image and video frontend 2026-07-12 12:15:36 +00:00
Daniel Han
ecae46cbfb Tighten comments in the diffusion training core and API models 2026-07-12 12:06:44 +00:00
Daniel Han
3a6a007038 Tighten comments in the image stack routes 2026-07-12 12:00:44 +00:00
Daniel Han
1762dab12f Tighten comments across the remaining image stack files 2026-07-12 11:46:23 +00:00
Daniel Han
9fde4b9991 Tighten comments across the remaining image stack files 2026-07-12 11:40:05 +00:00
Daniel Han
226363c7f0 Tighten comments across the video speed stack 2026-07-12 10:59:41 +00:00
Daniel Han
892696733e Tighten comments across the image generation stack 2026-07-12 10:55:39 +00:00
Daniel Han
dcd6666f8d Thread the task-scoped GGUF fit budget through every picker expander
The previous commit introduced the single-device budget but wired it
into only the downloaded-group and Hub search sites. The LM Studio,
custom-folder, local-dir, live-search and exported-GGUF expanders
reachable from the Images/Video pickers still measured against the
summed multi-GPU total, as did the size-based GGUF row badge, so those
paths could still recommend a quant that OOMs on a single device. All
GgufVariantExpander call sites in HubModelPicker now share
expanderGpuGb, and the row badge derives the same task-scoped budget.
2026-07-11 18:36:31 +00:00
Daniel Han
cece544fd9 Fix stale LoRA closure, variant fit budget, and hidden dataset remove button
The generate callback omitted loraCapable from its dependencies, so when
an auto-compile flips supports_lora off mid-session the memoized handler
still sent the previously selected adapters and the next generation
failed with the backend's LoRA-not-supported error instead of omitting
adapters the UI had already hidden.

HubModelPicker's GGUF variant expanders, format lists and Hub row fit
hints measured against the summed multi-GPU total. When the picker is
task-scoped (Images/Video) the loaders place the whole pipeline on one
device, so a variant could be recommended as fitting and then OOM at
load; those sites now share the single-device budget the group fit gate
already uses, while chat pickers keep the summed total since llama.cpp
splits layers across devices.

The dataset labeling grid's Remove button relied on group-hover with no
group parent, leaving it permanently invisible to mouse users; the image
wrapper now carries the group class.
2026-07-11 17:32:05 +00:00
pre-commit-ci[bot]
668088436e [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
2026-07-11 16:51:12 +00:00
Daniel Han
e1fa4fec04 Studio diffusion: fix static compile shape registration and prequant path validation
Register the dims the forward actually compiled with: image-conditioned
workflows (img2img, inpaint, upscale, edit) run at the input image's size,
not the slider's, so recording the slider values marked never-compiled
shapes as covered and warm restarts kept paying compile for the real one.

Validate a request-supplied transformer_prequant_path (existence plus the
UNSLOTH_ALLOW_LOCAL_PREQUANT_PATH allowlist) before treating prequant as
available at the resident-fit re-check: an unusable path skipped the dense
fit check up front and then fell back to materializing dense bf16 after
the previous pipeline was evicted, recreating the post-eviction OOM path.
Shared as usable_prequant_source, also used by the auto-policy planner.
2026-07-11 16:50:19 +00:00
Daniel Han
316c8b5a81 Studio video: resume a background generation on page mount
A generation runs on a backend daemon thread and survives a page reload,
but the mount effect only probed the load progress, so reloading during a
generate showed an idle page that never picked up the finished clip until
a manual refresh. Hoist the generate poll loop out of handleGenerate and
re-enter it on mount when generate-progress reports an active job; merge a
terminal completed record into the gallery to cover the race with the
mount gallery fetch.
2026-07-11 16:50:19 +00:00
Daniel Han
57f08ebfb2 Merge remote-tracking branch 'origin/main' into ig_merge 2026-07-11 15:15:54 +00:00
Daniel Han
6412efd7d9
Studio: auto-detect completion masking markers, stop silent full-sequence training (#7054)
* Auto-detect completion masking markers with template table fallback

Studio's train_on_completions previously relied only on the hardcoded
MODEL_TO_TEMPLATE_MAPPER / TEMPLATE_TO_RESPONSES_MAPPER tables and
silently disabled masking when a model was not in the table, so unmapped
models (LFM2-8B-A1B, DeepSeek, and others) trained on full sequences
without telling the user. Several mapped templates (glm, mistral, llama,
starling, zephyr, qwen3-thinking) also carried markers that mask every
assistant token, which made every row drop in the post-masking filter.

Both training callsites (CUDA trainer.py and MLX worker.py) now share
utils.datasets.completion_masking.apply_completion_masking:

- Try unsloth_zoo chat template auto-detection first; it raises loudly
  when the template cannot be parsed and never masks the EOS token.
- gpt-oss models keep their manual markers so non-final assistant
  <|end|> tokens stay trained, matching current behavior.
- If auto-detection raises, fall back to the template table exactly as
  before.
- If the table also misses, emit an explicit user-visible warning that
  completion masking could not be applied and full-sequence training
  will occur, instead of a quiet log line.

The >30 percent dropped-rows safety net in trainer.py now guards the
auto path as well. Table consumers for inference and chat templates are
unchanged. Validated against one representative tokenizer for every
template in TEMPLATE_TO_RESPONSES_MAPPER plus the unmapped models:
no template regresses; unit tests cover the four decision paths.

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Restrict masking fallback to marker detection failures

The auto branch wrapped the whole train_on_responses_only call, so a real
failure while applying the masking (dataset map, tokenization) was treated
as a detection miss and training silently proceeded on full sequences.
Detect markers separately via get_chat_template_parts (test seam via
detect_fn), then apply them with errors propagating, matching the manual
path. Tokenizers with preset unsloth marker attrs skip detection and call
bare so zoo reuses the stored parts.

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Fail the run when applying completion masking raises

The helper already falls back internally on detection failures and returns
applied=False on a double miss, so an exception reaching the callsites is a
real failure applying the masking. Remove the callsite catches that
downgraded it to full-sequence training; the run now fails visibly instead.

Also use the explicit re-export alias form in utils/datasets/__init__.py for
the two new names, satisfying the import-hoist source lint.

* Import completion masking from its submodule

The import-hoist source lint counts only real name loads, so package-level
re-exports of the two new names cannot satisfy it. Import
apply_completion_masking from utils.datasets.completion_masking directly at
both callsites and leave utils/datasets/__init__.py untouched.

* Completion masking: gpt-oss renames and MLX raw/alpaca parity

Renamed or private gpt-oss checkpoints are name-detected as gpt-oss but miss
the exact-name table; default them to the gpt-oss template markers instead of
falling through to full-sequence training.

Gate the MLX masking call on not raw_text_mode and format_type != alpaca,
mirroring the CUDA path: raw/CPT text has no chat turns to mask and
Alpaca-rendered text lacks the tokenizer's chat markers.

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Define raw_text_mode outside the MLX feature-detect block

With an older zoo lacking the append_eos config field, the masking
gate referenced raw_text_mode before assignment. Hoist the assignment
above the feature detection so both consumers see it.

* Gate MLX masking on the formatter's resolved format

format_type auto can resolve to alpaca or raw text; the masking skip
checked only the requested value, so auto-detected Alpaca data got
chat-template markers applied to rendered prompt text. Track the
final_format returned by format_and_template_dataset and gate on it,
matching the CUDA path.

* Unwrap the mlx-lm TokenizerWrapper before marker checks

The wrapper delegates plain reads to the wrapped HF tokenizer but hides
underscore attrs, so preset unsloth markers were invisible and detection
relied on the loader's call patch. Unwrap to the real tokenizer first,
as the zoo MLX resolver does.

* Tighten masking comments

* gpt-oss: auto-detect markers first like every other template

The quantized and BF16 gpt-oss checkpoints ship a chat template without
the channel final header, so the pinned manual markers match nothing
there and masking trained zero tokens. Auto-detection derives markers
from whichever template the checkpoint ships and keeps the final
terminator trained; the manual gpt-oss markers remain the detection
failure fallback, including for renamed checkpoints.

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Tighten comments

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2026-07-11 05:13:45 -07:00
Daniel Han
97161c89d6
Studio: route models by CONFIG_MAPPING_NAMES instead of hardcoded tables (#7043)
* Studio: route models by CONFIG_MAPPING_NAMES instead of hardcoded tables

A model whose model_type is absent from an overlay's transformers cannot load
there, so a new MoE arch not yet in the tier tables gets routed to default and
fails (e.g. lfm2_moe, deepseek_v4). Add a static resolver that parses each
overlay's CONFIG_MAPPING_NAMES straight from source (AST only, no import, no
network, no trust_remote_code) and picks the lowest tier that ships the
model_type. Runs after the existing checks and only ever upgrades default, so
no existing routing changes and new archs no longer need a table edit.

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Studio router: harden the CONFIG_MAPPING_NAMES resolver

- Resolve the default tier map from the base install, skipping any .venv_t5_*
  sidecar on sys.path, so an in-process 5.x activation cannot make a 5.x-only
  model look loadable by 4.x.
- Do not cache an overlay whose sidecar dir is absent, so a later call re-reads
  it once provisioned instead of serving a stale empty map.
- Also collect model types added via CONFIG_MAPPING_NAMES.update({...}) and
  **{...} unpacking, not just the literal assignment (5.10 uses both).
- Wrap the AST walk in the try/except so a malformed source can never crash tier
  resolution.
- Feed the mapping fallback from _load_config_json so a config served from the
  hub cache during a transient outage still routes new architectures.

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Tighten comments

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2026-07-11 05:12:37 -07:00
Daniel Han
c3feac6160
Studio: route lfm2_moe (LFM2-8B-A1B) to transformers 5.3.0 (#7040)
LFM2-8B-A1B and any other lfm2_moe checkpoint were missing from the
transformers tier tables, so they fell through to the default 4.57.x
sidecar, which does not register lfm2_moe and errors with
"not supported yet in transformers==4.57.6". Only lfm2_vl was listed.

Add Lfm2MoeForCausalLM / lfm2_moe to the 5.3.0 tier (lfm2_moe is
registered in transformers 5.3.0). get_transformers_tier now returns
530 for LFM2-8B-A1B and the model loads and trains as expected.
2026-07-11 05:08:07 -07:00
pre-commit-ci[bot]
a3593ba78a [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
2026-07-11 10:09:03 +00:00
Daniel Han
514f4c60fe Harden the video speed stack: cache quality pin, device identity, transactional caches, quant safety
- Wan2.2-A14B step cache: pin the balanced FBCache threshold to 0.08 even when
  quant is active (per-family override in diffusion_cache.py). Auto-fp8 made the
  generic quant promotion (0.12) the family's effective default at pairwise LPIPS
  0.128, over the 0.08 quality gate the balanced preset is held to. Measured
  operating point with fp8 actually engaged (1280x720/81f/50 steps, B200):
  fb@0.08 = 1.08x at 0.129 vs the old fb@0.12 = 2.58x at 0.181; documented in
  the preset table. Explicit thresholds and the fast preset are unaffected.

- MagCache curves: validated the shipped 33-frame calibrations at the production
  121-frame default for hunyuanvideo-1.5-720p, hunyuanvideo-1.5 (480p) and
  wan2.2-ti2v-5b. Fresh 121-frame calibrations differ by <= 0.024 max abs entry
  and produce byte-identical frames at the auto presets (hv720 quality 1.69x at
  LPIPS 0.042, hv480 quality 1.66x at 0.018, wan5b balanced 1.74x at 0.026, all
  pairwise vs the same-load uncached stack), so the curves ship unchanged with
  the frame-count transfer documented next to them.

- Dual-GPU CFG parallelism: the secondary-device pick now prefers a device whose
  name and compute capability match the primary, and the gate declines a
  mismatched pair in auto mode (eager kernel selection is arch-dependent, so the
  advertised bit-identity cannot hold across different GPU models); an explicit
  cfg_parallel=on proceeds but is downgraded to lossless=False with a warning.

- A14B expert step cache is now all-or-none, mirroring the transactional quant
  loop: a mixed outcome (cache engaged on one expert but not the other) is
  rolled back and reported uncached with the failure reason, on both the load
  path and the generation-time auto toggle.

- Partial torchao quantization is no longer reported as dense: after an
  in-place quantize_/caster failure, the DiT / text encoder / VAE is scanned
  for leftover torchao tensor-subclass parameters and the load fails with a
  clear error when any are found (a half-quantized module cannot run as dense,
  and offload's Module.to() crashes on torchao tensors). Failures that swapped
  nothing keep the best-effort dense fallback.

- Cleanup: apply_attention_backend / apply_speed_optims / the attention trim
  are called once on the pipe (they already fan out over every DiT internally),
  so the second A14B expert no longer passes through them twice; the stale
  dual-DiT helper comment is rewritten to match the two helper shapes.

Tests: device-identity picker/gate/lossy-plan coverage, per-family threshold
pin scoping, all-or-none rollback in both failure directions, and partial-quant
detection for all three quant modules.
2026-07-11 10:06:52 +00:00
pre-commit-ci[bot]
35eb80c8c4 [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
2026-07-11 08:10:42 +00:00
Daniel Han
47c202eee1 Let real generations preempt the background compile prewarm
The prewarm registered its cancel event in _active_generate_cancel, but a
begin_generate arriving mid-warmup overwrote that slot with its own event
and then queued its worker behind the full warmup on _generate_lock. From
that point unload and cancel_generate signalled the wrong event, so the
warmup could no longer be aborted and the first real request waited out
the 9-54s the prewarm exists to hide.

Track the prewarm's event in a dedicated _prewarm_cancel slot (cleared
identity-checked alongside _active_generate_cancel) and signal it from
begin_generate before registering the real job's event, and from direct
generate() calls that skip begin_generate. The warmup then aborts at its
next step boundary and the real job takes the lock, while unload/cancel
keep working against whichever run is actually active.
2026-07-11 08:10:03 +00:00
pre-commit-ci[bot]
6adcc34e1b [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
2026-07-11 06:49:46 +00:00
Daniel Han
40e3747d43 Absorb the first-generation compile hitch with a post-load background prewarm
vLLM and SGLang finish every compilation at server startup (dummy batches
through each compiled shape) so no request ever pays a compile mid-serving.
The video backend's compiled tier instead paid a first-generation extra after
every restart: ~54 s cold and ~11.3 s even with a warm Mega-cache bundle (the
residual is dynamo tracing plus cudnn.benchmark autotune, which the bundle
cannot carry).

After a compiled DEFAULT-tier resident load commits, a daemon thread now runs
one tiny throwaway generation (192x128 snapped, 4k+1-lattice 9 frames, 2
steps) under the generate lock. The default tier compiles dynamic=True, so
the small trace serves every later resolution. Measured through the real
backend (HunyuanVideo-1.5-480p, B200, 480x288/17f/30 steps):

  warm bundle: first-generation extra 11.3 s -> 2.1 s (9.6 s background warmup)
  cold start:  the full compile moves off the user's first request entirely
               (14.5 s background; first generation extra 2.1 s), and the
               warmup persists the Mega-cache bundle itself
  steady state: unchanged (2.4-2.5 s per 30-step clip in every phase)

Exactly lossless by construction: the warmup only changes when compilation
work happens. It resets its step-cache residuals, the real generation seeds
its own generator, and no process-wide flag is touched.

The warmup registers itself as the active cancellable job, so unload, a new
load, or cancel_generate abort it at a step boundary (verified: unload 2 s
into a running prewarm returns in 6.7 s with the warmup cancelled). It yields
untouched when a real request arrived first and is token-scoped against
superseded loads. Gated per family (supports_compile_prewarm), skipped for
speed=max (static per-shape graphs a warmup shape cannot serve), offload
(every warmup forward would stream the DiT over PCIe), and CFG parallel (its
planner owns compile-sensitive runs); the UNSLOTH_DIFFUSION_COMPILE_PREWARM
kill switch disables it. The decision and reason land in the resolved record.

Tests: +4 hermetic (decision gates, engaged-load spawn with snapped tiny
shape, skip-without-compile, yield to generations / stale tokens); related
backend set 551 passed; ruff clean.
2026-07-11 06:49:07 +00:00
Daniel Han
352fb40089 Warm-save the compile cache by default, compile U-Net denoisers whole-module
diffusion_compile_cache: auto mode now saves the Mega-cache bundle after the
first compiled generation (UNSLOTH_DIFFUSION_COMPILE_CACHE_SAVE=0 opts out), so
users get warm restarts without the distributor env; a bundle hit starts clean
(no pointless rewrite of the just-loaded artifacts) and explicit mode 1/on keeps
the distributor-style re-save. New register_shape + manifest shape coverage: a
STATIC compile produces new artifacts per (width, height, batch), so the
generate path registers each generation's shape and an uncovered shape
re-dirties the context, growing the bundle to cover every shape the session
used. Measured (B200, real backend): Qwen-Image deferred gen-3 hitch 29.1 ->
22.2 s warm with bit-identical output (7.9 MB bundle, ~0.5 s save); SDXL gen-3
115.7 -> 24.7 s and a mid-session 768px recompile 65.8 -> 12.6 s (bundle 63.6 ->
98.7 MB after the 768 re-save).

diffusion_speed: U-Net denoisers (UNet2DConditionModel; no _repeated_blocks, so
the regional compile never reached them) now get a whole-module STATIC
torch.compile on the default tier, plus fused QKV projections and a compiled VAE
decode. Measured on SDXL (30 steps / 7.0 / 1024px, 4 prompts, LPIPS vs the
bit-exact reference): 6.16 -> 3.14 s end to end (1.96x) at LPIPS 0.035, steady
state 0.70-0.88 s/image through the real backend. Rejected on measurement:
dynamic=True whole-module (366 s compile for 39.3 ms/step vs static's 73 s for
26.9), regional BasicTransformerBlock only (45.0 ms/step; ResNet convs stay
eager), max-autotune + inductor flags (25.9 ms/step for a 445 s warmup),
channels-last UNet alone (neutral). DiT tiers unchanged: fused QKV measured
exactly neutral under the regional compile (Qwen-Image 6.53 vs 6.52 s), so it
stays max-only there, and the DiT VAE decode stays eager (a few % of a DiT
generation). compiled_shapes_are_static tells the cache layer which loads are
per-shape (max tier, U-Net whole-module).

diffusion: register each generation's shape with the compile cache before the
save, pass pipe.unet to the cache fingerprint when the pipe has no transformer,
and correct the transformer_quant resolved reason on dense loads (it claimed a
GGUF transformer was loaded on every non-quantized pipeline load).

Tests: 333 passing across the related suites (speed 42, compile_cache 27, cache
40, precision 20, backend, base_precision, transformer_quant, memory); ruff
clean. Full measurement record: outputs/image_optim_round2_audit.md.
2026-07-11 06:18:29 +00:00
pre-commit-ci[bot]
90ee1c3fd2 [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
2026-07-11 06:09:43 +00:00
Daniel Han
d26ef758dd Refuse the kernels auto-install on a pre-1.0 huggingface_hub
Every current release of the kernels package (0.13 through 0.16) builds its
dependency tables with huggingface_hub >= 1.0's strict-dataclass API, and with
an older hub the breakage is not contained to the requested backend: import
kernels raises at module scope, and diffusers imports kernels whenever it is
installed, so a single on-demand install (an explicit flash3/flash4 attention
request on a stack pinned to hub < 1.0) permanently breaks every later
diffusers pipeline import on the box until the package is removed. Reproduced
against hub 0.36.2 with kernels 0.13.0 and 0.16.0: the HunyuanVideo-1.5
pipeline import dies in hub's strict-dataclass validator both times.

_ensure_attention_backend_installed now checks the resident hub version before
installing kernels (_kernels_hub_compatible) and refuses on < 1.0, logging why
and falling back to the native default, which is the best-effort contract the
installer already promises for an uninstallable wheel. The refusal is a policy
decision, not a failed attempt, so it is not memoised and a later request on a
fixed environment can still install. An undeterminable hub version keeps the
previous permissive behaviour, and the gate applies only to the kernels
package: sage/flash-attn/xformers wheels do not import hub at module scope.

Tests: the refusal (nothing memoised), the hub >= 1.0 allow, the
package-scoping, and the version-parse fallback.
2026-07-11 06:06:40 +00:00
Daniel Han
b24a94d1c2 Wire the Mega-cache compile prewarm into the video backend
The pre-warmed torch.compile cache (diffusion_compile_cache.py: fingerprinted
bundles over torch.compiler.save/load_cache_artifacts plus a persistent per-key
TORCHINDUCTOR_CACHE_DIR) was wired into the image backend only, so every video
load re-paid the full first-generation compile after every process restart (the
stock inductor dir lives in /tmp).

video.py now mirrors the image backend exactly: compile_cache.begin runs after
the attention-backend set (the fingerprint keys on the engaged kernel) and
before apply_speed_optims on a compile-eligible default/max tier, keyed on the
same fullgraph decision as the compile itself (an engaged or still-toggleable
step cache and a planned offload both drop it); the context is committed to
_VideoLoadState and compile_cache.save persists the bundle after the first
successful generation (env-gated distributor / first-run-warm mode);
_teardown_state restores the inductor dir, and a token-scoped
_rollback_precommit_compile_cache covers loads that die before the state
commit, mirroring the globals and CFG-parallel rollbacks.

Measured on HunyuanVideo-1.5-480p through the real VideoBackend (B200,
480x288/17f/30 steps): the first-generation extra drops from 107.5 s cold to
13.8 s when the 12.8 MB bundle loads into a fresh inductor dir (0.10 s load)
and to 11.7 s from the persistent per-key dir alone; through the wired
production path a restart lands at 10.5-10.8 s (bundle-only included) vs
86.5 s cold. Steady state is unchanged (2.4-2.6 s), and the loaded artifacts
are the same bits a local compile would produce, so numerics are untouched.

Tests: begin/save/restore lifecycle with the fullgraph keying, the Speed=off
and compile-ineligible skips, and the token-scoped pre-commit rollback.
2026-07-11 06:06:26 +00:00
Daniel Han
d7a5a01522 Gate auto CFG parallel on Speed=off
Speed=off is the reference contract: the loaders pin every auto speed
lever (transformer/TE/VAE quant tri-states) to off, but the cfg_parallel
auto path never consulted speed_active, so a resident two GPU
HunyuanVideo-1.5 load with Speed=off could still reserve a second GPU
and install the CFG-parallel proxy. Auto now returns off when
speed_active is false; an explicit cfg_parallel=on stays honored as a
deliberate override (the install-failure test now exercises exactly
that override path).
2026-07-10 23:02:17 +00:00
Daniel Han
ce32c1e1fd Document why the cache fan-out invalidates only the replica registry 2026-07-10 21:24:39 +00:00
oobabooga
d105bd7b42
Studio: detect Windows Intel GPUs via the registry before WMI (#7064) 2026-07-10 17:59:04 -03:00