Studio: preserve foreign gallery files, force safetensors on remote ControlNets, and close dataset/seed/GPU gaps

Gallery clear/delete now scope to Studio-owned files: image_gallery and
video_gallery skip PNGs / MP4s without a readable recipe (a hand-dropped or
orphan file the listing already hides), so clear() and a guessed-id delete no
longer destroy files the gallery never surfaced.

Remote ControlNets now force use_safetensors: a bare owner/name reaches
from_pretrained without the base trust gate, and the Hub scan fails open when
unavailable, so requiring safetensors closes the pickle deserialization vector.

POSIX uninstall now stops resident sd-server / sd-cli under an owned sd.cpp root
before removing the tree (marker-gated), mirroring the Windows stop-before-delete
scan; a live native server no longer survives unlinking its binary.

Diffusion dataset containment: the training-start read path and the discovery
picker route bare names through the protected resolver, so a symlinked dataset
is rejected / not advertised like the caption/delete routes already do. Uploads
gain the inference decode guard (oversized real images 400 before OOMing the
trainer) and dataset upload/caption/delete/import are blocked with 409 while a
diffusion run is active.

JSONL readers (trainer + routes) tolerate non-object JSON and invalid UTF-8
instead of raising AttributeError / 500.

LoRA family compatibility is enforced in the shared resolver, not only the
picker, so a direct API client cannot apply a mismatched-family adapter.

GPU arbiter gains release_if so the image/video unload idle-check and release
are atomic against a concurrent same-owner load's registration. Native batch
recipes persist the base batch_seed and restore replays from it, so a native
batch_index>0 image no longer advances its seed twice.

FLUX.2-klein selects its sd.cpp text encoder by variant (4B -> Qwen3-4B,
9B -> Qwen3-8B) instead of the single family default.
This commit is contained in:
Daniel Han 2026-07-13 10:02:42 +00:00
commit 5eef2f4003
23 changed files with 548 additions and 33 deletions

View file

@ -35,6 +35,37 @@ _pkill_escape() {
printf '%s' "$1" | sed -e 's:[][\\.^$*+?{|}()/]:\\&:g'
}
# Owned sd.cpp roots (default + custom siblings), each gated on the install-time
# owner marker. Native diffusion builds beside a custom/env root at
# <parent>/stable-diffusion.cpp (find_sd_cpp_binary resolves from
# UNSLOTH_STUDIO_HOME.parent) and at $HOME/.unsloth/stable-diffusion.cpp by default.
# The marker is mandatory so we never stop a user-managed sd-server from an
# unrelated checkout that happens to sit at one of these paths.
_owned_sd_cpp_roots() {
_default_sd="$HOME/.unsloth/stable-diffusion.cpp"
[ -f "$_default_sd/.unsloth-studio-owned" ] && printf '%s\n' "$_default_sd"
_custom_studio_roots 2>/dev/null | while IFS= read -r _root; do
[ -n "$_root" ] || continue
_sd_root="$(dirname "$_root")/stable-diffusion.cpp"
[ -f "$_sd_root/.unsloth-studio-owned" ] && printf '%s\n' "$_sd_root"
done
}
# pkill resident sd-server / sd-cli whose executable lives under an owned sd.cpp
# root, BEFORE that tree is removed below: a live native server keeps running
# after its binary is unlinked. Anchored on the owned root so an unrelated
# checkout's sd-server is never matched.
_stop_owned_sd_cpp_processes() {
_signal="$1"
command -v pkill >/dev/null 2>&1 || return 0
_owned_sd_cpp_roots | while IFS= read -r _root; do
[ -n "$_root" ] || continue
[ -d "$_root" ] || continue
_re=$(_pkill_escape "$_root")
pkill "-$_signal" -f "^${_re}/([^ ]*/)?sd-(server|cli)( |\$)" 2>/dev/null || true
done
}
_pkill_studio() {
# Prefer PID files written by _spawn_terminal so we only touch our own installs.
for _data_dir in "$HOME/.local/share/unsloth" $(_custom_studio_data_dirs); do
@ -80,6 +111,12 @@ $_roots_from_conf"
pkill -KILL -f "$_pat" 2>/dev/null || true
done
done
# Native diffusion servers (sd-server / sd-cli) survive unlinking their binary,
# so stop the ones under an owned sd.cpp root before those trees are removed.
_stop_owned_sd_cpp_processes TERM
sleep 0.5
_stop_owned_sd_cpp_processes KILL
}
_remove_path() {