Commit graph

1,182 commits

Author SHA1 Message Date
Daniel Han
7dbdd28161 perf(video): accuracy-first round 2 for HunyuanVideo-1.5: compile parity, cache quality presets, dual-GPU CFG
Cuts the shipped default's LPIPS vs the bit-exact reference from 0.224 to 0.139
while going faster (24.9 s to 21.2 s at 720p/33f/30 steps, 22.7x vs reference),
and makes the remaining speed/accuracy trade a user knob.

- inductor precision parity: set emulate_precision_casts=True for the regional
  compile (fused pointwise kernels kept fp32 intermediates where eager rounds to
  bf16 between ops); full-clip LPIPS vs bit-exact 0.221 to 0.052 at zero speed
  cost. Snapshot/restored with the other process-wide backend flags.
- cache x compile composition fix: diffusers cache hooks are
  torch.compiler.disable'd, so every COMPUTED step ran eager (1.69 vs 1.09
  s/step) under MagCache/FBCache in both enable orders. Re-point each hook's
  fn_ref.original_forward at a torch.compile'd wrapper of the same bound method
  (armed only where the speed layer compiled the block; restored before every
  disable_cache so the uncached path stays pristine). Balanced MagCache at 50
  steps: 1.48x to 2.17x, identical skip counts, bit-identical uncached rerun
  after enable/disable cycles.
- transformer_cache_quality knob (quality|balanced|fast; API + UI + bench)
  mapping to (threshold, max_skip_steps, retention_ratio). Auto resolves to the
  near-lossless quality preset (0.06, 2, 0.3; 1.63-1.64x at pairwise LPIPS
  0.05-0.09) for the HunyuanVideo-1.5 families and to balanced (the pre-knob
  values, byte-identical behaviour) everywhere else.
- TE auto-quant resolves dense for HunyuanVideo-1.5: TE fp8_dynamic alone moves
  the clip to LPIPS 0.236 vs bit-exact for zero speed win (the quantised encoder
  perturbs the conditioning and the trajectory amplifies it chaotically); VAE
  fp8 stays in auto (0.053, at the compile floor). Explicit schemes honored.
- dual-GPU CFG branch parallelism (new diffusion_cfg_parallel.py): transformer
  proxy + DiT replica on the most-free second CUDA device + worker thread,
  branch-routed off the pipeline's own cache_context names. Auto engages only
  where measured bit-identical (eager tier: max abs diff 0.0, 1.66x); the
  compiled stack is explicit cfg_parallel=on (1.52x over the sequential
  default; per-device compiled artifacts differ by 1 bf16 ulp/step, documented
  in the resolved record). Fail-soft gates: family allowlist, guider CFG,
  pipeline kind, dense DiT, no offload, free-VRAM check; single-GPU loads are
  untouched and the memory plan stays single-device.
- video API: the transformer_cache literal now accepts auto/magcache (an
  explicit magcache request was rejected at the pydantic layer); the mxfp8
  family deny records the round-2 measurement (block-32 MX scaling fixes the
  zero-row collapse, no black frames, but is latency-neutral at LPIPS 0.37:
  fails both ship bars).

Measured on B200 via the production lever path (video_speedmem_bench.py, which
gained a --cache-quality lever and companion-quant isolation configs). Tests:
441 passing across the video inference suite (32 new for cfg-parallel, 20 for
presets/arming, 3 for the inductor flag, 2 for TE auto-dense); ruff clean.
2026-07-10 14:30:00 +00:00
Daniel Han
8218c9bf42 studio: use largest single GPU for the diffusion catalog fit budget
The catalog fit budget used gpu.memoryTotalGb, which sums VRAM across
every GPU. That sum is right for the chat/llama.cpp path (tensor-split
shards across cards) but wrong for the diffusion/video catalog: those
backends place the whole pipeline on a single device (pipe.to or cpu
offload, never device_map), so on a multi-GPU host the fit toggle and
bare-group-click routing credited VRAM no single card has. On a 4x24 GB
plus 128 GB RAM host the 114 GB Wan A14B bf16 group passed the toggle
(0.7*96 + 0.7*128 budget) and a click would OOM, the exact load the
toggle exists to prevent. Expose maxDeviceMemoryGb (largest single
device) from use-gpu-info and use it for deviceBudget; the chat path
keeps the sum. Single-GPU hosts are unchanged.
2026-07-10 06:13:48 +00:00
Daniel Han
7a42f6b876 Merge remote-tracking branch 'origin/image-generation' into r7021 2026-07-09 11:09:08 +00:00
Daniel Han
280ffa8d89 studio: drop duplicate listStoredChatThreads import in app-sidebar
The main merge left listStoredChatThreads imported twice -- once as a standalone import from
the deep utils path and once via the @/features/chat barrel (which re-exports it) -- tripping
TS2300 'Duplicate identifier' and failing the Tauri frontend build. Keep the barrel import,
grouped with the other chat imports.
2026-07-09 10:55:49 +00:00
Daniel Han
46890b05b3 studio: drop duplicate listStoredChatThreads import in app-sidebar
The main merge left listStoredChatThreads imported twice -- once as a standalone import from
the deep utils path and once via the @/features/chat barrel (which re-exports it) -- tripping
TS2300 'Duplicate identifier' and failing the Tauri frontend build. Keep the barrel import,
grouped with the other chat imports.
2026-07-09 09:27:40 +00:00
Daniel Han
5e3ab0a8c4 images/video: apply the fit-on-device toggle to catalog group rows
The Recommended list's fit-on-device toggle filtered the live Hub rows and the
flat curated rows, but the canonical catalog GROUP rows (Images / Video pages)
were gated only by the format filter. A bare click on a filtered list could then
still start an OOM load the toggle was meant to hide (LTX-2 base at 90 GB, the
Wan2.2-A14B MoE at 114 GB, both bf16-only with no GGUF fallback).

Add catalogGroupFitsDevice: a group stays visible when at least one artifact can
actually run here (already downloaded, a GGUF whose quant ladder self-fits, or a
sized artifact within 0.7*GPU + 0.7*RAM), mirroring the Recommended fit predicate
across a group's formats. Gate the search (matchedCatalogGroups) and the
Recommended-section catalog rows (render + roving keys) on it. Node-native
catalog:check assertions cover the over-budget, GGUF-fallback, downloaded, unknown
-budget, and datacenter-budget cases.
2026-07-09 09:04:43 +00:00
Daniel Han
3b659ba075 Merge branch 'main' into image-generation
# Conflicts:
#	studio/frontend/src/components/app-sidebar.tsx
#	studio/frontend/src/components/assistant-ui/model-selector/pickers.tsx
2026-07-09 08:04:50 +00:00
Michael Han
1b825213ea
Stabilize floating monitor drag (#6984)
* Stabilize floating monitor drag

* Restore floating monitor exit animation

* Harden Windows Studio smoke checks

* Keep API menu badge removed

* Apply no-build-tools env overrides in-script

The runner does not apply step-level env keys containing parentheses,
so ProgramFiles(x86) kept its real value and Find-VsBuildTools still
detected VS through vswhere. Set the overrides inside each pwsh step
instead; child processes inherit them. The resolver step moves to pwsh
because bash cannot export a variable named ProgramFiles(x86).

* Reset chat UI session without a second browser context

macOS runs Chromium with --single-process, where closing the last
context tears down the whole browser, so the shutdown re-login died
with TargetClosedError on new_page. Clear cookies and swap pages
inside the same context instead, opening the replacement page before
closing the old one.

* Keep the no-build-tools Path filtered across session refreshes

install.ps1's Refresh-SessionPath and setup.ps1's Refresh-Environment
rebuild the session Path from the Machine and User registry scopes, so
the process-level filter could be undone mid-install and re-expose
CMake. Filter those scopes in the Prepare step with normalized dir
matching and restore them in cleanup.

* Drop stale localStorage auth tokens before re-login

Auth tokens live in localStorage, not cookies, and the login guest
guard redirects on their mere presence. Remove them during the session
reset so the /login navigation is deterministic instead of relying on
the tolerated redirect bounce.
2026-07-09 00:16:05 -07:00
Nilay
3b73cd8829
Fix per-block ID collisions and add block cleanup for unstructured uploads (#6944)
* unstructured block removal

* Enhance unstructured block handling

* Restrict block cleanup to upload UIDs

* cleanup for seed block uploads

* upload cleanup queue for unstructured blocks in recipe studio

* Fix unstructured upload cleanup edge cases

* Fix unstructured upload import ownership

* Fix-unstructured-import-path-ownership

* Guard failed-delete restore against stale block in unstructured drop zone

* Drain queued upload cleanups when autosave is skipped

---------

Co-authored-by: Lee Jackson <130007945+Imagineer99@users.noreply.github.com>
Co-authored-by: imagineer99 <samleejackson0@gmail.com>
Co-authored-by: Daniel Han <danielhanchen@gmail.com>
2026-07-08 20:03:03 -07:00
Michael Han
7a9fb4404e
Remove API menu new badge (#6983) 2026-07-08 08:17:09 -07:00
ErenAta16
e86b7874d4
feat: detect installed coding agent CLIs in Studio settings (#6909)
* feat: detect installed coding agent CLIs in Studio settings

The API-keys panel only ever showed the "claude" flavor of the
`unsloth start` command, so anyone using Codex, OpenCode, OpenClaw,
Hermes, or Pi had to manually rewrite the copied command by hand.

Add a backend check that looks for each agent's CLI binary on PATH
(shutil.which, mirroring the pattern already used elsewhere in
studio/backend/utils) and expose it as GET /api/settings/coding-agents.
The API-keys panel now renders a picker for all six supported agents,
marks the ones it finds installed, and defaults to one of those instead
of always falling back to claude.

Includes unit tests for the detection helper.

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

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

* address review feedback on coding-agent detection

Three fixes from PR review:

- detect_installed_coding_agents now treats a PATH lookup failure as
  "not installed" instead of letting it bubble up and break the
  settings endpoint; added a regression test for it.
- CodingAgentsResponse.agents is now typed as an immutable tuple
  instead of a list built from one, matching CODING_AGENTS itself.
- Fixed a race in the API-keys panel: picking an agent while the
  installed-CLI check is still in flight could get silently overwritten
  once that check resolved. A ref now tracks whether the user has made
  a manual choice, so the auto-detected default only applies before
  that happens.

* Address Codex feedback: GGUF gating and remote-detection scope

- codex refuses to launch against a non-GGUF (transformers-backed) model
  (unsloth_cli's _require_gguf_for_codex), so auto-defaulting to it produced
  a copy-pasteable command that fails immediately whenever the loaded model
  isn't GGUF. Add useActiveModelIsGguf() (looks up the active checkpoint in
  the chat runtime store) and a correction effect that steers the auto-pick
  away from codex unless the loaded model qualifies, without ever touching a
  choice the user made by hand.
- Detection runs via shutil.which on the Studio backend host, which isn't
  the same machine as the browser in a tunnel/remote session. Reword the
  'installed'/'detected' copy to say so explicitly when the tunnel URL is
  in use, instead of implying the check ran on the viewer's own device.

* Rework auto-default per review: loopback gating + inline GGUF check

Replaces the previous approach with the exact shape discussed on the PR:

- Export isLoopbackHost/normalizeHost from agent-command.ts. The detection
  endpoint runs shutil.which on the Studio backend, which only describes the
  browser's own machine when the base this panel targets resolves to
  loopback. For a LAN or tunnel/remote base, gate the whole thing off --
  don't mark anything as "detected" and don't let it drive the default --
  instead of just relabeling the copy.
- Drop the separate GGUF-correction effect and useActiveModelIsGguf hook.
  Read useChatRuntimeStore.getState().activeGgufVariant inline inside the
  existing detection effect's .then() (so it doesn't need to sit in the
  effect's deps), and pick the first detected agent that isn't codex unless
  the loaded model is GGUF, leaving the existing default untouched when no
  compatible agent is detected.

Verified both branches (loopback vs LAN/tunnel base, gguf vs non-gguf,
manual pick preserved, no-compatible-agent fallback) with a standalone
port of the .then() logic.

* Address latest Codex findings: stale detection, model swap, cache

- Clear detectedAgents (and skip the network call entirely) when the panel
  leaves a loopback base, instead of leaving a previous loopback detection
  result marked 'installed' for a command that now targets a LAN/tunnel/
  remote host.
- Add a separate, network-free correction effect keyed on the live
  activeGgufVariant: if codex was auto-picked while a GGUF model was loaded
  and the user then switches to a transformers-backed model while this panel
  stays mounted, steer away from codex instead of leaving a command that
  unsloth_cli's _require_gguf_for_codex will now reject. Never touches a
  manual pick.
- Drop coding-agents.ts's module-lifetime cache. Installed-CLI detection is
  environment state, not a persisted setting, so a stale positive/negative
  from before the user installed something (or reopened the tab) is worse
  than one extra cheap local API call per mount; keep only the in-flight
  de-dupe for concurrent callers.

Verified the correction-effect logic (gguf->non-gguf swap with/without a
fallback, still-gguf no-op, manual pick never overridden) with a standalone
port of the effect.

* Make the codex/GGUF auto-pick symmetric in both directions

The correction effect only steered away from codex when the model stopped
being GGUF; it never steered back toward codex if the model became GGUF
*after* a non-GGUF-gated fallback had already picked something else (e.g.
codex is the only detected CLI, a transformers model is loaded so the
selection correctly falls back to the claude default, then the user loads a
GGUF model while the panel stays mounted -- codex never gets reconsidered).

Consolidate into one effect that re-derives the preferred detected agent
from scratch whenever detectedAgents or activeGgufVariant changes, in either
direction, instead of only reacting to the codex-specific downgrade case.
The fetch effect now only populates detectedAgents/availableAgents; this
effect is the single source of truth for what gets auto-picked from that
list. Never overrides a manual choice.

Verified both transition directions plus the manual-pick-survives and
initial-detection cases with a standalone port of the derivation logic.

* Reset the auto-pick to the default when it stops being trustworthy

Two more real gaps from the latest Codex pass on d988f52:

- The unified derivation effect only handled the case where a *different*
  detected agent could take over. If codex was the only detected agent and
  auto-picked while a GGUF model was loaded, then the model stopped being
  GGUF, 'preferred' came back undefined and the effect silently left the
  selection on codex -- exactly the command unsloth_cli's
  _require_gguf_for_codex now rejects. Fall back to DEFAULT_AGENT in that
  case instead of leaving it untouched.
- Leaving a loopback base cleared detectedAgents (so the 'installed' badges
  correctly disappear) but left whatever agent had been auto-picked from
  that now-stale, server-side-only detection still selected. Reset to
  DEFAULT_AGENT there too, unless the user picked by hand.

Introduces a shared DEFAULT_AGENT constant instead of repeating the "claude"
literal at each reset site. Verified all five cases (both new resets, both
manual-pick-survives variants, and the existing multi-detected-agent
fallback still preferring another compatible agent over resetting) with a
standalone port of the effects.

* Derive GGUF-ness from the actual loaded state, not just the variant string

activeGgufVariant only covers an HF-repo GGUF pick (a specific quant
variant string). A direct local .gguf file -- custom folder, LM
Studio, or drag-drop -- is just as much a GGUF the codex preflight
(unsloth_cli's _require_gguf_for_codex) would accept, but it never has
a "variant" to report, so it read as non-GGUF here even though
/api/inference/status correctly reports is_gguf: true for it. That
mismatch could leave a Codex-only install not auto-selected, or reset
an auto-picked Codex, for a model that actually supports it.

Combined activeGgufVariant with activeNativePathToken (covers the
drag-drop/picked-file case) and ggufContextLength (only ever populated
when the backend last reported is_gguf: true for the active model, see
applyActiveModelStatusToStore) so all three paths a model can be GGUF
through are covered, matching the same is_gguf-or-equivalent check
hasGgufSource already applies to a staged pick elsewhere in this
codebase.

* Clear stale native-path token on a non-GGUF status refresh

When a native (drag-dropped or picked) GGUF was loaded and the backend later
switches to a transformers model outside the UI load path, refresh() adopts the
new /api/inference/status via setCheckpoint and applyActiveModelStatusToStore.
Those reset activeGgufVariant and ggufContextLength but never clear
activeNativePathToken, so the isGguf OR stays true after the switch and a
Codex-only detection auto-selects unsloth start codex for a non-GGUF model its
preflight rejects.

Drop activeNativePathToken in applyActiveModelStatusToStore whenever the status
is non-GGUF. A real GGUF load reports is_gguf: true, so its token is preserved
(the load path owns it); only a non-GGUF status clears it.

* Add the AGPL-3.0 header to the new studio contract test

* Fix/adjust agent detection for PR #6909

* [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>
Co-authored-by: danielhanchen <danielhanchen@gmail.com>
Co-authored-by: wasimysaid <112766706+wasimysaid@users.noreply.github.com>
2026-07-08 05:26:50 -07:00
Daniel Han
de60a3a994
Studio: fix currency and indentation edge cases in LaTeX rendering (#6957)
* Studio: fix link, currency and indentation edge cases in LaTeX rendering

Follow-up to #6914. Three fixes to studio/frontend/src/lib/latex.ts:

- Skip reference-link definition URLs ([id]: url) during delimiter
  conversion, so escaped parens in such URLs are not rewritten as math.
- Preserve the opener line's indentation when emitting a display $$ block,
  so a \[...\] inside a list item stays part of the list.
- Stop a currency amount from pairing with a converted span's opening $,
  which swallowed the price into math (for example $5 + x \(y\)).

* Exclude GFM footnote definitions from the reference-URL skip

A footnote definition like [^1]: \(x\) had its body treated as a link
destination, so leading math was left literal. Skip [^...] labels.

* Merge overlapping link destination regions

A reference-def token can nest inline-link spans (for example
[1]: http://h/[a](b)/foo\(x\)), so the combined spans could overlap and
isInRegion's binary search missed the outer one, rewriting the URL. Merge
overlapping spans before the search.

* Guard lineStart when the display opener is at index 0

Behavior is unchanged (lastIndexOf clamps a negative fromIndex to 0), but
the explicit guard avoids relying on that implicit clamp.

* Scope to indentation and currency fixes

Drop the reference-link URL protection added earlier. It guards a case
models effectively never emit (escaped parens in a reference-style URL),
and approximating CommonMark reference definitions with a regex needs
open-ended special-casing. Keep the two high-value fixes: preserve display
math indentation (including multi-line bodies) inside a list item, and stop
a currency amount from pairing with a converted span's opening dollar sign.
2026-07-08 03:13:32 -07:00
Daniel Han
f1a2621631
Studio: show Hugging Face address on hover for Hub and online model rows (#6382) (#6928)
* Studio: show Hugging Face address on hover for Hub and online model rows

The model selector already shows an on-disk path tooltip on local rows,
but Hub and online rows showed only the bare repo id, and nothing at all
when there was no VRAM estimate. Add an optional hubUrl prop and a
hubRepoUrl helper that mirrors localPathTooltip, and surface
huggingface.co/<repo_id> on hover for the Discover, search, and
downloaded Hub rows. Local and VRAM tooltips are unchanged; the VRAM
tooltip now also appends the address line.

Closes #6382

* Studio: use a 700ms hover delay before the model-row tooltip

Give the model-row hover tooltip (the Hugging Face address, plus the VRAM
and local-path lines it shares) a 700ms open delay instead of showing it
instantly, so it does not flash while sweeping the mouse down the list.

* Fix/adjust GGUF tooltips for PR #6928

---------

Co-authored-by: wasimysaid <112766706+wasimysaid@users.noreply.github.com>
Co-authored-by: Wasim Yousef Said <wasimysdev@gmail.com>
2026-07-08 03:09:52 -07:00
Michael Han
7f9964f21e
Move New badge to System settings tab (#6963)
* Move New badge to System settings tab

Show the "New" badge on the System tab and drop it from Connections.

* Stabilize refresh revocation UI test

* [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-08 01:51:34 -07:00
Michael Han
e7e6a0fb47
Polish assistant message actions menu (#6962)
* Polish assistant message actions menu

Use the circle question mark (HelpCircleIcon) for the "See response
details" action instead of the file-database icon, and lowercase the
"Export as markdown" label.

* Align response details sheet icon
2026-07-08 01:50:35 -07:00
Daniel Han
8fead6fcfa Key image-gen LoRA rows by index so typing a repo id keeps input focus
The LoRA row div was keyed on sel.id, the editable repo-id field. Typing the
first character changed the key from the fallback index to that character, which
remounted the row and dropped focus on the input, so only one character was ever
captured and manual entry of a repo id was impossible (paste still worked). The
list is index-addressed (every mutation uses j === i) and rows are removed
explicitly via the delete button, so the index is the stable key, matching the
reference-image rows above.
2026-07-08 07:19:13 +00:00
Daniel Han
4f57a43492 Fix union ControlNet mode for bare repo-id inputs and resident single-file Reapply
union_control_mode() only matched the short catalog id, so a client naming a
curated union ControlNet by its bare HF repo id (the form resolve_controlnet
documents and accepts) got None and the generate path omitted control_mode,
which makes FluxControlNetModel.forward raise controlnet_mode cannot be None.
Fall back to matching repo_id against the curated entries so the bare repo id
resolves to the same union mode; non-union bare repos still return None.

The images page also wired Reapply for a resident single_file model with no
checkpoint filename (status carries none), but the backend rejects a
single_file/gguf load without a filename, so clicking Reapply 400'd. Narrow the
resident-Reapply wiring to pipeline (the one kind that needs no filename),
matching the existing GGUF handling, so single_file stays a no-op instead of
erroring.
2026-07-08 03:15:09 +00:00
Daniel Han
27511f30ee Merge origin/main into image-generation
Resolve the app-sidebar.tsx conflict: main refactored the chat-export dropdown to a
format-based CHAT_EXPORT_OPTIONS + dynamic-import exportConversationByFormat dispatcher,
which the merged body already uses. Keep main's dispatcher and drop the branch's static
export imports; keep TestTubeOutlineIcon imported from the shared @/lib/hugeicons-derived
module (also used by images-page) rather than main's duplicate inline definition. The
branch's Images and Video nav items are preserved.
2026-07-08 01:57:07 +00:00
Wasim Yousef Said
49d1fb3863
Speed up Studio startup path (#6899)
* Speed up Studio startup path

* Studio: recheck managed binary executability on preflight cache hit and ignore stale unauthenticated platform fetches

Preflight: a matching capability cache fingerprint no longer skips the
runnability check when the managed binary's executable bit was cleared
(size and mtime unchanged, since chmod bumps ctime not mtime). The cache
fast path now confirms the binary is still executable, otherwise it falls
back to the CLI help probe so preflight reports Stale and can repair,
instead of returning Ready and failing later at backend start. Adds a
regression test.

Frontend: now that first render is no longer gated on fetchDeviceType,
the initial unauthenticated health call can resolve after an
authenticated platform fetch. Guard the store so a late unauthenticated
or failed non-forced response cannot overwrite an already authoritative
device type, tunnel URL, or secure flag. Forced refreshes and the first
unauthenticated load are unaffected.

* Studio: use access(X_OK) for the preflight cache executability guard

A mode bitmask treats any execute bit as launchable, but the executable
bits can be set only for another owner or group, or be denied by an ACL,
so the current user could still hit PermissionDenied at launch and the
cached fast path would wrongly return Ready. access(X_OK) checks real
executability for the calling user, so an ownership or permission change
correctly falls back to the CLI help probe and the Stale repair path.

* Studio: ignore any stale non-forced platform fetch once authoritative

Extend the platform store guard so a non-forced health response never
overwrites an already authoritative result, not only unauthenticated
ones. With a saved token the post-render non-forced request can be
authenticated but older than a later forced refresh that already picked
up the tunnel URL and secure flag; if that earlier request resolves last
it would null those fields. Now any non-forced response is dropped once
the store holds a server-reported platform. Forced refreshes and the
first authoritative write are unaffected.

* Studio: run the managed CLI help probe before trusting the preflight cache

Restore running the managed CLI help probe before returning Ready from
the desktop capability cache, so a managed install whose venv interpreter
or a runtime dependency is broken (while path, size, mtime, and markers
are unchanged) is reported Stale for repair rather than proceeding to a
backend start that cannot spawn. The capability cache still skips the
heavier desktop-capabilities probe on a hit, so a warm cache runs one
probe instead of two. Removes the executable-access shortcut, which the
help probe now subsumes.

---------

Co-authored-by: Daniel Han <danielhanchen@gmail.com>
2026-07-07 18:08:07 -07:00
oobabooga
93c9d6d0dd
Studio: render \[ \] and \( \) LaTeX delimiters in chat (#6914) 2026-07-07 15:13:53 -03:00
Nilay
07ecdb34c0
Sort chat recents by last activity (#6844)
* show chat by by last activity

* Update chat thread updated_at logic and enhance sidebar chat item handling

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

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

---------

Co-authored-by: Lee Jackson <130007945+Imagineer99@users.noreply.github.com>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2026-07-07 17:54:32 +01:00
Daniel Han
ffa8a56391 Merge image-generation bug fixes (#6872)
Fold PR #6872's image-generation fixes into the branch, deduped against the
round-12 dataset-upload and gallery integrity work already on image-generation.

Fixes carried forward from #6872:
- fp8 single-file transformer memory estimate: an fp8 checkpoint loads with no
  quantization_config and diffusers upcasts it to bf16 (~2x resident), so budget
  it accordingly in _plan_memory and estimate_safetensors_dense_mib.
- dense-quant OOM-evict preflight: when the GGUF fits resident but the dense bf16
  transformer this path materializes does not, skip the fast path up front rather
  than evict the current pipeline and OOM in finalization. Combined with the
  existing offload->resident candidate re-plan so both the family-table estimate
  and the on-disk shard measurement gate engagement (unified on the
  transformer_resident_override_mib plan override).
- ControlNet: evict the previous module and its from_pipe wrapper before loading a
  new one so swapping ControlNets within a base-model load cannot accumulate to OOM.
- ControlNet union_control_mode: raise on an unknown control type instead of
  silently defaulting to canny.
- edit-family mask rejection: raise instead of silently dropping a mask on an
  image-editing model that has no inpaint pipeline.
- companion cache: walk the snapshot dir and exclude transformer/ so the
  dense-quant prefetch's cached shards do not inflate the companion total and
  wrongly force offload.
- training: drop piecewise_constant from the LR scheduler enum and force bf16 for
  fp16-incompatible families.
- dataset upload: batch-atomic staging with the same-stem duplicate guard.
- images page: guard negative-prompt restore on guidance>0, clear stale ControlNet
  selection on restore, and revert an optimistic quant label when a pipeline load
  never starts.
- uninstall (sh + ps1): keep the owner-marker guard on sd.cpp removal.

Conflicts resolved in favour of image-generation's evolved memory system,
loadSpecFor catalog, and stop-and-save (lora_path) run detection; #6872's fp8 and
dense-preflight fixes carried forward on top. All affected backend tests pass
(test_diffusion_backend, test_diffusion_training, test_diffusion_lora_trainer,
test_video_gallery, test_diffusion_controlnet).
2026-07-07 16:14:06 +00:00
Etherll
ba450b437e
Studio: add assistant response details panel (#6842)
* Studio: add assistant response details panel

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

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

* Hide model badge by default, show on hover/focus

Wrap MessageResponseModelBadge in a span with hidden/group-hover visibility classes to reduce visual clutter. The badge now only displays when hovering or focusing on the assistant message, improving the UI presentation. Updated corresponding tests to verify the new CSS classes.

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2026-07-07 06:26:00 -07:00
Daniel Han
414503745e
Run the malware gate on the RAG embedding model before it loads (#6887)
* Run the malware gate on the RAG embedding model before it loads

Setting the RAG embedding model through PUT /api/settings/embedding-model
persisted an arbitrary repo and later handed it straight to
SentenceTransformer, which deserializes pickle weights. Unlike the normal
model-load paths, this route never ran evaluate_file_security, and force
skipped verification entirely, so a repo Hugging Face flags as unsafe (or
any repo under force) could be downloaded and loaded in the backend
process without a scan.

Run the malware/pickle scan at both ends: the settings endpoint now scans
before persisting and returns 409 on a flagged repo even under force
(force still only skips the is-embedding-model type check for offline or
local repos), and the embedder scans again at the load sink so a name that
arrives via env or default is covered too. Local paths and unreachable
scans fail open inside evaluate_file_security, and the sink never bricks
the embedder on a gate error.

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

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

* Thread the load token into the embedding scan and hard-fail on a block

The load-sink scan ran without a token, so evaluate_file_security (which
passes token=False when none is given) could not reach a gated or private
repo and failed open for exactly the model SentenceTransformer would still
load. Resolve the loader's own token (HF_TOKEN env or the cached login)
and pass it to the sink scan, and fall back to it in the settings endpoint
when the request omits one.

The sink previously raised a plain RuntimeError, which the llama-server
fallback in encode() and _build_st_backend_or_fallback() swallowed as a
routine ST failure, silently switching backends instead of blocking. Raise
a distinct UnsafeEmbeddingModelError that both fallback paths re-raise, so
a flagged model hard-fails.

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

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

* Scan sentence-transformers module dirs and scope the embedding pickle gate to the ST backend

Extend the RAG embedding malware gate so a poisoned pickle under a SentenceTransformer
module dir (for example 0_Transformer/pytorch_model.bin) blocks. Those dirs are read
from the repo's modules.json and passed as load roots to evaluate_file_security at both
the settings endpoint and the load sink, so such a pickle is treated as root-level there
instead of an unreferenced nested shard that was previously allowed.

Scope the ST pickle scan to the sentence-transformers backend. On the llama-server
backend the embedder loads GGUF files (inert) from the -GGUF companion repo, never the
ST repo's pickle, so a custom ST repo with a flagged pickle and a clean GGUF companion
is no longer rejected. The existing GGUF availability checks already cover that path.

Return 403 for the hard security block instead of 409. The settings UI routes every 409
into the forceable save-anyway flow, but this block cannot be bypassed by force, so it
now uses a distinct status the client treats as non-forceable.

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

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

* Base the embedding pickle scan on the actual backend, not just the resolver

_llama_backend_active only consulted the auto resolver, so on a GPU box
where auto resolves to sentence-transformers but the process already fell
back to the llama-server backend at runtime (a torch or CUDA load/encode
failure), it returned False and the settings endpoint hard-blocked a save
whose ST pickle is flagged even though the process loads only inert GGUF.

Add active_backend_is_llama, which reflects the actual built backend (True
when the cached backend is a LlamaServerBackend, including a runtime
fallback) and otherwise defers to the resolver as a fresh process would,
and delegate _llama_backend_active to it.

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

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

* Report the cached embedding backend verbatim, not the resolver

active_backend_is_llama() fell through to the config resolver whenever a
backend was already built but was not llama-server, so a live
sentence-transformers backend could report llama=True once the resolver
picked llama (GPU heuristic or a runtime config change) and wrongly skip
its pickle scan. Once a backend exists, return isinstance(backend,
LlamaServerBackend) directly; only defer to the resolver before any
backend is built.

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2026-07-07 04:30:21 -07:00
Daniel Han
2e752e6900 Seed image sliders from a resident model's recipe on discovery
When the images page mounts with a diffusion model already loaded (a prior
session or another route left it resident), refreshStatus only set status; the
steps/guidance sliders kept the unrecognised-model fallback (few-step, no CFG),
so a resident full model such as flux.1-dev generated at 9 steps guidance 0 and
produced garbage until the user re-picked it. Add a one-shot effect that seeds
the sliders from defaultsFor(status.repo_id) when the page discovers a resident
model it did not load itself, and wires Reapply to it for non-GGUF pipelines.
Guarded by lastLoad and a per-repo ref so a manual slider edit is never
clobbered.
2026-07-07 06:10:36 +00:00
Daniel Han
c399aabd5d Merge remote-tracking branch 'origin/main' into fold-integration
# Conflicts:
#	scripts/scan_packages_baseline.json
2026-07-07 05:52:16 +00:00
Daniel Han
9dabe96786
Studio chat: tool-call nudging on by default (API stays opt-in) (#6883)
* Studio chat: tool-call nudging on by default (API stays opt-in)

Healing is already default-on everywhere and the nudge retry from the
client-tool passthrough is opt-in on the API. Studio chat had neither
signal: the frontend never sent nudge_tool_calls, and the safetensors
and MLX server-side loop lacked the GGUF loop's plan-without-action
re-prompt entirely.

Backend: the re-prompt helpers move from llama_cpp.py into
tool_call_parser.py (shared, cycle-free; the GGUF loop imports them
under its old names with zero behavior change) and
run_safetensors_tool_loop now re-prompts once at the streaming
no-tool-call exit, gated on Auto-Heal, active tools, nothing executed
yet, and short forward-looking text. Re-prompts do not consume tool
iterations.

Frontend: the chat adapter sends nudge_tool_calls from a new
nudgeToolCalls runtime setting (default true) with the same
persistence, hydration, and settings toggle plumbing as Auto-Heal.
Request-model defaults are untouched, so raw API callers stay opt-in.

* Address review: persist the nudge setting, consume the flag in the loops, skip the re-prompt after RAG autoinject

ChatSettingsPayload uses extra forbid, so a settings patch containing
nudgeToolCalls failed to persist any settings; the field is now typed
and round-trips. nudge_tool_calls now plumbs into both server-side tool
loops and gates the plan-without-action re-prompt with None meaning on,
so API callers keep today's behavior, explicit false disables it, and
Studio's default-on flag actually controls the path Studio chat runs.
The safetensors loop no longer re-prompts after RAG autoinject: the
injected retrieval bypasses the tool controller, so the nothing-executed
gate saw an empty history and re-asked after a successful retrieval.

* Safetensors loop: the plan-without-action retry requires an explicit nudge flag

The retry is new on this loop, so an omitted nudge_tool_calls must not
change existing API behavior; Studio opts in explicitly. The GGUF loop
keeps None as on because its re-prompt predates the flag.

* Suppress the plan-without-action re-prompt after a denied tool confirmation

A denial appends TOOL_REJECTED_MESSAGE but records nothing in the tool
controller history, so the nothing-executed gate re-prompted the model
to call the tool the user had just rejected, producing another
confirmation prompt. A denial now suppresses the re-prompt for the rest
of the request, mirroring the RAG autoinject handling.

* Tighten plan-without-action re-prompt comments

* Tighten plan-without-action re-prompt comments

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

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

* Studio: match unified plan-without-action nudge cap to GGUF default of 3

The shared MAX_ACT_REPROMPTS was set to 1, but GGUF's established default
(llama_cpp.py) has re-prompted a stalling model up to 3 times since #5620.
Restore the GGUF-matched cap so safetensors and MLX inherit the same
behavior, and update the safetensors cap test to assert the cap dynamically.

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2026-07-06 19:41:19 -07:00
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
oobabooga
9d8bf002f8 Merge remote-tracking branch 'origin/image-generation' into fix/imggen-review-bugs
# Conflicts:
#	studio/backend/core/inference/diffusion.py
#	studio/backend/core/training/diffusion_train_common.py
#	studio/backend/routes/training.py
#	studio/backend/tests/test_diffusion_dataset_api.py
2026-07-06 09:16:30 -03:00
Daniel Han
cb6737cbb8
Auto Xet to HTTP download fallback in from_pretrained; share Studio's fallback via unsloth_zoo (#6638) 2026-07-06 05:13:25 -07: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
115adaf50c Merge branch 'video-wan' into video-hunyuan-gate 2026-07-05 11:39:50 +00:00
Daniel Han
1e2ee60dd4 Merge branch 'video-tab' into video-wan 2026-07-05 11:39:49 +00:00
Daniel Han
cbfb1388f7 Merge branch 'video-inference' into video-tab 2026-07-05 11:39:47 +00:00