Fix/adjust diffusion: round 22 P1+P2 batch for PR #5754
P1 #1: ``TrainingStartRequest.model_name`` now runs the same control-character and embedded-HF-token validators that the chat and diffusion request models gained in rounds 5 / 15 / 20 / 21. ``/api/training/start`` previously accepted newline / tab / control characters and URL-form ``hf_xxxxx`` tokens that flowed into structured-log sinks via "Loading model %s" lines. P1 #2: ``_run_with_helper`` in ``utils/datasets/llm_assist.py`` now skips the helper GGUF when the diffusion image backend reports loaded / loading. The public chat / training / export routes already do this through ``_release_diffusion_for``, but this dataset-side helper loaded llama-server directly with no diffusion guard, so an Images-page allocation would race the helper for VRAM. New ``_diffusion_image_model_busy`` helper fails closed (treats status() failure as busy) so the resident image model is preserved instead of being overwritten. P1 #3: same ``_diffusion_image_model_busy`` guard added to ``_run_multi_pass_advisor`` (the dataset conversion advisor), which has the same direct llama.cpp load shape. P2 #4: the early "Could not infer a diffusion family" RuntimeError now routes ``repo_id`` through ``_display_repo_id`` before formatting. A local absolute path that did not match any known family used to leak the operator's filesystem layout via the 400 response body, last_error, and log line. All 97 diffusion + training-validation + related tests pass locally.
This commit is contained in:
parent
63f3faf022
commit
09c51147a9
3 changed files with 62 additions and 1 deletions
|
|
@ -802,8 +802,13 @@ class DiffusionBackend:
|
|||
|
||||
fam = detect_family(repo_id, override_family = family_override)
|
||||
if fam is None:
|
||||
# Round 22 P2 #4: route the repo label through
|
||||
# ``_display_repo_id`` so a local absolute path that did
|
||||
# not match any family does not leak the operator's
|
||||
# filesystem layout via the error message / last_error
|
||||
# / 400 response body.
|
||||
raise RuntimeError(
|
||||
f"Could not infer a diffusion family for '{repo_id}'. "
|
||||
f"Could not infer a diffusion family for '{_display_repo_id(repo_id)}'. "
|
||||
"Pass family_override = 'flux.2-klein' / 'flux.2' / "
|
||||
"'flux.1' / 'qwen-image' / 'stable-diffusion-3' / "
|
||||
"'stable-diffusion-xl' to disambiguate."
|
||||
|
|
|
|||
|
|
@ -8,6 +8,13 @@ Pydantic schemas for Training API
|
|||
from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator
|
||||
from typing import Any, Optional, List, Dict, Literal
|
||||
|
||||
# Round 22 P1 #1: reuse the chat / diffusion identifier validators
|
||||
# so /api/training/start rejects newline / tab / control characters
|
||||
# and URL-form ``hf_xxxxx`` tokens in ``model_name``. Without these
|
||||
# a caller could log-line-smuggle through "Loading model %s" lines
|
||||
# and leak the bearer token into structured-log sinks.
|
||||
from models.inference import _no_control_chars, _reject_embedded_hf_token
|
||||
|
||||
|
||||
_MAX_BATCH_SIZE = 4096
|
||||
_MAX_GRAD_ACCUM = 4096
|
||||
|
|
@ -49,6 +56,20 @@ class TrainingStartRequest(BaseModel):
|
|||
model_name: str = Field(
|
||||
..., description = "Model identifier (e.g., 'unsloth/llama-3-8b-bnb-4bit')"
|
||||
)
|
||||
|
||||
# Round 22 P1 #1: identifier hardening (round 5 / 15 / 20 / 21
|
||||
# extended these to chat + diffusion request models; training
|
||||
# was the last unguarded entry point).
|
||||
@field_validator("model_name")
|
||||
@classmethod
|
||||
def _no_model_name_control_chars(cls, v, info):
|
||||
return _no_control_chars(v, info.field_name)
|
||||
|
||||
@field_validator("model_name")
|
||||
@classmethod
|
||||
def _no_model_name_embedded_hf_tokens(cls, v, info):
|
||||
return _reject_embedded_hf_token(v, info.field_name)
|
||||
|
||||
training_type: Literal["LoRA/QLoRA", "Full Finetuning", "Continued Pretraining"] = (
|
||||
Field(
|
||||
...,
|
||||
|
|
|
|||
|
|
@ -109,6 +109,28 @@ def precache_helper_gguf():
|
|||
pass
|
||||
|
||||
|
||||
def _diffusion_image_model_busy() -> bool:
|
||||
"""Round 22 P1 #2 / #3: helper / advisor GGUFs share VRAM with
|
||||
the Images page diffusion pipeline. Public chat / training /
|
||||
export routes call the strict ``_release_diffusion_for`` helper
|
||||
before allocating, but these dataset-side helpers used to load
|
||||
llama-server directly with no diffusion guard at all. Skip the
|
||||
helper GGUF when ``DiffusionBackend.status()`` reports loaded /
|
||||
loading so we do not double-own VRAM. Fail closed (treat as
|
||||
busy) on any status() error to preserve the resident image
|
||||
model rather than racing it for memory.
|
||||
"""
|
||||
try:
|
||||
from core.inference.diffusion import get_diffusion_backend
|
||||
except Exception:
|
||||
return False
|
||||
try:
|
||||
status = get_diffusion_backend().status()
|
||||
except Exception:
|
||||
return True
|
||||
return bool(status.get("is_loaded") or status.get("is_loading"))
|
||||
|
||||
|
||||
def _run_with_helper(prompt: str, max_tokens: int = 256) -> Optional[str]:
|
||||
"""
|
||||
Load helper model, run one chat completion, unload.
|
||||
|
|
@ -118,6 +140,12 @@ def _run_with_helper(prompt: str, max_tokens: int = 256) -> Optional[str]:
|
|||
if os.environ.get("UNSLOTH_HELPER_MODEL_DISABLE", "").strip() in ("1", "true"):
|
||||
return None
|
||||
|
||||
if _diffusion_image_model_busy():
|
||||
logger.info(
|
||||
"Skipping helper GGUF while a diffusion image model is loaded/loading"
|
||||
)
|
||||
return None
|
||||
|
||||
repo = os.environ.get("UNSLOTH_HELPER_MODEL_REPO", DEFAULT_HELPER_MODEL_REPO)
|
||||
variant = os.environ.get(
|
||||
"UNSLOTH_HELPER_MODEL_VARIANT", DEFAULT_HELPER_MODEL_VARIANT
|
||||
|
|
@ -508,6 +536,13 @@ def _run_multi_pass_advisor(
|
|||
if os.environ.get("UNSLOTH_HELPER_MODEL_DISABLE", "").strip() in ("1", "true"):
|
||||
return None
|
||||
|
||||
# Round 22 P1 #3: same diffusion-busy guard as ``_run_with_helper``.
|
||||
if _diffusion_image_model_busy():
|
||||
logger.info(
|
||||
"Skipping advisor GGUF while a diffusion image model is loaded/loading"
|
||||
)
|
||||
return None
|
||||
|
||||
repo = os.environ.get("UNSLOTH_HELPER_MODEL_REPO", DEFAULT_HELPER_MODEL_REPO)
|
||||
variant = os.environ.get(
|
||||
"UNSLOTH_HELPER_MODEL_VARIANT", DEFAULT_HELPER_MODEL_VARIANT
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue