Leaf group offload hooked every Linear and Conv inside a decoder layer on its own, below the unit a transformers model declares in _no_split_modules. Qwen3.5 passes conv1d.weight to the causal-conv1d kernel without calling conv1d, so the weight stayed on cpu and the encode failed. Under leaf_level each declared unit of a transformers component now onloads as one group on its own forward, leaves outside the units keep their groups, and the stream prefetch tracer is registered again over the new groups.
network_backup_weights cloned the bias into a local and never stored it on the module, so network_apply_weights found no bias backup and refused every diff_b delta whenever the native path ran in backup mode (quantized models, select stacking, lora_fuse_native off). The test-lora-apply helpers stamped backups by hand and now go through network_backup_weights.
SD_SPARSE_KINDS=cond=image remaps a declared kind for every stream that
carries it, so a running server can A/B sparsified reference images from an
environment variable.
Flux 3 runs per-mode blocks over text, image and reference images
separately, then joint blocks over all three, so each forward attends at
three or four sequence lengths. The declaration follows the order measured
on both checkpoints: text, image, then one concatenated reference stream;
prompt padding is attended.
Each transformer class declares its streams and the stream order of every
block group that attends over a sequence; the class set it replaces read
txt_ids and img_ids and fit only single-sequence models. The reader
publishes one layout per group keyed by sequence length, the stage looks it
up by the length it is called with, and where two groups share a length the
one keeping more tokens dense serves both. The modular pre-hook reads the
same declaration, so modular denoisers without *_indices tensors publish
too. The stage logs each layout once per shape.
The request now sets their output size. sd_models.takes_condition_set (a declared count above 1) keeps an image list on these two models as one image per sample.
Each detection's crop is the condition image and the mask applies on paste; these models were rejected as not compatible. test/test-detailer-mode.py covers the verdict per model.
Inference-only PDD exports such as alibaba-pai's Qwen-Image-2.1-Fun-Acc-4Step carry no pdd_num_steps: the grid is pdd_sigmas in the metadata or in the pdd_config.json the release keeps beside the weights, and the file replaces norm weights outright. Those parameters are set with the heads, blended toward the loaded value by strength and restored on removal. The pin passes the grid's block boundaries as sigmas and turns off dynamic shifting, static shift and terminal stretch on that generation's scheduler, which would otherwise reshape them, and leaves num_timesteps alone where the pipeline exposes it as a read-only property. Qwen-Image 2.1 gets an ArchSpec and the PDD loader in its chain; a file with heads but no grid warns and loads its backbone only.
QwenImage21Transformer2DModel keeps attention and the SwiGLU split, while ComfyUI checkpoints fuse the SwiGLU input as img_mlp.gate_up with rows [gate_layer; proj], the layout ai-toolkit and musubi-tuner train against. pipelines/qwen/qwen21_lora.py splits that projection by row halves for LoRA, DoRA, LoKr and LoHA in dotted and kohya-flattened form and binds every other key verbatim; qwen21 joins allow_native and NATIVE_DISPATCH. The diffusers path loads none of the fused files and fails on transformer.-prefixed files that carry alpha tensors. The tests map every layout found on CivitAI and the Hub against the real class, check the split against the fused SwiGLU forward, and refuse Qwen-Image 1.x files.
/model-versions can still report a fresh upload by its storage key
(VAPA5E3KPBRRJZ2PMTCMW8WSE0.safetensors) while /models already carries
the uploader name, and it drops GGUF quantType; /models is also what
the UIs list from. The names route and id-based download therefore take
their file entries from /models whenever it answers, and keep the
version's own files only when it does not.
A suffix only adds what the CivitAI name lacks: the variant is skipped
when the name already spells that token (ideogram4INT8_...UncondINT8
stays as is, x_fp8 still gains -fp8_e4m3fn once the header names the
exact dtype), header-derived expert roles obey the same file-name guard
as title-derived ones (ideogram4_fp8Uncond no longer ends in -uncond),
and companions never take a role, since the text encoder of a
high-noise version is the same file the low-noise version ships.
A GGUF's variant is its quant, never metadata.fp, which is the dtype it
was quantized from (a Q4_K_M upload tagged fp8 was saved as -fp8). The
name route now reads the GGUF header the way it reads safetensors
headers: general.file_type gives the exact quant (Q4_K_M) and files
without it, such as Ideogram 4 GGUFs with an empty KV block, fall back
to the dominant tensor type (Q4_0). When the header cannot be read the
metadata or url quantType still applies; fp never does.
- peek_civitai gains a v2/v3 GGUF reader over a 1 MB ranged read, with
the same id-keyed cache and the quant token in the "quant" field
- peek_headers dispatches by extension, so .gguf siblings are probed
alongside .safetensors ones
Enso, the Gradio CivitAI tab and API callers share one naming rule in
modules/civitai/names_civitai.py. The variant token (metadata.fp,
quantType, or the quantType url parameter) is suffixed unconditionally
so names stay stable when a creator adds variants; dual-transformer
expert roles (Wan 2.2 A14B high/low noise, Ideogram 4 uncond) join it
from the version title or the file header; remaining collisions cascade
to the size class, then the file id. Header peeks upgrade a bare fp8
claim to the dtype-exact token.
- GET /sdapi/v2/civitai/version/{id}/names lists save name, route type,
variant and role per file
- POST /sdapi/v2/civitai/download takes version_id + file_id and
resolves url, name, sha256 and companion type; url + filename callers
are unchanged
- /model-versions drops GGUF quantType, so such versions take their
files from /models
- civitai_save_precision (Settings > Huggingface) drops the
unconditional suffix; roles stay and names remain unique per version
- header peek moves to peek_civitai.py; failed reads are never cached
- ui/civitai.ts passes file ids instead of computing names
- test/test-civitai-names.py replays recorded /model-versions, /models
and header responses under both toggle states
Cases: two images on img2img, an aligned first image with a different requested size, a batch size above the image count, a request over max_condition_images, and two control inputs. With --log each case also reads the Base log line for the image count and call size the pipeline received; a call sized from the first image returns the requested size with scrambled content.
The metadata cache keeps a failed read forever and --no-metadata returns
nothing, and in both cases the head loader returned None silently while
the backbone still applied. The grid is now read from the file's own
header when the cached metadata lacks it, and head-shaped tensors
without a grid log a warning.
The projection a parallel head replaces waits out of the module tree,
so nothing moves it, and a weight still viewing its checkpoint shard
keeps the whole shard mapped. The stash now clones the projection's
tensors.
Group hooks on the no-stream path and the on-demand hook return a
component to cpu through a device copy, so a memory-mapped text encoder
sits in memory twice, as the mapped file its never-run vision tower
keeps alive and as the copies, and every encode pays a device-to-host
transfer of unchanged weights. The engine now records the cpu tensors
at onload and hands them back at offload; a component moved by any
other path still takes the copy.
MiniMaxH3Scheduler counts the terminal sigma in num_inference_steps, so
Steps N ran N-1 evaluations while every other model runs N. The shim
hands the scheduler p.steps + 1, the slider starts at 1, and the PDD
pin records the evaluation count while passing the scheduler its grid
argument. Metadata written before this change counted grid points.
Shift is a property of the trained schedule, not of the step count, so
the tab sliders take absolute values, defaulting to the shipped 12 and
3. video_minimax resolves each request from the request value or the
scheduler config inside apply_overrides, which the tab, the api and
the still path all call, so a request without values lands on the
shipped schedule. The api maps sampler_shift onto the video schedule
and gains audio_shift. Applied values are recorded as Video shift and
Audio shift; the PDD pin records what it enforces.
Layer-wise extractions (FastH3) ship diff_b on every projection and
diff on the norms; final_layer.norm had no rename onto norm_out.norm,
so its residual went unmapped.
PDD files pair a backbone LoRA with the output projections repeated per
interval of a training grid; each step fuses the heads of its block
into one projection. network_pdd reads the grid from the metadata,
swaps a ParallelHead in for each projection, fuses from the scheduler's
step_index and pins the step count and shift while heads are
installed. The MiniMax loader declares which scheduler each head
follows.
The pruned transformer stores W @ P for each AdaLN projection against
the rank-8 basis in time_embedder.basis, so deltas trained at the
released width failed the shape check. try_load_lora gains an
adapt_weights hook and the MiniMax loader refits lora_down as down @ P,
which is exact.
save_cache handed writefile the live HashStore objects, so an add_hash
from another thread during the deep copy raised "dictionary changed
size during iteration" and dropped the save. The stores are now copied
under a lock that also covers the write, so a later save never lands
under an earlier snapshot.
Every caller writes a whole JSON document, and a plain open/write can
leave a torn or empty file when the process dies or another writer
overlaps, which readfile then returns as {}. Writes now go through the
temp file and replace unless atomic=False or the mode is append. A read
waits out an open that Windows refuses while a replace of the same name
is in flight, and an empty file is reported when the read is not
silent, since a zero-byte config.json otherwise resets settings without
a trace.
The fasteners lock is inter-process only. On Linux fcntl record locks
belong to the process, so two threads writing the same file were never
serialized and could tear it; on Windows they were, until the lock
switched itself off. Every writer of these files runs inside one
multi-threaded process, so that is the case that matters.
readfile(lock=True) and writefile now take a re-entrant lock per
normalized path, held from the snapshot through the replace so writes
to one path land in call order. Other processes are covered by the
atomic replace. A .lock file left next to a JSON file by the old lock
is removed the first time that path is used.
writefile(atomic=True) renamed the temp file while it was still open,
which Windows refuses, so every atomic save failed there and left a temp
file behind. The temp file is now created with mkstemp, closed before
os.replace and removed when the save fails; the replace retries briefly
when another handle holds the target, which Windows reports as a
permission error.
Both helpers removed the .lock file after releasing it. That removal
raced other holders on both platforms, and any failure switched locking
off for the whole process without a log line. The lock file now stays in
place; a failed release or a lock timeout is logged instead.
writefile deep-copied and validated the live object in Python, so a
concurrent insert into a shared dict raised "dictionary changed size
during iteration" and dropped the save. dict.copy and list.copy run
under the GIL, so the deep copy and the per-key validation now walk that
snapshot; nested containers remain the caller's responsibility.
test/test-json-helpers.py covers all three on Linux and Windows.
A checkpoint-name key survives unload, so a reload of the same checkpoint
never printed the per-module stats block again, even with a different
quantization. Each component now carries its own reported stamp: a task
switch rebuilds the pipe around the same modules and stays quiet, while a
reload or a component swap brings new modules and reports them.
The group offload functions live in sd_offload_group, sd_offload_utils and
sd_offload_state rather than modules.sd_offload, so the tests and their
monkeypatches now import and patch those modules directly.
The parser no longer takes reference-name prefixes to tell bare
reference keys from bare diffusers keys. Any bare key carries the
sentinel and the arch resolver renames what it knows and passes the rest
through. Flux2 keeps its list for file-format detection only.
A bare key that matches no known prefix is parsed with the
bare-diffusers sentinel and handed to the resolver instead of being
dropped at parse time. The per-arch lists of bare diffusers prefixes are
gone, and a path that names no live module counts as unmapped.
A ChunkSpec can reorder equal row blocks of the rows it selects, so an
arch declares a swapped SwiGLU projection on the target instead of
permuting the state dict first. Only the LoRA family applies it; the
others skip a reordered target with a warning.