[FunPack] K/V conditioning patch: Inactive | Non-LTX model loaded
[FunPack] Chunk cache: Active | 22 chunks, 4 steps each, consistency
`feature(tag, name, active, reason)` is the shape, and `skipped` renders
identically — a deliberate skip and an inactive feature are the same fact and
should not have two spellings. `failed` becomes `Name: Failed | <effect>`,
keeping the effect rather than the exception text: "that explains what I am
looking at" is the half worth the line, and an exception message rarely says it.
Everything funpack_log prints is now cut at the first sentence end, so the call
sites keep their reasoning without spending it on every run. SENTENCE ENDS ONLY
— an em-dash introduces the reason as often as the essay, and a renderer that
guesses wrong eats the one part of the line worth reading. A single long
sentence is hard-trimmed instead of left to run.
FUNPACK_LOG=verbose (or set_verbose) prints the full text, exception included.
Converted to the new shape: the chunk cache, region locks, interior pins, the
taste push, the token-refiner edit, AdaLN gains, rating-learned gains, phrase
emphasis, the K/V conditioning patch, the scene split, Bounded Attention,
per-block LoRA weights, LTX enhancements. The rest of the log gets the trim for
free and keeps its own phrasing until converted.
Tests: 1815 python (+9), 68 JS.
94 lines
3.7 KiB
Python
94 lines
3.7 KiB
Python
"""Per-block LoRA application is driven by the adapter's deltas, not by the architecture.
|
|
|
|
It used to run only for image_model in {ltxv, ltxav} and only for keys named
|
|
`transformer_blocks.N.`, so every other model got the global fallback without anything ever
|
|
looking at its weights. MiniMax H3 names its stack `blocks.N.`, which the old pattern did
|
|
not match at all — per-block would have been a silent no-op there even with the gate open.
|
|
"""
|
|
import sys
|
|
|
|
import pytest
|
|
import torch
|
|
|
|
sys.path.insert(0, ".")
|
|
|
|
|
|
@pytest.fixture
|
|
def mm():
|
|
import model_management
|
|
return model_management
|
|
|
|
|
|
@pytest.mark.parametrize("key,expected", [
|
|
("diffusion_model.transformer_blocks.7.attn.to_q.weight", 7), # LTX
|
|
("diffusion_model.blocks.7.attn.qkv.weight", 7), # MiniMax H3
|
|
("diffusion_model.double_blocks.3.img_attn.qkv.weight", 3),
|
|
("diffusion_model.single_blocks.11.linear1.weight", 11),
|
|
("model.layers.4.self_attn.q_proj.weight", 4),
|
|
("diffusion_model.final_layer.weight", None),
|
|
])
|
|
def test_a_numbered_block_is_found_whatever_the_stack_is_called(mm, key, expected):
|
|
assert mm.transformer_block_index(key) == expected
|
|
|
|
|
|
def test_transformer_blocks_is_not_read_as_blocks(mm):
|
|
"""`transformer_blocks` must match as itself, not as a suffix of the generic name."""
|
|
assert mm.block_container_name(
|
|
"diffusion_model.transformer_blocks.2.ff.net.0.proj.weight") == "transformer_blocks"
|
|
|
|
|
|
def test_ltx_anchors_are_unchanged_on_a_28_block_model(mm):
|
|
"""PAG/STG's 14 and 19 were measured on LTX's 28 blocks. Expressing them as positions
|
|
must resolve back to exactly those two blocks, or this generalisation silently retunes
|
|
the model it was derived from."""
|
|
assert mm.semantic_anchor_blocks(range(28)) == frozenset({14, 19})
|
|
|
|
|
|
def test_anchors_scale_to_a_deeper_stack(mm):
|
|
"""A 48-block model gets the proportionally equivalent blocks, not 14 and 19."""
|
|
anchors = sorted(mm.semantic_anchor_blocks(range(48)))
|
|
assert anchors == [round(14 / 27 * 47), round(19 / 27 * 47)]
|
|
assert anchors != [14, 19]
|
|
|
|
|
|
def test_a_stack_too_short_to_compare_yields_what_it_has(mm):
|
|
assert mm.semantic_anchor_blocks([0]) == frozenset({0})
|
|
assert mm.semantic_anchor_blocks([]) == frozenset()
|
|
|
|
|
|
class _Entry(dict):
|
|
pass
|
|
|
|
|
|
def test_per_block_is_no_longer_gated_on_the_model(mm):
|
|
"""The gate is 'did the user ask for it'. Whether it can actually run is discovered
|
|
from the deltas afterwards."""
|
|
loader = mm.FunPackLoraLoader()
|
|
stack = {"mode": "minimax_h3", "per_block": True, "loras": []}
|
|
assert loader._per_block_supported(object(), stack, _Entry()) is True
|
|
assert loader._per_block_supported(object(), {"mode": "ltx2", "per_block": False},
|
|
_Entry()) is False
|
|
|
|
|
|
def _deltas(prefix, n, energy=1.0):
|
|
return {f"diffusion_model.{prefix}.{i}.attn.qkv.weight": torch.full((4, 4), energy * (i + 1))
|
|
for i in range(n)}
|
|
|
|
|
|
def test_an_h3_style_stack_gets_a_real_block_profile(mm):
|
|
"""The end of the change that matters: `blocks.N.` deltas now produce per-block scales
|
|
instead of falling through to global."""
|
|
loader = mm.FunPackLoraLoader()
|
|
template = loader._block_profile_template(_deltas("blocks", 8))
|
|
assert template is not None
|
|
assert template["block_count"] == 8
|
|
assert len(template["base_scales"]) == 8
|
|
|
|
|
|
def test_two_block_stacks_decline_rather_than_collide(mm, capsys):
|
|
"""double_blocks.0 and single_blocks.0 are different blocks with the same index."""
|
|
loader = mm.FunPackLoraLoader()
|
|
mixed = {**_deltas("double_blocks", 4), **_deltas("single_blocks", 4)}
|
|
assert loader._block_profile_template(mixed) is None
|
|
assert "Per-block LoRA weights: Inactive" in capsys.readouterr().out
|