mirror of
https://github.com/vladmandic/automatic
synced 2026-09-19 09:14:35 +02:00
feat(lora): support the lycoris_ prefix in every native adapter arch
The lycoris_ save format is arch-independent: LyCORIS standalone wraps the loaded diffusers model and emits the wrapped module path with dots as underscores, so verbatim passthrough is correct for any arch. Only flux2 handled it; zimage, chroma, ernie and krea2 reported such files as not loaded. - add lycoris_ to KNOWN_PREFIXES_DEFAULT and PASSTHROUGH_PREFIXES_DEFAULT - drop flux2's per-arch prefix append and resolve_targets branch - add lycoris_ to ANIMA_PREFIXES (anima replaces the default tuple); network_prefix_for already routes it to the transformer namespace - cover the passthrough with a zimage loader test
This commit is contained in:
@@ -48,7 +48,11 @@ from modules.lora import lora_common as l
|
||||
# vendor-specific naming conventions. ``lora_transformer_`` is not a vendor
|
||||
# format but sdnext's own internal transformer namespace; files already saved
|
||||
# in it (e.g. OneTrainer) pass through verbatim, see :func:`resolve_group_targets`.
|
||||
KNOWN_PREFIXES_DEFAULT = ("diffusion_model.", "transformer.", "lora_unet_", "lora_transformer_")
|
||||
# ``lycoris_`` is the LyCORIS-standalone save format: the wrapped diffusers
|
||||
# module path with dots rendered as underscores. It is arch-independent by
|
||||
# construction (the wrapped layout equals the target layout), so it lives here
|
||||
# rather than in any arch's prefix list.
|
||||
KNOWN_PREFIXES_DEFAULT = ("diffusion_model.", "transformer.", "lora_unet_", "lora_transformer_", "lycoris_")
|
||||
|
||||
|
||||
# Sentinel ``prefix_used`` value emitted by :func:`parse_key` when a bare path
|
||||
@@ -67,9 +71,9 @@ NETWORK_PREFIX_DEFAULT = "lora_transformer_"
|
||||
|
||||
# Prefixes whose parsed ``base`` is already a network-key tail (``arch_prefix +
|
||||
# base.replace(".", "_")`` matches the stamped module name), so the loader binds
|
||||
# them directly with no per-arch rewrite. Arch-local already-resolved prefixes
|
||||
# (e.g. flux2's ``lycoris_``) stay in that arch's ``resolve_targets``.
|
||||
PASSTHROUGH_PREFIXES_DEFAULT = ("transformer.", BARE_DIFFUSERS_PREFIX_USED, "lora_transformer_")
|
||||
# them directly with no per-arch rewrite. ``lycoris_`` bases are already
|
||||
# underscored, so the loader's ``.replace(".", "_")`` is a no-op on them.
|
||||
PASSTHROUGH_PREFIXES_DEFAULT = ("transformer.", BARE_DIFFUSERS_PREFIX_USED, "lora_transformer_", "lycoris_")
|
||||
|
||||
|
||||
def _resolve_prefix(network_prefix, prefix_used):
|
||||
|
||||
@@ -51,6 +51,10 @@ ANIMA_PREFIXES = (
|
||||
"diffusion_model.",
|
||||
"lora_te_",
|
||||
"lora_unet_",
|
||||
# LyCORIS-standalone save format; resolved by the universal passthrough in
|
||||
# native_adapter.resolve_group_targets and routed to lora_transformer_ by
|
||||
# network_prefix_for's default arm.
|
||||
"lycoris_",
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -56,7 +56,7 @@ from modules.lora.native_adapter import ChunkSpec
|
||||
|
||||
# === Arch-specific prefix configuration ===
|
||||
|
||||
KNOWN_PREFIXES = native_adapter.KNOWN_PREFIXES_DEFAULT + ("lycoris_",)
|
||||
KNOWN_PREFIXES = native_adapter.KNOWN_PREFIXES_DEFAULT
|
||||
|
||||
BARE_FLUX_PREFIXES = (
|
||||
"single_blocks.", "double_blocks.", "img_in.", "txt_in.",
|
||||
@@ -183,25 +183,16 @@ def resolve_targets(prefix_used, base):
|
||||
"""Return ``[(diffusers_path, ChunkSpec | None), ...]`` for a parsed group key.
|
||||
|
||||
For ``lora_unet_`` prefix, applies ``KOHYA_SUFFIX_MAP`` then ``F2_*_MAP``.
|
||||
For BFL / bare-BFL, applies ``F2_*_MAP`` directly. ``lycoris_`` is an
|
||||
already-underscored diffusers path, returned verbatim. Unrecognized
|
||||
prefixes return an empty list.
|
||||
For BFL / bare-BFL, applies ``F2_*_MAP`` directly. Unrecognized prefixes
|
||||
return an empty list.
|
||||
|
||||
Universal passthrough prefixes are handled upstream by
|
||||
:func:`native_adapter.resolve_group_targets`.
|
||||
Universal passthrough prefixes (including ``lycoris_``) are handled
|
||||
upstream by :func:`native_adapter.resolve_group_targets`.
|
||||
"""
|
||||
if prefix_used == "lora_unet_":
|
||||
return _kohya_to_diffusers_targets(base)
|
||||
if prefix_used in (None, "diffusion_model."):
|
||||
return _bfl_to_diffusers_targets(base)
|
||||
if prefix_used == "lycoris_":
|
||||
# base is an already-underscored diffusers path (e.g.
|
||||
# 'transformer_blocks_0_attn_add_k_proj'). The caller's network_key
|
||||
# construction does base.replace('.', '_'); for already-underscored
|
||||
# paths that's a no-op, so the network_key matches the entry stamped
|
||||
# by lora_convert.assign_network_names_to_compvis_modules
|
||||
# (e.g. 'lora_transformer_transformer_blocks_0_attn_add_k_proj').
|
||||
return [(base, None)]
|
||||
return []
|
||||
|
||||
|
||||
|
||||
@@ -718,8 +718,9 @@ def test_parse_key_lycoris_prefix():
|
||||
got = F.parse_key(key, suffixes)
|
||||
assert got == expected, f'parse_key({key!r}) = {got}, expected {expected}'
|
||||
|
||||
# resolve_targets: the underscored path is returned verbatim (no chunk).
|
||||
targets = F.resolve_targets('lycoris_', 'transformer_blocks_0_attn_add_k_proj')
|
||||
# Resolution: lycoris_ is a universal passthrough handled upstream of the
|
||||
# arch resolver, so the underscored path is returned verbatim (no chunk).
|
||||
targets = F.native_adapter.resolve_group_targets(F.resolve_targets, 'lycoris_', 'transformer_blocks_0_attn_add_k_proj')
|
||||
assert targets == [('transformer_blocks_0_attn_add_k_proj', None)], f'targets={targets}'
|
||||
return True
|
||||
|
||||
|
||||
@@ -313,6 +313,24 @@ def sd_lokr_legacy_fused_qkv():
|
||||
}
|
||||
|
||||
|
||||
def sd_lokr_lycoris_style():
|
||||
"""LyCORIS-standalone LoKR (``lycoris_`` + underscored diffusers path).
|
||||
|
||||
The prefix is resolved by the universal passthrough in native_adapter, not
|
||||
by anything zimage-specific. ``to_out.0`` exercises the verbatim
|
||||
round-trip where a naive underscore-to-dot expansion would corrupt the
|
||||
ModuleList index.
|
||||
"""
|
||||
return {
|
||||
'lycoris_layers_0_attention_to_q.lokr_w1': torch.randn(LOKR_W1_DIM, LOKR_W1_DIM),
|
||||
'lycoris_layers_0_attention_to_q.lokr_w2': torch.randn(HIDDEN // LOKR_W1_DIM, HIDDEN // LOKR_W1_DIM),
|
||||
'lycoris_layers_0_attention_to_q.alpha': torch.tensor(float(LOKR_W1_DIM)),
|
||||
'lycoris_layers_1_attention_to_out_0.lokr_w1': torch.randn(LOKR_W1_DIM, LOKR_W1_DIM),
|
||||
'lycoris_layers_1_attention_to_out_0.lokr_w2': torch.randn(HIDDEN // LOKR_W1_DIM, HIDDEN // LOKR_W1_DIM),
|
||||
'lycoris_layers_1_attention_to_out_0.alpha': torch.tensor(float(LOKR_W1_DIM)),
|
||||
}
|
||||
|
||||
|
||||
def sd_loha_bfl_proj():
|
||||
"""LoHA on attention.to_out.0 (non-fused; LoHA on fused qkv is skipped by the loader)."""
|
||||
return {
|
||||
@@ -599,6 +617,20 @@ def test_lokr_legacy_fused_qkv_chunked():
|
||||
return True
|
||||
|
||||
|
||||
def test_lokr_lycoris_prefix_passthrough():
|
||||
"""lycoris_ keys load via the universal passthrough (hoisted, not zimage-specific)."""
|
||||
net = _load_via(Z.try_load_lokr, sd_lokr_lycoris_style())
|
||||
assert net is not None and len(net.modules) == 2, f'got {net.modules if net else None}'
|
||||
expected = {
|
||||
'lora_transformer_layers_0_attention_to_q',
|
||||
'lora_transformer_layers_1_attention_to_out_0',
|
||||
}
|
||||
assert set(net.modules) == expected, f'got {set(net.modules)}'
|
||||
for mod in net.modules.values():
|
||||
assert isinstance(mod, network_lokr.NetworkModuleLokr)
|
||||
return True
|
||||
|
||||
|
||||
def test_loha_bfl_proj():
|
||||
"""BFL LoHA on a non-fused proj target binds via NetworkModuleHada."""
|
||||
net = _load_via(Z.try_load_loha, sd_loha_bfl_proj())
|
||||
@@ -729,6 +761,7 @@ def run_tests():
|
||||
test_lora_dora_threading,
|
||||
test_lokr_bfl_adaln,
|
||||
test_lokr_legacy_fused_qkv_chunked,
|
||||
test_lokr_lycoris_prefix_passthrough,
|
||||
test_loha_bfl_proj,
|
||||
test_loha_legacy_fused_qkv_chunked,
|
||||
test_oft_lycoris_no_npe,
|
||||
|
||||
Reference in New Issue
Block a user