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.
The reference fc1 is a fused [gate; value] SwiGLU projection and the
diffusers port stores [value; gate]. The native mapping did not swap the
halves, so gate and value deltas landed on each other's rows. The
mapping now also renames the standalone projections, reads a
metadata-only alpha, and accepts the musubi, peft dit and diffusers-named
layouts.
SDNQ codebook layers keep their Lloyd levels in the scale slot, so reading
scale.mean() as the grid step returned the levels' near-zero mean and sent
sub-step deltas to requantize, where the grid erases them. grid_step returns
the mean adjacent-level gap for those layers and the plain scale mean otherwise.
Anima 2.9B interleaves twelve new blocks among the 28 of Anima 1.0, so a block
index trained against 1.0 names a different block on the expanded model. Every
such key still resolves, since blocks 0 to 27 exist either way, so the
mismatch was silent. The Anima loader now shifts base-depth indices onto the
blocks that carry those weights, keyed by (base depth, expanded depth) and
applied only when the transformer is expanded and the LoRA stays inside the
base depth. Transformer keys move; llm_adapter and text encoder keys keep
their own numbering.
Anima-2.9B is a depth-expanded finetune of Anima 1.0 Base carrying 40
transformer blocks against the base repo's 28. The reference entry points at
the Diffusers conversion. Single-file releases load through the native loader:
TransformerSpec gains an infer_config hook, the Anima spec uses it to size
num_layers to the block indices in the file, and model_anima routes a
checkpoint-selected safetensors through the loader with the remaining
components from the base repo.
Two tables decide the native path: one says which architectures may take
it, the other says which loader they get. An entry in the second without
one in the first is a loader nothing can reach, and nothing checked that.
The error limiter halts a pass by raising, and nothing between the raise
and the caller put the model back. A halted pass left group offload hooks
stripped from every component the walk had reached, left a sequential
model on the cpu with offload disabled, and left the counters other
modules read describing the pass before it.
The epilogue moves into finish_pass under a finally, so the model returns
to its offload mode and the counters describe the pass that just ran. The
abort still reaches the caller.
Pass state is reset in one place in lora_sdnq now. Two of the six
accumulators were not being cleared at the start of a pass, and a stale
routed layer suppresses the fallback count for that layer next time.
The per-module walk carried four mechanisms inline, each repeating the
same tail: count the layer, stamp the pair that marks it current, advance
the bar, continue. Five copies of that tail and three of the backup probe
put the deepest arm nine levels in.
Each mechanism is now a function that either takes the layer or declines
to the next, and the walk reads as the four of them in order. The pass
state they share moves onto one object built before the walk starts, with
the accept tail, the stamp and the bar tick as its methods. That takes
network_activate from 218 lines to 55, none of it deeper than the module
loop.
Two shapes are deliberately not folded into that tail: the weight path
counts weights and bias separately and tracks what the module refused,
and the factor-strip restore stamps without counting. Hosting hands a
declined delta back rather than leaving it in a flag, so a pair of Nones
still reads as assembled and no layer is calculated twice.
The keys that mark a degradation as reported lived for the life of the
process, so a user who saw "flip=skipped weight=offloaded", changed the
offload mode and hit the same wall again was told nothing the second
time. Tie the set to the settings the warnings speak about: the stack
signature, the offload mode, the host rank and the checkpoint. Repeating
under one context still says it once.
The generic loader never offered a file to the boft type, so butterfly
OFT adapters reached the oft type instead, which claims any oft_blocks
key without checking its rank and then reads the block count as the lora
dim. Register boft ahead of oft; files with 3-d blocks still land on oft.
The nunchaku path replaced the loaded network list with the on-disk
entries it composed from, so reading a loaded network back hit an object
without the fields it expects: choosing the reported method reads
len(net.modules) and raised on every set change, costing that generation
its infotext and trigger tags. The adapter was already composed by then,
so the image was unaffected. Wrap the composed set in Network objects
and mutate the list in place.
The mechanism gate tests assert select_candidate declines under
requantize, and the apply-method hint names the cache option among
those the requantize choice disables.
The in-place weight installs, the promote-after-deactivate fuse
ordering, and the dynamo reset at model unload live in the shared
loader code; their regression pins belong in the campaign suite beside
the paths they protect.
<lora:name:1.0:lbw=VALUE> scales each targeted layer's delta by a slot of a
per-architecture block vector. VALUE is a preset name, a scalar, or a comma
vector; presets stretch onto the block count of the current model and the
a1111 17-slot and 12-slot layouts are accepted on sd and sdxl. The factor
enters through the module multiplier, so every apply path carries it: the
exact factor channel, hosting, requantize routing, dense stack combines and
select scoring.
- modules/lora/lora_blocks.py: slot classification from network_layer_mapping
(namespace-first, anchored chain prefixes), preset resolution reusing the
merge block-weight tables with BASE forced neutral, generated classic
segment names plus DOUBLE/SINGLE chain names, per-model memoization
- the raw spec stages through pending_config and promotes with the other
multipliers, keeping fuse removal consistent
- block weights join the activation signature, the per-module apply stamp
and the factor cache identity; entries without block weights keep their
existing signature bytes
- non-native load methods warn once and ignore the argument