Commit graph

945 commits

Author SHA1 Message Date
Daniel Han
2e75b0131c Merge remote-tracking branch 'origin/main' into fold-integration
# Conflicts:
#	scripts/scan_packages_baseline.json
2026-07-07 01:46:07 +00:00
Daniel Han
de099eaecd Merge remote-tracking branch 'origin/video-hunyuan-gate' into fold-integration
# Conflicts:
#	studio/backend/core/inference/video.py
#	studio/backend/routes/models.py
#	studio/backend/tests/test_cached_gguf_routes.py
#	studio/frontend/src/features/images/images-page.tsx
#	studio/frontend/src/features/images/train/diffusion-train-panel.tsx
2026-07-07 01:15:20 +00:00
Daniel Han
c7822fa728 Merge remote-tracking branch 'origin/video-wan' into fold-integration 2026-07-07 01:08:50 +00:00
Daniel Han
341b876212 Merge remote-tracking branch 'origin/video-tab' into fold-integration 2026-07-07 01:08:50 +00:00
Daniel Han
6edb00aa34 Merge remote-tracking branch 'origin/diffusion-more-families' into fold-integration 2026-07-07 01:08:50 +00:00
Daniel Han
2e855a018d Merge remote-tracking branch 'origin/diffusion-auto-badges' into fold-integration
# Conflicts:
#	studio/backend/models/inference.py
#	studio/frontend/src/features/images/images-page.tsx
2026-07-07 01:08:43 +00:00
Daniel Han
d1fbe62aeb Merge remote-tracking branch 'origin/diffusion-auto-install' into fold-integration 2026-07-07 01:06:57 +00:00
Daniel Han
826a31d7b6 Merge remote-tracking branch 'origin/diffusion-auto-policy' into fold-integration 2026-07-07 01:06:57 +00:00
Daniel Han
e1dd2dda6b Merge remote-tracking branch 'origin/diffusion-train-perf2' into fold-integration
# Conflicts:
#	studio/backend/core/training/diffusion_dit_trainer.py
#	studio/backend/core/training/diffusion_train_common.py
2026-07-07 01:06:42 +00:00
Daniel Han
2dbfd3c4a9 Merge remote-tracking branch 'origin/diffusion-krea2' into fold-integration
# Conflicts:
#	studio/backend/core/training/diffusion_train_common.py
2026-07-07 01:03:21 +00:00
Daniel Han
186f381bc6
Studio: diffusion UX polish and stronger auto policies (images + video) (#6885)
* Auto policies: deferred dense compile, video compile default, step cache and precision auto

Image dense loads with speed unset no longer sit at plain off: the load stays
bit-identical eager, and the 3rd generation in a session engages the default
compile profile plus the cuDNN attention upgrade mid-session (a one-off image
never pays the warmup, repeated use amortises it). Video dense loads resolve
straight to the default profile since a clip denoise amortises the compile
within a single run, and never to max.

Video also gains the image backend's tri-state auto policies: unset step cache
now decides from the default schedule and re-checks the actual step count per
generation, and unset precision (transformer_quant) hands the decision to the
hardware ladder instead of staying off. Memory badge reason now says plainly
that everything fits when no offload is planned.

* Rename Dtype to Precision, add the video Precision control, step cache Auto option

The images Advanced panel's Dtype row is now Precision (same control, clearer
name), and the video Advanced panel gains the matching Precision select wired
to the load route's existing transformer_quant field, gated to full-pipeline
loads the way the image control gates to GGUF. Step cache selects on both
pages gain an explicit Auto option as the default (the previous Off default
silently behaved as auto and never let anyone pin off), and the Speed and
Attention tooltips now state the deferred dense compile and the SageAttention
black-frame caveat.

* Model catalog: canonical diffusion model groups with device-aware routing

One canonical name per image/video model, its published artifacts (GGUF, FP8,
bnb-4bit, official BF16) as data, and pure routing helpers: suffix-stripped
canonical keys (owner-preserving; cross-owner merges only via explicit
aliases), group/artifact lookups, a flat back-compat options shim, load-spec
resolution replacing the pages' lookup tables, search matching over old ids
and format tokens, the GGUF fit ladder extracted from the variant expander,
and pickDefaultArtifact/pickDefaultQuant deciding what a bare group click
loads (downloaded first, then the best quality that fits 70 percent of VRAM,
GGUF as the safe fallback). Checked by npm run catalog:check, following the
i18n:check pattern.

* Picker: one canonical row per diffusion model with a format second level

The Images and Video pickers now render the curated catalog as one row per
model in Recommended: clicking loads the best artifact for the device (the
routed GGUF quant, a prequant FP8/bnb-4bit that fits, or the official BF16),
and a chevron opens the per-format list, with the GGUF row nesting the usual
quant expander. Live HF listing rows that belong to a group are deduplicated,
search collapses member repos into their group (old ids and format tokens
still match), and the On Device sections group cached member repos under the
same canonical name with the per-repo rows inside. Curated groups render from
the catalog rather than the HF listing, which finally surfaces LTX-2.3 in the
video Recommended list (its hub pipeline_tag is image-to-video, so the
text-to-video listing always missed it) and exposes the HunyuanVideo 720p
repack next to 480p.

Backend: /cached-models now tags trusted video-family repos text-to-video
instead of blanket text-to-image, and the pickers admit catalog-known
non-unsloth repos On Device, so cached Lightricks/Wan/Hunyuan pipelines
finally appear in the Video picker. Chat pickers pass no catalog and are
unchanged.

* Download formats, tab icons, plain-language train tips, 3-loop autoplay

The image Download button becomes a menu: PNG saves the original bytes with
the embedded recipe, JPEG and WebP re-encode client-side from the fetched
blob (JPEG flattened onto white). The video Download button gains MP4
(original, keeps audio), WebM and GIF; the latter two transcode server-side
from the stored MP4 via PyAV (VP9 realtime profile for WebM, ~12 fps adaptive
palette for GIF) behind a new gallery export route that 501s with a readable
message when a codec is missing.

Generated clips no longer loop forever: the player replays a clip three times
per selection, then pauses with controls up; a new generation or a refresh
gets its own three plays. The Create/Train tabs reuse the sidebar's New Chat
and Train icons (TestTubeOutlineIcon moved to a shared lib module), and every
Train tab helper text is now one plain sentence.

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

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

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

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

* Keep the Create/Train tab icon and label on one line

TabsTrigger renders its children inside a plain inline span and the
Tailwind preflight gives svg display:block, so the HugeiconsIcon forced
the label onto a second line. Wrap icon plus label in their own
inline flex row inside each trigger.

* Strip -int8 and -nvfp4 prequant suffixes in the model catalog key

canonicalKeyFor already lowercases before matching, so -GGUF/-FP8 in any
case were covered; -int8 and -nvfp4 were not in the suffix table, so
such repos rendered as standalone rows in Recommended and On Device
instead of standardizing into their base-name group and routing through
pickDefaultArtifact. Added both suffixes plus case-insensitivity and
routing assertions to the catalog check.

* Standardize non-catalog picker rows to their base model name

The curated catalog already collapses its own groups, but hub listing
rows and cached repos outside the catalog (ERNIE-Image, FLUX.2-klein,
Qwen-Image-Edit-2509, FLUX.2-dev) still rendered raw ids with -GGUF /
-FP8 style suffixes in Recommended and On Device.

- model-catalog.ts: new stripArtifactSuffixesForDisplay, a
  case-preserving twin of canonicalKeyFor's stripping that keeps the
  owner prefix and original casing for display.
- pickers.tsx: recommended hub rows and the downloaded GGUF/model rows
  pass their labels through it when a catalog is present, so only the
  diffusion pickers change; chat rows keep raw ids. Click targets keep
  the full repo id, and the format badge still shows the artifact kind.
- Catalog check covers the new helper across GGUF/FP8/int8/nvfp4 in
  both cases plus no-op and suffix-only names.

* Offer official BF16/FP8 artifacts per model group and fix gallery label clipping

Model picker changes so groups are not limited to unsloth quant repos:

- model-catalog.ts: each image group that has an official vendor pipeline
  now carries its BF16 (official) artifact as the top (highest quality)
  entry - Tongyi-MAI/Z-Image-Turbo, Qwen/Qwen-Image, Qwen/Qwen-Image-2512,
  Qwen/Qwen-Image-Edit-2511, black-forest-labs/FLUX.1-dev, FLUX.1-schnell
  and FLUX.1-Kontext-dev. The LTX-2.3 video group now lists Lightricks'
  own bf16 and fp8 distilled single-file checkpoints alongside the GGUF.
  Resident sizes are set from the actual weight totals (FLUX ships a
  duplicate single-file that from_pretrained ignores, so FLUX bf16 is ~32
  GB not 54). The repos that used to be aliases are now real artifacts.
- The router already prefers the highest-quality artifact that fits the
  0.7 x GPU budget, so a datacenter GPU now defaults to official BF16
  while consumer GPUs still route to the fitting quant or GGUF. That is
  why bnb-4bit was the Z-Image-Turbo default before: it was the only
  non-GGUF artifact and it was already downloaded.
- diffusion.py: allowlist the four official image repos not previously
  trusted (qwen/qwen-image-2512, qwen/qwen-image-edit-2511,
  black-forest-labs/flux.1-schnell, flux.1-kontext-dev). All verified as
  safetensors-only diffusers model_index pipelines. The LTX-2.3
  checkpoints are already on the video trust list.
- catalog check: BF16-wins-on-datacenter, quant-wins-on-consumer, and the
  single-file load specs for the LTX-2.3 checkpoints.

Also fixes the video gallery thumbnail caption: the leading duration was
clipped by the rounded corner and selection border, so the strip now has
enough left/bottom padding to clear the curve.

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

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

* video gallery: guard export transcode against a stream-less clip

_transcode_webm and _transcode_gif indexed src.streams.video[0] before
checking the stream list, so a container with no video stream raised a bare
IndexError that the broad handlers then re-labeled as a missing libvpx or
decoder. Raise an explicit RuntimeError naming the real cause in both the
WebM and GIF paths.

* Studio: honor explicit attention/format choices, fix distilled-LTX defaults and On Device catalog routing

* Remove stray planning notes accidentally committed to the branch

* video: add transformerQuant to the load callback deps

handleLoad reads transformerQuant but omitted it from the useCallback dep array,
so after the user changes only Precision and then selects a model or clicks
Reapply, the memoized callback keeps the stale closure and loads the previous
precision. The image page's equivalent callback already lists it.

* model picker: honor the format filter when routing catalog clicks; add catalog rows to the roving list

- routedArtifactFor now scopes a group's artifacts to the active format filter
  (the same matchesFormatFilter predicate the visibility check uses) before
  pickDefaultArtifact, so a group shown only because it owns a GGUF no longer
  routes a click to a large non-GGUF download. Covers both the Recommended and
  On Device grouped paths.
- hubOptionKeys now includes the catalog-group, search-catalog-group, and grouped
  On Device row keys in exact render order, so arrow/Home/End roving reaches the
  catalog rows instead of giving them a duplicate missing id and skipping them.

* model picker: don't treat a partial base cache as downloaded

A partially-cached base repo (a cancelled download that left only some weights)
was counted as downloaded, so an On Device click routed to a fresh multi-GB
re-download instead of the complete GGUF. The picker's endpoint (/api/models/
cached-models) did not carry a partial flag at all, so a frontend-only guard
could not see it. Surface partial from that endpoint by reusing the hub inventory
scan's snapshot-partial detector, plumb it through CachedModelRepo (backend +
frontend types), and skip partial base repos when building the downloaded set.

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

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

* model picker + diffusion: drop partial/unloadable cached rows, skip defer-compile before a LoRA gen

- On Device (cached non-GGUF) rows filtered partial-download snapshots back in: sortedCachedModels
  gated on passesTaskGate + a groupForRepoId key match but, unlike downloadedSet, never checked
  c.partial, so an incomplete unsloth snapshot showed as a loadable On Device row (click errors or
  silently re-fetches multi-GB). It also admitted repos that only match the catalog by group KEY
  (a base / uncurated-quant sibling like Qwen/Qwen-Image-2512) which have no loadable artifact and
  dead-end at the trust gate. Add !c.partial and gate on artifactForRepoId (what loadSpecFor
  resolves) instead of groupForRepoId, so a cached row shows only when the backend can load it.

- Deferred speed-auto engaged the compile profile on the 3rd generation BEFORE _apply_loras. A
  compiled transformer rejects LoRA (supports_lora is False) and _apply_loras raises before its
  unchanged-selection no-op, so once compile engaged every LoRA generation on that load failed
  permanently. Skip the deferral when a LoRA is requested (compile and LoRA are mutually exclusive)
  and let it engage on a later LoRA-free generation.

* Scope the cached-model partial probe to the listed snapshot dir

list_cached_models builds each row from the largest/complete copy across HF cache
roots, but _cached_repo_partial probed is_snapshot_partial with no repo_cache_dir,
so the scan spanned every root: a stale .incomplete copy in one root would flag a
complete copy in another as partial and hide the usable model from the picker (the
click then routes to a re-download). Forward the winning snapshot's repo_path so all
three partial signals are scoped to that copy, matching the sibling inventory paths
(models/dataset cache_inventory, local_inventory).

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

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

* Do not auto-route to gated repos, prefer complete cached copies, defer compile past attached LoRA, scope group expand keys

Four fixes:
- pickDefaultArtifact's not-downloaded ladder returned the gated BF16 FLUX.1-dev / Kontext-dev
  before the open GGUF on a large GPU, so a bare group click routed to a repo the user may lack
  license/token access to. Add a gated flag and skip gated artifacts in the not-downloaded ladder
  (an already-downloaded gated artifact is still returned).
- list_cached_models picked the largest duplicate cache copy and computed partial only on it, so a
  larger partial copy shadowed a smaller complete one; since partial rows are dropped from the
  picker the usable model vanished. Prefer completeness, then size.
- the deferred-speed compile engaged on a no-LoRA generation while an adapter from a prior
  generation was still attached, baking it into the compiled graph (the later unload is swallowed
  on a compiled pipe); also defer while adapters remain attached.
- routeGroupClick's GGUF fallback toggled the context-free canonicalId while the chevron toggles
  the context-scoped expandKey, leaving the format list un-collapsible in one context, dead in the
  other, and risking cross-context expansion; thread expandKey through.

* Guard video pipeline repos from deletion, drop the always-failing LTX FP8 artifact, prefer 720p Hunyuan

Three round-6 fixes:
- cached non-GGUF video repos now surface in the Video On-Device picker with the normal delete
  action, but /delete-cached only guarded chat + the Images engine, so a loaded/loading Wan / LTX /
  Hunyuan pipeline could have its HF snapshot removed from under it. Add a VideoBackend
  loading_repo_ids accessor and a video loaded/loading guard mirroring the Images one.
- the catalog advertised Lightricks/LTX-2.3-fp8 as loadable, but the LTX-2.3 loader refuses the
  official scaled-FP8 single file (.weight_scale/.input_scale) and points to GGUF/BF16, so a pick
  routed to a ~76 GB download that always fails on load. Remove the FP8 artifact.
- pickDefaultArtifact only sorts by format, so the HunyuanVideo group's 480p (listed first) beat
  the 720p even on GPUs where 720p fits the budget. List 720p first so the fit loop prefers it and
  falls back to 480p only on smaller cards.

* diffusion: add compute int8/fp8_dynamic text-encoder quant, wire into video

Add two torchao compute text-encoder quant modes to the diffusion precision
engine, alongside the existing layerwise fp8 and weight-only nvfp4:

- int8: per-token activation + per-channel weight (torch._int_mm), with per-layer
  keep-bf16 selection. int8 degrades on large encoders unless the most
  quant-sensitive decoder blocks stay bf16, so it engages only for families with
  a measured keep-bf16 schedule (qwen-image / qwen-image-edit keep first+last 6,
  flux.2-dev keeps first 3); a family without one falls back to fp8.
- fp8_dynamic: per-row fp8 compute (torch._scaled_mm), keeping the matmul in fp8
  on the tensor cores instead of upcasting each forward like the layerwise fp8.

The selective int8 caster reuses the committed transformer-quant factory
(_make_quant_config / make_filter_fn / exclude_tokens_for_scheme) plus a small
structural first/last-N block skip, so it depends only on committed APIs.

Wire text-encoder quant into the video backend, which previously loaded the
companion encoder (Gemma3 / UMT5 / Qwen2.5-VL) dense bf16 while quantising only
the DiT. text_encoder_quant is plumbed through the load request, validation, the
load chain, the resolved record, and status, mirroring the image backend; it
applies for every load kind (the encoder is dense regardless of how the DiT was
sourced). Widen the image and video load request Literals and add the video
status field.

Tests: int8 family-schedule routing and fp8 fallback, fp8_dynamic routing,
hardware gates (int8 sm_80+, fp8_dynamic sm_89+), the structural block selection,
the real int8 filter closure (keeps the first blocks plus the vision tower /
lm_head / T5 wo dense), and the video route threading and 422 validation.

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

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

* text-encoder quant: skip the torchao modes under offload (both backends)

quantize_text_encoders applied int8-with-schedule / fp8_dynamic / nvfp4 (all torchao) to the
text encoder regardless of the offload policy. An offload placement then moves the quantized encoder
with Module.to(), which torchao tensor subclasses reject (aten._has_compatible_shallow_copy_type is
unimplemented) -- a hard crash, the same one the DiT path already skips torchao quant under offload to
avoid. Add offload_active to quantize_text_encoders and skip the torchao modes when set; layerwise fp8
is not torchao and still streams under offload. Both the video and image loaders pass
offload_active = (offload policy != none).

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

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

* diffusion: skip non-bf16 linears for scaled_mm quant schemes

The fp8 / mxfp8 / nvfp4 schemes run on torch._scaled_mm and the fp4 / mx GEMMs,
which assert a bfloat16 input weight. On a mixed-precision DiT that keeps some
linears in fp32 for numerical stability (the Wan and Hunyuan video transformers
do this), quantize_ hits the first fp32 linear, raises, and the best-effort
wrapper swallows it to None, so the whole transformer stays dense with no error
and no speedup or memory saving.

Add a require_bf16 gate to make_filter_fn and pass it for the scaled_mm schemes
in quantize_transformer (and the fp8_dynamic text-encoder caster). The gate
skips non-bf16 linears so the scheme engages on the bf16 ones. int8 uses
torch._int_mm, which quantizes fp32/fp16 weights fine, so it leaves the gate off
and keeps its current coverage.

Verified on Wan2.2-TI2V-5B: fp8 and mxfp8 now quantize 303 linears via the
committed quantize_transformer path where they previously engaged 0.

* prequant builder: mirror the scaled-mm bf16 gate offline

The runtime DiT quantizer skips non-bf16 Linears for the scaled_mm schemes (fp8,
nvfp4, mxfp8) so the scheme engages on a mixed-precision transformer instead of
aborting on the first fp32 Linear. The offline prequant builder reused make_filter_fn
without that gate, so building an fp8/nvfp4/mxfp8 checkpoint for a mixed-precision DiT
(Wan, Hunyuan keep _keep_in_fp32_modules in fp32 even under torch_dtype=bf16) would hit
the same fp32 Linear and abort, breaking the builder's stated offline == runtime,
LPIPS-0 invariant. Thread require_bf16 = scheme in _SCALED_MM_SCHEMES through the builder,
record it in the checkpoint metadata, and verify it on load (mirrors the existing
exclude_name_tokens guard) so a future _SCALED_MM_SCHEMES change cannot silently load a
checkpoint built under the old filter.

* Keep nvfp4 fp32 linears quantised (bf16 gate is fp8/mxfp8 only)

Verified on torchao 0.17 / B200: fp8 per-row asserts 'PerRow quantization only
works for bfloat16 precision input weight' and mxfp8 asserts 'Only supporting bf16
out dtype', but NVFP4's high-precision conversion quantises an fp32 weight fine
(forward included). So the bf16 skip-gate must be fp8/mxfp8 only, not all scaled_mm
schemes -- otherwise nvfp4 leaves large fp32 projections dense, losing the intended
memory/speed gain. Rename _SCALED_MM_SCHEMES -> _REQUIRE_BF16_SCHEMES = (fp8, mxfp8)
and thread it through the runtime filter, the offline builder, and the loader
require_bf16 verification (offline == runtime preserved).

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

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

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2026-07-06 17:48:39 -07:00
Daniel Han
46e2cf5dee
studio: label RAM and VRAM readouts as GiB not GB (#6895)
The live resource monitor and GPU readouts derive memory from binary byte
counts (bytes / 1024**3 for torch and psutil, MiB / 1024 for the nvidia-smi
path), which is GiB, but the UI labeled the values "GB". On a B200 this
showed "178.35 GB" for a card whose nvidia-smi total is 183359 MiB
(179 GiB), so it looked like memory was missing.

Relabel the measured RAM and VRAM readouts to GiB across the floating
monitor, the resources tab, the studio live GPU panel, the hub header, the
about tab and the onboarding summary. The numeric values are unchanged, so
the training GPU selection and memory-fit logic that read the same fields
are unaffected. Disk stays labeled GB because the backend reports it in
decimal GB (bytes / 1e9), and model file sizes and download progress keep
their decimal GB labels to match Hugging Face.
2026-07-06 08:27:26 -07:00
Daniel Han
26aeaf52ff Resolve picked GGUFs from every cache root; hide the dead Reapply button on refresh
Two follow-ups to the video-tab review fixes:
- the cached-hub arch fallback only probed the active HF cache, but the cached-gguf picker
  scans the active, legacy, and default cache roots; a GGUF cached in a non-active root was
  offered yet 400d on load. Probe all three roots.
- on a mount/refresh with a model already resident, status.loaded made the Reapply button render
  but lastLoad (set only by our own loads) was null, so clicking it silently did nothing. Track a
  session reapply descriptor and hide the button when it is absent rather than offer a dead control.
2026-07-06 14:41:11 +00:00
Daniel Han
bb937296e7 Guard in-flight video loads from deletion, resolve cached hub GGUFs by arch, seed gen controls
Three video-tab fixes:
- delete-cached refused a loaded video repo but not one a background load is still downloading
  (status().loaded is False in that window); add VideoBackend.loading_repo_ids() mirroring the
  image backend and check it in the route so deleting mid-download can no longer yank blobs.
- the cached-gguf picker tags a cached HUB GGUF by its general.architecture, but the loader's
  arch fallback only read a LOCAL file, so an opaquely-named cached hub LTX GGUF the picker
  offered 400d on load; read the arch from the cached blob (network-free) too.
- on a mount with a model already loaded (refresh / load from another client) steps and guidance
  stuck at the pre-load default, so a base checkpoint silently generated a degraded clip; seed
  them from the backend-authoritative status.defaults once per newly-loaded model.
2026-07-06 14:14:51 +00:00
Daniel Han
9ab68825ee Give Krea-2-Raw its undistilled 52-step recipe instead of the distilled default
Krea-2-Raw is in _TRUSTED_NON_GGUF_REPOS, so it is inference-loadable, but the generic
"krea" generation-defaults key matched it too and applied Turbo's distilled 8-step / no-CFG
recipe, producing degraded output on the undistilled base. Add a more specific krea-2-raw key
(52 steps, guidance 3.5 per the model card) ahead of the generic one, in both the backend
table and the frontend MODEL_DEFAULTS so the Studio UI and the OpenAI images route agree.
2026-07-06 14:00:39 +00:00
Daniel Han
a1114bfdc3 Align LR-schedule copy with the diffusion two-card chart layout
DiffusionCharts deliberately renders only Training Loss + Gradient Norm (the LR
curve is the deterministic schedule the user picked), but the settings copy and two
comments still promised a live LR chart. Reword them so the UI no longer references a
chart that was intentionally dropped.
2026-07-06 11:21:06 +00:00
Daniel Han
2d974219bf Deploy Krea adapters on Turbo and use its distilled recipe over the API
- DiffusionFamily gains deploy_base_repo (krea/Krea-2-Turbo): deploying a LoRA
  trained on Raw now previews it on Turbo, not the non-distilled Raw checkpoint.
  Scoped to a same-precision override so it never turns an nf4 train base into a
  larger bf16 deploy load; exposed through family_train_infos -> the Train UI's
  onDeployClick / historical-run deploy resolve the deploy base.
- _GENERATION_DEFAULTS gains a Krea entry (8 steps, 0 CFG) so the OpenAI
  /v1/images/generations route matches the Create UI's documented distilled recipe
  instead of falling through to the generic (9, 0.0).
2026-07-06 11:16:25 +00:00
Daniel Han
7c8f4919d6 diffusion: fix step-cache Off, kernel-install dep clobber, retry-under-lock, effective steps
- Step cache 'Off' is preserved: the frontend defaulted to 'off' and mapped it
  to an omitted transformer_cache, which the backend now reads as 'auto', so
  leaving the control at Off silently enabled FBCache on 20+ step families.
  Default the control to Auto, add an explicit Auto option, and send
  auto -> omitted so Off maps to an explicit cache-off.
- Kernel auto-install adds --no-deps: 'pip install --only-binary :all: xformers'
  resolves xformers' pinned torch and replaces the running torch/triton. --no-deps
  installs only the best-effort kernel wheel; an ABI mismatch just fails to import
  and falls back to native, never clobbering core deps.
- Do not retry a failed kernel install under the load lock: the pre-install runs
  outside the locks, then the in-lock resolve re-attempts pip (up to 600s) while
  holding _generate_lock/_lock and blocking unload/cancel/new loads. Record the
  attempt in a process-level set so the in-lock call short-circuits to native.
- Cache auto-toggle keys on effective denoise steps: an image-conditioned run with
  strength < 1 (upscale default 0.35) denoises a fraction of the requested steps,
  so a 28-step request runs ~10 steps. Compute the effective count the way diffusers
  get_timesteps does and gate FBCache on it, only when strength is actually applied.
2026-07-06 08:45:17 +00:00
Daniel Han
af2835fe79 Merge branch 'video-wan' into video-hunyuan-gate
# Conflicts:
#	studio/backend/tests/test_video_backend.py
2026-07-05 00:34:16 +00:00
Daniel Han
9a4f9a6ec5 Merge branch 'video-tab' into video-wan
# Conflicts:
#	studio/backend/core/inference/video.py
2026-07-05 00:33:06 +00:00
Daniel Han
c8d5081e0e Video tab review fixes: on-device GGUF discovery, family defaults, cancel and chat-only polish
Tag the ltxv and wan GGUF archs text-to-video so cached video checkpoints
actually surface in the Video picker (they were classed unsupported and
hidden everywhere). Adopt the loaded family's default clip length instead
of silently keeping the 25-frame pre-load fallback, and derive steps and
guidance from the picked GGUF filename so a distilled variant gets its
few-step schedule. Suppress the error toast for the user's own Cancel and
disable the Video nav item on chat-only hosts with a hint, matching Train.
2026-07-05 00:28:21 +00:00
Daniel Han
479996f85b Skip the gallery src state update after unmount in ensureSrc
The object URL still lands in the module cache either way; the setState call
now checks isMounted like the other async callbacks in the file.
2026-07-05 00:13:54 +00:00
Daniel Han
f77fa80007 Add HunyuanVideo-1.5 family and the video quality gate
HunyuanVideo-1.5 (the 8.3B DiT with Qwen2.5-VL + ByT5 text encoders) loads
through the hunyuanvideo-community Diffusers repacks; tencent's own repo is
the original non-diffusers layout and cannot load as a pipeline, so only the
community 480p/720p t2v repos are trusted. Two pipeline quirks, both verified
against pipeline_hunyuan_video1_5.py in diffusers 0.39, shape the wiring:

- __call__ takes no guidance kwarg: CFG lives on the pipeline's guider
  component (ClassifierFreeGuidance, shipped at scale 6.0). The family gains
  guidance_via_guider and generate() writes the requested scale onto
  pipe.guider instead of passing cfg_kwarg, which the pipeline would reject.
- __call__ has no callback_on_step_end: progress and cancellation fall back
  to a scheduler.step wrapper (one call per denoise step), installed for the
  duration of the call and always restored. Cancellation unwinds the loop by
  raising through the wrapper and surfaces the same cancelled sentinel the
  callback path uses.

The VAE compresses 16x spatial / 4x temporal, so sizes snap to /16 and frame
counts to 4k+1. The transformer declares _repeated_blocks and CacheMixin, so
the regional compile profile and the step cache both apply unchanged.

scripts/video_quality.py is the video accuracy gate, the analogue of
scripts/diffusion_quality.py with the same pure-numpy PSNR/SSIM math so image
and video budgets compare: fixed prompt/seed/shape, one short clip per
candidate against a reference clip, per-frame SSIM/PSNR over sampled frames,
a temporal-consistency deviation (motion-energy series error, catching
flicker SSIM alone misses), black-frame/NaN collapse checks, an audio RMS
silence trip-wire for LTX-2, and wall time + peak VRAM per candidate.
Verdicts map the standing budget: ssim >= 0.75 passes, >= 0.50 warns,
anything lower or any collapse fails. --selftest runs the metric path on
synthetic clips with no GPU or model.
2026-07-04 14:22:10 +00:00
Daniel Han
9f83d387c2 Add Wan2.2 text-to-video families to the video backend
Register two new video families and wire them through the backend, routes,
and frontend picker: wan2.2-ti2v-5b (single DiT) and wan2.2-t2v-a14b (the
dual-expert MoE). Both share diffusers' WanPipeline + WanTransformer3DModel
+ AutoencoderKLWan, which the VideoFamily dataclass already reserved fields
for (transformer2_class, is_moe, cfg2_kwarg).

Verified against the installed diffusers 0.39.0 before writing code:
- WanPipeline, WanTransformer3DModel, and AutoencoderKLWan are all exported
  from top-level diffusers 0.39.0.
- WanPipeline.__call__ (pipeline_wan.py:383) defaults to num_frames=81,
  num_inference_steps=50, guidance_scale=5.0. guidance_scale_2 DOES exist
  in 0.39 (line 392) and its check_inputs raises if it is passed when the
  pipeline's boundary_ratio is None (line 322), so the second guidance is
  threaded ONLY for the MoE family and only when inspect.signature accepts
  it (the same gate frame_rate already uses).
- The Wan VAE temporal factor is 4 (autoencoder_kl_wan.py scale_factor_temporal),
  and the pipeline snaps num_frames to 4k+1 (line 493), so frame_step is 4,
  unlike LTX-2's 8k+1. Sizes patchify at spatial 8 * patch 2 = 16, so
  resolution_multiple is 16.
- boundary_ratio and transformer_2 come from model_index.json: TI2V-5B ships
  boundary_ratio=null and transformer_2=[null,null] (single DiT), while A14B
  ships boundary_ratio=0.875 and transformer_2=WanTransformer3DModel (dual
  DiT). boundary_ratio lives in the pipeline config, so it needs no per-call
  plumbing.
- WanTransformer3DModel declares _repeated_blocks=["WanTransformerBlock"] and
  inherits CacheMixin (transformer_wan.py:508/551), so regional compile and
  First-Block-Cache both work.

bf16-resident component sizes, measured from each diffusers repo's on-disk
safetensors (all stored bf16), feed the auto memory table:
  TI2V-5B: transformer 20.0, UMT5 text encoder 11.4, VAE 2.8 GB.
  A14B:    two experts 57.2 each (114.3 total), text encoder 11.4, VAE 0.5 GB.

Backend changes make the optimisation layers dual-DiT aware: a small
_SecondDiTView proxy presents transformer_2 as pipe.transformer so the
existing single-DiT helpers (apply_speed_optims, apply_attention_backend,
apply_step_cache, quantize_transformer) cover BOTH experts on an is_moe load
without forking any helper; single-DiT loads are unchanged (views is just
(pipe,)). The two Wan base repos are added to the trusted non-GGUF allowlist.
A transformer_quant option is added to the load path, mirroring the image
backend's dense torchao fast path: on a pipeline-kind load the dense DiT(s)
are quantised in place onto the low-precision tensor cores and the engaged
scheme is surfaced in status. generate() threads guidance_2 through the
family's cfg2_kwarg when the loaded pipeline accepts it.

Routes and Pydantic models gain the optional transformer_quant (load /
status) and guidance_2 (generate) fields. The frontend picker gains the two
Wan models with 50-step / CFG 5.0 defaults; fps is supplied per family by
the backend.

Tests extend the fake runtime with WanPipeline and per-DiT transformer fakes
(single-DiT and dual-DiT), and cover family detection for both repos, 4k+1
frame snapping, default application, dual-DiT speed/cache/attention/quant
coverage on both experts, cfg2 threading gated on the pipeline signature,
trusted-repo validation, and the new route fields. Both the standard and the
diffusers/torchao-blocked CI-sim runs are green.
2026-07-04 14:06:47 +00:00
Daniel Han
a487f3c1c0 Add Video tab to Studio frontend
Add a Video generation page that mirrors the Images feature's create
workflow. It loads a text-to-video model, generates a clip, and plays it
back inline with the gallery of past clips.

- src/features/video/api.ts: typed client for the /api/inference/video
  routes (load, load-progress, generate, generate-progress, cancel,
  status, unload, gallery CRUD, and an auth-protected MP4 blob fetch).
- src/features/video/video-page.tsx: the page. Curated model picker
  (LTX 2.3 distilled GGUF, LTX 2 base pipeline), prompt and negative
  prompt, resolution preset select, duration select over the family's
  temporal lattice, fixed fps display, steps and guidance sliders seeded
  from per-model defaults, seed box. Generate polls per-step progress
  with a phase label and ETA and a Cancel button, then plays the result
  in a video player with a download button and an audio badge. Gallery
  strip below with per-card delete and clear all. Right-docked Advanced
  panel for memory, speed, attention, and step-cache with Auto badges
  fed from the resolved status.
- Register the page: router child, /video route, sidebar nav item with a
  video icon after Images, and the __root keep-alive mount so an
  in-flight generation survives leaving the tab.
- Add the text-to-video task to the model picker so video models never
  appear in the chat picker.
- Add the video nav label to the en and zh-CN locales.
2026-07-04 13:30:42 +00:00
Daniel Han
cbfc43215d Add Ideogram 4 family, structured HunyuanImage exclusion, curated Krea 2 LoRAs
Ideogram 4 (diffusers 0.39 Ideogram4Pipeline) as a new image family. The vendor
publishes no bf16 checkpoint, so ideogram-ai/ideogram-4-fp8 (raw float8 DiTs,
upcast by from_pretrained) is the family base and ideogram-4-nf4-diffusers is
the bnb-4bit pipeline artifact (ideogram-4-nf4 is byte-identical and detects to
the same family). All three repos join the trusted non-GGUF allowlist and the
frontend safetensors catalog.

Family specifics handled:
- Dual-branch CFG runs through a SEPARATE unconditional_transformer, so the
  auto-policy size table entry counts two ~9.3B DiTs (37.2 GB bf16), and the
  pipeline-kind memory plan now takes max(cached bytes, family table) for the
  family base repo: the fp8 repo's cached bytes undershoot the bf16-resident
  footprint by ~2x, which would let auto planning pick a resident placement
  that OOMs.
- The pipeline accepts EITHER guidance_scale OR a per-step guidance_schedule
  (its default: the recommended 45x7.0 + 3x3.0 taper, valid only at 48 steps)
  and raises when both are set. At the advertised defaults (48 steps, guidance
  7) generate() drops the constant so the recommended taper engages; any other
  request nulls the schedule so the constant broadcasts legally.
- Generation defaults per the model card: 48 steps, guidance 7 (both tables).

tencent/HunyuanImage-3.0 is deliberately excluded: it has no diffusers pipeline
(an 80B autoregressive MoE behind trust_remote_code). A structured exclusion
map now surfaces that reason verbatim from validate_load_request instead of
the generic unknown-family error.

The curated diffusion LoRA catalog gains the nine official krea/Krea-2-LoRA-*
style adapters (family-tagged krea-2, explicit weight filenames), so they show
up in the picker instead of requiring a typed repo id.

Tests: new test_diffusion_more_families.py (detection, trust, defaults, size
table, exclusion reason, curated catalog + family filter), two generate()
tests for the guidance_scale/guidance_schedule pairing, and the local-scan
LoRA test updated for a non-empty curated list. Backend suite + CI-sim
(block_diffusers/block_torchao) green; frontend builds.
2026-07-04 12:46:24 +00:00
Daniel Han
04396ec507 Merge diffusion-auto-install: request type accepts explicit Dtype off 2026-07-04 09:47:04 +00:00
Daniel Han
c38ae1cef5 Widen the load request type for the explicit Dtype off value
The Dtype select now sends none through instead of omitting it, so the
request type must accept it (tsc caught the mismatch at the badges tip).
2026-07-04 09:46:38 +00:00
Daniel Han
45fe22d8eb Merge diffusion-auto-install: Dtype defaults to auto with disk gate 2026-07-04 09:44:58 +00:00
Daniel Han
9a34934030 Dtype defaults to auto: unset resolves by hardware, explicit off pins the GGUF
An unset transformer_quant used to mean off (run the GGUF as-is), so the
hardware ladder only engaged when auto was explicitly chosen and the panel
showed Off as the default. Unset (or auto) now hands the decision to the
ladder: a dense-capable GPU gets at least int8, data-center silicon fp8,
falling back to the GGUF when the device, VRAM, family deny table or disk
cannot take it. An explicit none/off pins GGUF-as-is and is now
expressible in the API (previously only omission meant off, so pinned-off
and unset were indistinguishable); an explicit scheme pins that scheme.

The dense candidate also gains a free-disk gate: with auto as the default
the bf16 base download (up to ~40 GB) must never wedge a nearly-full
model-cache disk, so the candidate is dropped (GGUF build kept) when free
space cannot hold it plus a 10 GiB margin. Unprobeable disk passes.

Frontend: the Dtype select defaults to Auto (fastest for GPU), keeps Off
as an explicit choice, and sends none through instead of omitting it.

Suite: 622 diffusion tests green (default-load test rewritten to the new
contract, explicit-off short-circuit covered), CI-sim green.
2026-07-04 09:43:43 +00:00
Daniel Han
02256d1820 Advertise per-family footprints and surface Auto badges for resolved controls
GET /api/inference/images/info returns each family's bf16 component sizes and
the estimated resident GB under bf16/int8/fp8/mxfp8/nvfp4, computed purely from
the auto-policy tables (no GPU probing, torch-free), so the panel can show the
Dtype tradeoff before anything is loaded.

DiffusionStatusResponse gains an additive resolved field: per-control
{value, source, reason} provenance the loader already records. The Advanced
panel renders a muted Auto: X pill next to Speed / Dtype / Attention / Memory /
Step cache / CPU offload when the backend decided that control (source auto),
with the reason as the tooltip; an explicit user choice renders no badge.
2026-07-04 07:47:01 +00:00
Daniel Han
e39ae8fb58 Merge diffusion-krea2: Dtype rename and empty-state copy 2026-07-04 06:17:58 +00:00
Daniel Han
2459bdbbf1 Merge diffusion-train-tab-2: Dtype rename and empty-state copy 2026-07-04 06:17:57 +00:00
Daniel Han
b219118ff0 Merge diffusion-train-precision: Dtype rename and empty-state copy 2026-07-04 06:17:56 +00:00
Daniel Han
d146209f88 Rename the GGUF compute control to Dtype and simplify the empty-state copy
The always-visible description under the select is gone (the hint tooltip keeps the
full detail) and the no-model gallery placeholder now reads 'Select a diffusion model
to load'.
2026-07-04 06:17:45 +00:00
Daniel Han
67f8f6cfae Merge diffusion-krea2: grad norm reconciliation + review fixes 2026-07-04 04:38:24 +00:00
Daniel Han
a82fc89d03 Merge diffusion-train-tab-2: grad norm reconciliation + review fixes 2026-07-04 04:38:05 +00:00
Daniel Han
a346a0eb20 Merge diffusion-train-precision: grad norm chart + review fixes
# Conflicts:
#	studio/backend/core/training/diffusion_dit_trainer.py
#	studio/backend/core/training/diffusion_lora_trainer.py
#	studio/backend/core/training/diffusion_training_service.py
#	studio/backend/models/training.py
#	studio/frontend/src/features/images/api.ts
#	studio/frontend/src/features/images/train/diffusion-charts.tsx
#	studio/frontend/src/features/images/train/diffusion-train-panel.tsx
2026-07-04 04:37:58 +00:00
Daniel Han
8ad8a58742 Add grad norm chart, clearer completion state, Windows caption keys, GGUF compute copy
- Trainers emit the pre-clip gradient norm; the service keeps a bounded
  grad_norm history and the Train tab renders a Grad Norm chart next to
  Loss and LR
- Completed runs show 'Training complete' with a celebratory marker in
  the success color instead of a plain status word
- metadata.jsonl caption keys now match on Windows (as_posix relative
  paths) in both the trainer discovery and the dataset image records
- RMSNorm eager patch skips installation on torch builds without
  F.rms_norm instead of failing at forward time
- GGUF compute description no longer says the GGUF is dequantised: the
  INT8/FP8/FP4 modes load the base model's bf16 transformer and quantise
  that directly; label no longer wraps in the Advanced panel
2026-07-04 04:31:04 +00:00
Daniel Han
95f783ae1e Merge diffusion-krea2: Raw training default + stacked review fixes
# Conflicts:
#	studio/backend/core/training/diffusion_train_common.py
#	studio/backend/tests/test_diffusion_dit_trainer.py
#	studio/backend/tests/test_diffusion_training.py
2026-07-04 03:28:32 +00:00
Daniel Han
2eded64b25 Merge diffusion-train-tab-2: run history robustness, epoch sentinel, torchao probe, image-generation review fixes 2026-07-04 03:25:30 +00:00
Daniel Han
51de9da488 Fix review findings: run history robustness, epoch-mode sentinel, seed 0, history refresh race
- list_diffusion_runs skips wrong-shape records and the runs route tolerates
  per-record ValidationError so one bad file never breaks the panel
- max_steps: 0 epoch-mode sentinel no longer trips train_steps validation
  before epochs are resolved
- numberField keeps an explicit 0 (Seed, LR warmup) instead of falling back
- previous-runs list refetches once more shortly after a terminal status so
  the just-finished run appears even if the record write races the fetch
2026-07-04 03:23:20 +00:00
Daniel Han
94b076226b Merge branch 'diffusion-krea2' into diffusion-train-perf2 2026-07-04 02:21:31 +00:00
Daniel Han
ea9f7ae9f9 Merge branch 'diffusion-train-tab-2' into diffusion-krea2 2026-07-04 02:21:20 +00:00
Daniel Han
f1dbb74308 Merge branch 'diffusion-train-precision' into diffusion-train-tab-2 2026-07-04 02:21:11 +00:00
Daniel Han
71ab68fd55 Merge remote-tracking branch 'origin/main' into image-generation
# Conflicts:
#	studio/frontend/src/app/router.tsx
2026-07-04 02:18:20 +00:00
Daniel Han
14b2a68025 Merge branch 'diffusion-krea2' into diffusion-train-perf2
# Conflicts:
#	studio/backend/core/training/diffusion_dit_trainer.py
#	studio/backend/core/training/diffusion_train_common.py
#	studio/frontend/src/features/images/train/diffusion-train-panel.tsx
2026-07-04 01:32:02 +00:00
Daniel Han
1d60e979e4 Merge branch 'diffusion-train-tab-2' into diffusion-krea2 2026-07-04 01:27:55 +00:00
Daniel Han
23c6457e62 Address review: Train panel precision and notification edge cases
- Reset mixed precision to bf16 when the family changes to a DiT: an fp16/no
  value left over from SDXL rode along in the DiT start payload and the backend
  rejected it (dense base precisions require bf16 compute).
- Gate the dense base precisions behind the selected base: a bnb-4bit repo
  disables bf16/int8/fp8 with a hint, and a dense selection auto-flips to auto so
  the run does not fail at the validator.
- Re-arm the run-completion notification in onStart, so a second run notifies
  even when its running phase is never observed by the poll.
2026-07-04 01:27:40 +00:00
Daniel Han
2e3cfaa075 Merge branch 'diffusion-krea2' into diffusion-train-perf2 2026-07-04 01:05:48 +00:00