Fix/adjust diffusion: round 30 P1 + P2 batch for PR #5754

Four actionable findings from round 30. Skipped P1 #1 / #2 / #3
(huggingface-hub bump in studio.txt / single-env / colab-new) because
the live B200 Studio that successfully generated FLUX.2 klein images
runs the exact combo the reviewer flags as broken:
    huggingface_hub 0.36.2 + transformers 4.57.6 + diffusers 0.37.1
    Flux2KleinPipeline: True (imports cleanly)
The is_offline_mode ImportError only fires with transformers 5.x, and
the standard install path pins transformers==4.57.6 via constraints.
The round 26 fix bumped no-torch-runtime.txt + pyproject huggingfacenotorch
where the --no-deps install path can land on transformers 5.x; that
remains the correct surface.

1. core/inference/diffusion.py: preflight transformers + accelerate
   via importlib.util.find_spec BEFORE any destructive GPU-owner
   unload. Diffusers can expose stub pipeline classes when
   transformers / accelerate are missing, so the load used to drop
   chat first and fail later inside from_pretrained. find_spec
   keeps existing tests that stub these modules passing because no
   real module is executed (round 30 P1 #11).

2. models/export.py ExportGGUFRequest.quantization_method: extend
   the embedded HF token validator to this field too. Round 23
   added the control-char guard but not the token guard; the value
   is forwarded into worker command lines and reflected in error /
   success text (round 30 P1 #5).

3. models/data_recipe.py SeedInspectUploadRequest: add
   _no_control_chars + _reject_embedded_hf_token field_validators
   to filename and to each entry of file_names. Mirrors the sibling
   SeedInspectRequest.dataset_name hardening (round 30 P1 #6).

4. frontend/src/features/images/images-page.tsx: defer the initial
   refreshStatus() call via queueMicrotask so the synchronous
   setRefreshingStatus(true) inside it does not trip the
   react-hooks/set-state-in-effect lint on mount (round 30 P2 #12).

Deferred (need larger surgery / out of scope for this round):
   P1 #4 native_path_lease for diffusion local-path loads
   P1 #7-#10 helper/advisor + public-start window mutual lock symmetry

Tests: 98 targeted (diffusion + cached_gguf + inference_validation)
pass locally; frontend npm run typecheck passes.
This commit is contained in:
Daniel Han-Chen 2026-05-25 15:11:05 +00:00
commit 3b60d40f92
4 changed files with 63 additions and 1 deletions

View file

@ -800,6 +800,23 @@ class DiffusionBackend:
"loading an image model."
) from exc
# Round 30 P1 #11: also preflight transformers + accelerate
# BEFORE any destructive unload. Diffusers can expose stub
# pipeline classes when transformers is missing or broken, so
# the load would otherwise tear down chat first and fail
# later inside from_pretrained. Use find_spec (no module
# execution) so test environments that stub these modules
# still pass the preflight without us actually importing them.
import importlib.util as _ilu
for _mod in ("transformers", "accelerate"):
if _ilu.find_spec(_mod) is None:
raise RuntimeError(
"Diffusion image generation requires the Studio torch "
f"runtime. Missing dependency: {_mod}. Install the "
"Studio torch runtime (re-run setup.sh / install.ps1) "
"before loading an image model."
)
fam = detect_family(repo_id, override_family = family_override)
if fam is None:
# Round 22 P2 #4: route the repo label through

View file

@ -119,6 +119,37 @@ class SeedInspectUploadRequest(BaseModel):
unstructured_chunk_size: int | None = Field(default = None, ge = 1, le = 20000)
unstructured_chunk_overlap: int | None = Field(default = None, ge = 0, le = 20000)
# Round 30 P1 #6: filename / file_names are reflected as dataset
# names + error/log messages; harden them the same way the sibling
# SeedInspectRequest hardens dataset_name.
@field_validator("filename")
@classmethod
def _no_filename_control_chars(cls, v, info):
return _no_control_chars(v, info.field_name)
@field_validator("filename")
@classmethod
def _no_filename_embedded_hf_tokens(cls, v, info):
return _reject_embedded_hf_token(v, info.field_name)
@field_validator("file_names")
@classmethod
def _no_file_names_control_chars(cls, v):
if v is None:
return v
for i, entry in enumerate(v):
_no_control_chars(entry, f"file_names[{i}]")
return v
@field_validator("file_names")
@classmethod
def _no_file_names_embedded_hf_tokens(cls, v):
if v is None:
return v
for i, entry in enumerate(v):
_reject_embedded_hf_token(entry, f"file_names[{i}]")
return v
@model_validator(mode = "after")
def _check_mutual_exclusivity(self) -> "SeedInspectUploadRequest":
has_legacy = self.content_base64 is not None

View file

@ -218,6 +218,14 @@ class ExportGGUFRequest(BaseModel):
def _no_quantization_control_chars(cls, v, info):
return _no_control_chars(v, info.field_name)
# Round 30 P1 #5: quantization_method is forwarded into worker
# command lines and reflected in error / success text, so also
# reject embedded HF tokens to mirror the repo_id hardening.
@field_validator("quantization_method")
@classmethod
def _no_quantization_embedded_hf_tokens(cls, v, info):
return _reject_embedded_hf_token(v, info.field_name)
class ExportLoRAAdapterRequest(ExportCommonOptions):
"""Request for exporting only the LoRA adapter (not merged)."""

View file

@ -145,7 +145,13 @@ export function ImagesPage() {
}, []);
useEffect(() => {
void refreshStatus();
// Round 30 P2 #12: defer the first refreshStatus call via
// queueMicrotask so the synchronous setRefreshingStatus(true)
// inside it does not trip the react-hooks/set-state-in-effect
// lint rule on the mount render.
queueMicrotask(() => {
void refreshStatus();
});
}, [refreshStatus]);
// Round 27 P2: when the backend is mid-load (is_loading=true) the