Block map: a profile of how much each block can take; REINS, Q steer, Attention temperature and Attention explorer nudge their strength by it, a big setting is kept up to what the block takes

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Tw9fjW75aYB8UyVRZo6fCr
This commit is contained in:
Claude
2026-11-01 11:59:10 +00:00
parent a5c74d0065
commit f32aaac550
9 changed files with 686 additions and 8 deletions
+15
View File
@@ -189,6 +189,21 @@ def current_step(transformer_options):
return int(hit[0]), int(sched.shape[0]) - 1
def block_map_value(transformer_options, kind, block, value, blocks) -> float:
"""The feature's own `value` (its change from neutral: a temperature minus 1, a push strength) for `block`, nudged by the
Block map: spread between `blocks` by how much each can take, and softly limited so a very large setting is respected
but cannot push a block past what its profile says it handles. `value` as given with no map, a flat one, or an unmeasured kind."""
view = (transformer_options or {}).get("funui_block_map")
if view is None:
return float(value)
where = current_step(transformer_options)
frac = 0.5 if where is None else where[0] / max(1, where[1] - 1)
try:
return float(view.adjust(kind, block, frac, blocks, value))
except Exception: # noqa: BLE001 -- a bad map never costs the picture
return float(value)
# Set on the guider by Two stages: 1 while the small first stage runs, 2 while the full-size one does. A
# SAMPLER_SAMPLE wrapper runs once per stage and reads it (its `model_wrap` is the guider) to do its
# once-per-run work in stage 1 only.
@@ -284,6 +284,14 @@ def make_plan(rows, cond, signature, n_groups, focus, manual_spread=None, region
"unknown": sum(1 for s in sims if s is None)}
def followed(factors, options, index, blocks):
"""(temperature, prompt gain) for one block, nudged by the block map when this model has one: each change
from 1 is spread by how much that block can take at this point of the run and softly limited."""
if (options or {}).get("funui_block_map") is None:
return factors
return tuple(1.0 + dit_hooks.block_map_value(options, "attention", index, v - 1.0, blocks) for v in factors)
def _prompt_of(guider):
try:
p = guider.conds["positive"][0]["cross_attn"]
@@ -403,7 +411,7 @@ def install(patcher, values, key):
if f is None or (f == (1.0, 1.0) and not live["region_wanted"]):
return extra["original_block"](args)
to = dict(args.get("transformer_options") or {})
to[_SCOPE] = f
to[_SCOPE] = followed(f, to, index, blocks)
return extra["original_block"]({**args, "transformer_options": to})
return scoped
@@ -387,3 +387,17 @@ def test_with_the_sound_kept_the_sound_is_left_alone_and_the_log_says_so(tiny_h3
assert any("sound left alone in" in x for x in messages)
drawn = [x for x in messages if "clip regions looser" in x]
assert drawn and not any("end of the clip, sound" in x for x in drawn) # the sound is never among the changed regions
def test_the_block_map_spreads_the_change_between_blocks_around_one():
from modules.sampling.attention_bandit import followed
from modules.sampling.block_map import measure
grid = measure.groups(10)
data = {"blocks": 10, "groups": grid, "points": list(measure.POINTS),
"attention": {"leverage": [[1.0] * 3, [4.0] * 3], "fragility": [[0.1] * 3, [0.1] * 3]}}
options = {"funui_block_map": measure.MapView(data), "sample_sigmas": torch.linspace(1, 0, 5), "sigmas": torch.tensor([0.5])}
blocks = [1, 2, 7, 8]
low, high = followed((1.2, 1.0), options, 1, blocks), followed((1.2, 1.0), options, 7, blocks)
assert low[0] > 1.2 > high[0] > 1.0 and low[1] == high[1] == 1.0 # the sharper change is larger where the block takes it
assert followed((1.2, 1.0), {}, 1, blocks) == (1.2, 1.0) # no map: as set
assert followed((1.0, 1.0), options, 1, blocks) == (1.0, 1.0)
@@ -64,13 +64,16 @@ def install(patcher, values, key):
# The widget floor keeps this above zero; the clamp is for API callers.
temperature = max(1.0 + amount, 0.05)
def scoped(args, extra):
to = dict(args.get("transformer_options") or {})
to[_SCOPE] = temperature
return extra["original_block"]({**args, "transformer_options": to})
def scoped_for(index):
def scoped(args, extra):
to = dict(args.get("transformer_options") or {})
# The Block map (when this model has a profile) spreads the change between the blocks, 1 on average.
to[_SCOPE] = max(1.0 + dit_hooks.block_map_value(to, "attention", index, temperature - 1.0, blocks), 0.05)
return extra["original_block"]({**args, "transformer_options": to})
return scoped
for index in blocks:
dit_hooks.add_block_hook(patcher, key, index, scoped)
dit_hooks.add_block_hook(patcher, key, index, scoped_for(index))
def override(func, q, k, v, *args, **kwargs):
t = (kwargs.get("transformer_options") or {}).get(_SCOPE)
+174
View File
@@ -0,0 +1,174 @@
"""Block map: where in the model, and how hard, a per-block feature should act.
A profile run (switched on from Settings > Refinement & Taste, off by default) nudges one group of
blocks at a time and measures how far the picture guess moves and how much of that move is fine-detail
noise (see measure.py). It costs extra model calls on three steps of one run, then writes a map for this
model. Features that act on chosen blocks (Attention explorer, REINS, ...) read the map through the
model's transformer_options and scale their strength block by block, around 1 on average, so a strength
setting keeps its meaning. With no map, or a flat one, every multiplier is 1 and nothing changes.
"""
from comfy.patcher_extension import WrappersMP
from ..._core import dit_hooks, log, streams
from . import measure
ID = "block_map"
TITLE = "Block map"
MOUNT = "generation.sampling"
STAGE = "post"
CATEGORY = "sampling"
STATUS = "experimental"
REQUIRES = ["dit_block_hooks"]
# Innermost, like the other measurement modules: only real forwards are nudged.
AFTER = ["video_detail", "late_branch", "score_slider", "sharpen"]
PUBLISHED = "funui_block_map"
SETTINGS = {
"follow": {
"type": "bool", "default": True,
"label": "Follow the block map",
"hint": "Per-block features spread their strength by how much each block can take, from a profile of this model. "
"Nothing changes until a profile has been made.",
},
}
def _say(message):
log.once(f"{ID}:{message}", log.ALERT, "FunUI Block map", message)
def install(patcher, values, key):
n = dit_hooks.block_count(patcher)
if n == 0:
return None
view = None
data = measure.load(n) if values.get("follow", True) else None
if data:
try:
view = measure.MapView(data)
except (KeyError, TypeError, ValueError):
_say("the saved map for this model is unreadable and is not used")
dit_hooks.publish(patcher, PUBLISHED, view)
note = []
if view is not None:
for kind in measure.KINDS:
if view.has(kind):
note.append(f"{kind}: " + ("flat, no effect" if view.flat(kind) else "in use"))
if not measure.enabled():
measure.problem = None
return ("block map " + ", ".join(note)) if note else None
measure.problem = None
live = {"state": measure.Probe(n)}
def hook_for(block):
def hook(args, extra):
return measure.block_hook(live["state"], block)(args, extra)
return hook
for b in range(n):
dit_hooks.add_block_hook(patcher, key, b, hook_for(b))
def attention(func, *a, **kw):
return measure.attention_override(live["state"])(func, *a, **kw)
dit_hooks.add_attention_override(patcher, key, attention)
dit_hooks.without_compiler(patcher, key)
def outer(executor, *args, **kwargs):
live["state"] = measure.Probe(n)
try:
return executor(*args, **kwargs)
finally:
res = measure.result(live["state"])
if res is not None:
measure.save(res)
log.once(f"{ID}:saved:{n}", log.INFO, "FunUI Block map",
f"Active | profile of {n} blocks saved; later runs follow it")
else:
measure.problem = "the profile run measured nothing: the model's picture could not be read"
log.once(f"{ID}:none", log.INFO, "FunUI Block map", "Inactive | " + measure.problem)
patcher.add_wrapper_with_key(WrappersMP.OUTER_SAMPLE, key, outer)
def apply_model(executor, x, t, *args, **kwargs):
out = executor(x, t, *args, **kwargs)
named = streams.model_args(args, kwargs)
to = named.get("transformer_options") or {}
st = live["state"]
where = dit_hooks.current_step(to)
if where is None or dit_hooks.probing(to) or st.kind is not None:
return out
point = measure.point_of(*where)
if point is None or point in st.done:
return out
st.done.add(point)
base = streams.video_of(out, named)
if base is None:
return out
video = base[0]
st.size = [int(video.shape[-2]), int(video.shape[-1])]
if point == min(st.done):
log.once(f"{ID}:cost", log.INFO, "FunUI Block map",
f"profiling: {2 * len(measure.groups(n))} extra model calls on each of {len(measure.POINTS)} steps")
probe_args, probe_kwargs = streams.with_options(args, kwargs, {**to, dit_hooks.PROBE: True})
for kind in measure.KINDS:
for g, span in enumerate(measure.groups(n)):
st.kind, st.group, st.block = kind, span, 0
try:
probed = streams.video_of(executor(x, t, *probe_args, **probe_kwargs), named)
if probed is not None:
st.cells[(kind, g, point)] = measure.response(video, probed[0])
except Exception as exc: # noqa: BLE001 -- a probe never costs the step
_say(f"a probe pass failed ({type(exc).__name__}: {exc}); that cell is not measured")
finally:
st.kind = None
return out
patcher.add_wrapper_with_key(WrappersMP.APPLY_MODEL, key, apply_model)
return "profiling blocks on this run (switched on in Settings > Refinement & Taste)"
def routes(table, base, web):
def view(n=None):
data = measure.load(n) if n else None
return {"enabled": measure.enabled(), "problem": measure.problem,
"maps": sorted(p.name for p in measure.FOLDER.glob("*_blocks.json")) if measure.FOLDER.exists() else []}
@table.get(base + "/status")
async def _status(_req):
return web.json_response(view())
@table.post(base + "/enabled")
async def _enabled(req):
try:
body = await req.json()
except Exception: # noqa: BLE001
body = {}
measure.set_enabled((body or {}).get("enabled") is True)
return web.json_response(view())
@table.post(base + "/clear")
async def _clear(_req):
measure.clear()
return web.json_response(view())
@table.get(base + "/export/{blocks}")
async def _export(req):
try:
data = measure.load(int(req.match_info["blocks"]))
except ValueError:
data = None
if data is None:
return web.json_response({"why": "no map for that block count"}, status=404)
return web.json_response(data, headers={"Content-Disposition": 'attachment; filename="block_map.json"'})
def cache_key():
return "on" if measure.enabled() else "off"
PROVIDES = {"modifier": install, "routes": routes, "cache_key": cache_key}
+267
View File
@@ -0,0 +1,267 @@
"""The block map's maths and its file: what a profile run measures, and how a feature reads it.
A profile run nudges one group of blocks at a time, in one of two ways, and measures how far the model's
picture guess moves:
* leverage: the size of that move, relative to the picture and to the size of the nudge. A block with a
big leverage changes the picture a lot for a small nudge.
* fragility: the share of the move that sits in the finest detail (what is left after a 3x3 blur). That
is the share that looks like noise or damaged edges rather than a change of content.
Two kinds of nudge, because blocks do not react alike to both: "attention" (the attention query scaled,
what Attention explorer and the temperature features do) and "residual" (the block's own push scaled,
what REINS, Block repeat and steering do in effect).
A feature asks `multiplier(kind, block, fraction_of_run, blocks)` and gets a number around 1: how much
more or less than its set strength to use in that block at that point of the run. The effect share a
block gets is its quality, (1 - fragility)^2, and the nudge that produces that share is the share over
the leverage, so the picture change goes where it is clean and the nudge is small where leverage is big.
Averaged over the blocks the feature uses it is 1, so a strength slider keeps its meaning.
"""
import json
import math
import time
import torch
import torch.nn.functional as F
from ..._core import config
SWITCH = config.ROOT / "block_map.enabled"
FOLDER = config.ROOT / "block_map"
KINDS = ("attention", "residual")
DELTA = 0.1 # the size of a probe nudge
GROUP_SIZE = 5 # blocks nudged together
POINTS = (0.1, 0.45, 0.8) # where in the run the profile is taken (fraction of the steps)
LOW, HIGH = 0.25, 3.0 # a multiplier stays inside these
REFERENCE = 0.5 # a nudge the typical block is taken to handle without harm ...
HEADROOM = 3.0 # ... times this is the most picture change any block is let produce
FLAT = 0.10 # leverage spread under this, and fragility spread under 0.05: nothing to aim at
problem = None # why nothing was measured, for the panel
def enabled() -> bool:
try:
return SWITCH.read_text().strip() == "1"
except OSError:
return False
def set_enabled(on: bool) -> bool:
SWITCH.parent.mkdir(parents=True, exist_ok=True)
SWITCH.write_text("1" if on else "0")
return bool(on)
def groups(n):
"""[(first, stop)] blocks per probe group."""
return [(a, min(n, a + GROUP_SIZE)) for a in range(0, n, GROUP_SIZE)]
def point_of(step, steps):
"""Which profile point this step is, or None. The first step at or after each point's place in the run."""
if steps < 2:
return None
for i, p in enumerate(POINTS):
if step == min(steps - 1, int(round(p * (steps - 1)))):
return i
return None
def path_of(n):
return FOLDER / f"{int(n)}_blocks.json"
def response(base, probed, delta=DELTA):
"""-> (leverage, fragility) of one probe. Both 5-D pictures [B, C, T, H, W]."""
d = (probed.float() - base.float())
ref = base.float()
size = float(d.norm())
lev = size / (max(float(ref.norm()), 1e-8) * delta)
if size <= 1e-12 or d.dim() != 5:
return lev, 0.0
b, c, t, h, w = d.shape
x = d.reshape(b * c * t, 1, h, w)
low = F.avg_pool2d(x, 3, 1, 1, count_include_pad=False)
frag = min(1.0, max(0.0, float((x - low).norm() / x.norm().clamp(min=1e-12)) ** 2))
return lev, frag
class Probe:
"""What the hooks read: which nudge is on right now, and which block is running."""
def __init__(self, n):
self.n = n
self.kind = None # "attention" / "residual" while a probe pass runs
self.group = None # (first, stop)
self.block = 0
self.done = set()
self.cells = {} # (kind, group index, point) -> (leverage, fragility)
self.size = None # (H, W) of the picture latent, for the record
def active(self):
return self.kind is not None and self.group is not None and self.group[0] <= self.block < self.group[1]
def attention_override(state):
def override(func, q, k, v, heads, *args, **kw):
if state.kind == "attention" and state.active() and torch.is_tensor(q):
q = q * (1.0 + DELTA)
return func(q, k, v, heads, *args, **kw)
return override
def block_hook(state, block):
def hook(args, extra):
if state.kind is None:
return extra["original_block"](args)
state.block = block
before = args.get("img")
if state.kind == "residual" and state.active() and torch.is_tensor(before):
before = before.clone() # a block may add to its input in place: keep the input as it was
out = extra["original_block"](args)
if state.kind == "residual" and state.active() and torch.is_tensor(before):
after = out.get("img") if isinstance(out, dict) else None
if torch.is_tensor(after) and after.shape == before.shape:
out = {**out, "img": before + (1.0 + DELTA) * (after - before)}
return out
return hook
def result(state):
"""The finished profile as a plain dict, or None when no cell was measured."""
if not state.cells:
return None
grid = groups(state.n)
out = {"when": time.strftime("%Y-%m-%d %H:%M:%S"), "blocks": state.n, "groups": grid,
"points": list(POINTS), "size": state.size, "delta": DELTA}
for kind in KINDS:
cells = [[state.cells.get((kind, g, p)) for p in range(len(POINTS))] for g in range(len(grid))]
if any(c is not None for row in cells for c in row):
out[kind] = {"leverage": [[None if c is None else c[0] for c in row] for row in cells],
"fragility": [[None if c is None else c[1] for c in row] for row in cells]}
return out
def save(res):
FOLDER.mkdir(parents=True, exist_ok=True)
path = path_of(res["blocks"])
tmp = path.with_suffix(".tmp")
tmp.write_text(json.dumps(res))
tmp.replace(path)
def load(n):
try:
return json.loads(path_of(n).read_text())
except (OSError, ValueError):
return None
def clear(n=None):
paths = [path_of(n)] if n is not None else list(FOLDER.glob("*_blocks.json")) if FOLDER.exists() else []
for p in paths:
try:
p.unlink()
except OSError:
pass
def _interp(xs, ys, x):
"""Piecewise linear through the points that have a value; flat outside them."""
pts = [(a, b) for a, b in zip(xs, ys) if b is not None]
if not pts:
return None
if x <= pts[0][0]:
return pts[0][1]
for (x0, y0), (x1, y1) in zip(pts, pts[1:]):
if x <= x1:
return y0 + (y1 - y0) * (x - x0) / max(1e-9, x1 - x0)
return pts[-1][1]
class MapView:
"""A saved profile, asked for multipliers."""
def __init__(self, data):
self.data = data
self.n = int(data["blocks"])
self.centres = [(a + b - 1) / 2.0 for a, b in data["groups"]]
self._cache = {}
self._memo = {}
def has(self, kind):
return kind in self.data
def _fields(self, kind, frac):
"""(leverage, fragility) for every block at this fraction of the run."""
key = (kind, round(float(frac) * 20))
if key in self._cache:
return self._cache[key]
d, pts = self.data[kind], self.data["points"]
lev_g, frg_g = [], []
for row_l, row_f in zip(d["leverage"], d["fragility"]):
lev_g.append(_interp(pts, row_l, frac))
frg_g.append(_interp(pts, row_f, frac))
lev = [_interp(self.centres, lev_g, float(b)) for b in range(self.n)]
frg = [_interp(self.centres, frg_g, float(b)) for b in range(self.n)]
self._cache[key] = (lev, frg)
return lev, frg
def flat(self, kind):
lev, frg = self._fields(kind, 0.5)
lev = [v for v in lev if v is not None]
frg = [v for v in frg if v is not None]
if not lev or not frg:
return True
mean = sum(lev) / len(lev)
return (max(lev) - min(lev)) < FLAT * max(mean, 1e-9) and (max(frg) - min(frg)) < 0.05
def multipliers(self, kind, frac, blocks):
"""{block: multiplier} for the blocks a feature uses, mean 1; all 1 when the profile is flat or missing."""
blocks = [int(b) for b in blocks]
memo = (kind, round(float(frac) * 20), tuple(blocks))
if memo in self._memo:
return self._memo[memo]
self._memo[memo] = self._multipliers(kind, frac, blocks)
return self._memo[memo]
def _multipliers(self, kind, frac, blocks):
if not blocks or not self.has(kind) or len(blocks) == 1 or self.flat(kind):
return {b: 1.0 for b in blocks}
lev, frg = self._fields(kind, frac)
raw = {}
for b in blocks:
if b >= self.n or lev[b] is None or frg[b] is None:
raw[b] = None
continue
raw[b] = max(0.05, 1.0 - frg[b]) ** 2 / max(lev[b], 1e-6)
known = [v for v in raw.values() if v is not None]
if not known:
return {b: 1.0 for b in blocks}
mean = sum(known) / len(known)
return {b: (1.0 if v is None else min(HIGH, max(LOW, v / mean))) for b, v in raw.items()}
def adjust(self, kind, block, frac, blocks, value):
"""The feature's own `value` (its change from neutral: a temperature minus 1, a strength) for this block:
spread by `multipliers`, then softly limited so no block is pushed far past what its profile says it can
take. The limit is the picture change a typical block makes from REFERENCE times HEADROOM, divided by this
block's leverage and its fragility, so a setting of 10 is respected up to that limit and not beyond it.
A flat or missing profile returns `value` as it came."""
value = float(value)
if value == 0.0 or not self.has(kind) or self.flat(kind):
return value
m = self.multipliers(kind, frac, blocks).get(int(block), 1.0)
lev, frg = self._fields(kind, frac)
b = int(block)
known = sorted(v for v in lev if v is not None and v > 0)
if b >= self.n or lev[b] is None or frg[b] is None or not known:
return value * m
cap = REFERENCE * HEADROOM * known[len(known) // 2] / (max(lev[b], 1e-6) * (1.0 + 2.0 * frg[b]))
x = abs(value) * m
return math.copysign(cap * math.tanh(x / cap), value)
def multiplier(self, kind, block, frac, blocks):
return self.multipliers(kind, frac, blocks).get(int(block), 1.0)
@@ -0,0 +1,194 @@
"""The block map: the maths on constructed profiles, and a profile run on the real tiny H3."""
import pytest
import torch
from modules.sampling.block_map import measure
def _data(n=10, lev=None, frg=None):
grid = measure.groups(n)
g = len(grid)
lev = lev or [[1.0] * 3 for _ in range(g)]
frg = frg or [[0.1] * 3 for _ in range(g)]
return {"blocks": n, "groups": grid, "points": list(measure.POINTS),
"attention": {"leverage": lev, "fragility": frg}}
def test_a_flat_profile_changes_nothing():
view = measure.MapView(_data())
assert view.flat("attention")
assert view.multipliers("attention", 0.5, [2, 3, 7]) == {2: 1.0, 3: 1.0, 7: 1.0}
def test_a_missing_kind_or_a_single_block_changes_nothing():
view = measure.MapView(_data())
assert view.multipliers("residual", 0.5, [2, 3]) == {2: 1.0, 3: 1.0}
assert measure.MapView(_data(lev=[[1.0] * 3, [3.0] * 3])).multipliers("attention", 0.5, [4]) == {4: 1.0}
def test_nudges_are_smaller_where_leverage_is_big_and_smaller_where_detail_is_fragile():
lev = [[1.0] * 3, [4.0] * 3] # blocks 5-9 move the picture four times as much
frg = [[0.1] * 3, [0.1] * 3]
m = measure.MapView(_data(lev=lev, frg=frg)).multipliers("attention", 0.5, [1, 2, 7, 8])
assert m[1] > m[7] and m[2] > m[8]
assert sum(m.values()) / 4 == pytest.approx(1.0, abs=0.35) # about 1 on average (the clamp may bend it)
lev = [[1.0] * 3, [1.0] * 3]
frg = [[0.0] * 3, [0.8] * 3] # blocks 5-9 turn their move into fine noise
m = measure.MapView(_data(lev=lev, frg=frg)).multipliers("attention", 0.5, [1, 2, 7, 8])
assert m[1] > m[7]
def test_multipliers_stay_inside_their_limits_and_average_one_before_clamping():
lev = [[1.0] * 3, [1000.0] * 3]
m = measure.MapView(_data(lev=lev)).multipliers("attention", 0.5, [0, 1, 8, 9])
assert all(measure.LOW <= v <= measure.HIGH for v in m.values())
def test_response_reads_the_size_of_the_move_and_how_much_of_it_is_fine_detail():
base = torch.randn(1, 4, 2, 8, 8)
smooth = torch.ones_like(base) * 0.01
noisy = torch.randn_like(base) * 0.01
lev_s, frag_s = measure.response(base, base + smooth)
lev_n, frag_n = measure.response(base, base + noisy)
assert lev_s == pytest.approx(lev_n, rel=0.5) and frag_s < 0.05 < 0.5 < frag_n
assert measure.response(base, base) == (0.0, 0.0)
def test_response_follows_the_picture_dtype_and_device():
base = torch.randn(1, 2, 2, 6, 6, dtype=torch.bfloat16)
lev, frag = measure.response(base, base * 1.01)
assert lev > 0 and 0.0 <= frag <= 1.0
def test_points_are_hit_once_each_and_a_one_step_run_has_none():
hits = [measure.point_of(i, 8) for i in range(8)]
assert [h for h in hits if h is not None] == [0, 1, 2]
assert all(measure.point_of(i, 1) is None for i in range(1))
# --- the profile run, on the real tiny H3 -------------------------------------------------------------
@pytest.fixture
def root(tmp_path, monkeypatch):
monkeypatch.setattr(measure, "SWITCH", tmp_path / "switch")
monkeypatch.setattr(measure, "FOLDER", tmp_path / "maps")
def _load(tiny, settings=None):
from comfy.patcher_extension import WrappersMP
from modules.sampling.modifiers.nodes import FunUILoadModifiers
patched, status = FunUILoadModifiers.execute(tiny.patcher, settings or {}).result
wraps = [w for ws in patched.wrappers.get(WrappersMP.APPLY_MODEL, {}).values() for w in ws]
outer = [w for ws in patched.wrappers.get(WrappersMP.OUTER_SAMPLE, {}).values() for w in ws]
return patched, (wraps[-1] if wraps else None), outer
def _run(tiny, patched, wrap, outer, steps=4):
from conftest import packed_av
x0, shapes = packed_av(tiny.video, tiny.audio)
seen = []
def executor(x, t, c_concat=None, c_crossattn=None, control=None, transformer_options=None, **kw):
to = dict(transformer_options or {})
seen.append(to)
video, audio = tiny.run(patched, sigma=float(t.max()),
options={k: v for k, v in to.items() if k == "funui_probe"})
return packed_av(video, audio)[0]
s = torch.linspace(1.0, 0.0, steps + 1)
call = lambda: [wrap(executor, x0, s[i:i + 1].reshape(1, 1), None, None, None,
{"sample_sigmas": s, "sigmas": s[i:i + 1], "cond_or_uncond": [0]},
latent_shapes=shapes) for i in range(steps)]
for o in reversed(outer):
call = (lambda w, inner: (lambda: w(lambda: inner())))(o, call)
return call(), seen
def test_off_it_installs_no_hooks_and_publishes_no_map(tiny_h3, root):
patched, wrap, outer = _load(tiny_h3)
assert wrap is None and outer == []
assert patched.model_options["transformer_options"].get("funui_block_map") is None
def test_a_profile_run_writes_a_map_and_returns_the_untouched_answers(tiny_h3, root):
patched, _w, outer = _load(tiny_h3)
outs0, _ = _run(tiny_h3, patched, lambda ex, *a, **k: ex(*a, **k), outer)
measure.set_enabled(True)
patched, wrap, outer = _load(tiny_h3)
outs, seen = _run(tiny_h3, patched, wrap, outer)
assert all(torch.equal(a, b) for a, b in zip(outs, outs0))
n = len(tiny_h3.patcher.model.diffusion_model.blocks)
per_point = 2 * len(measure.groups(n))
assert len(seen) == 4 + 3 * per_point
assert [bool(t.get("funui_probe")) for t in seen][:1 + per_point] == [False] + [True] * per_point
data = measure.load(n)
assert data["blocks"] == n and set(measure.KINDS) <= set(data)
for kind in measure.KINDS:
lev = data[kind]["leverage"]
assert len(lev) == len(measure.groups(n)) and all(v is not None and v > 0 for row in lev for v in row)
def test_a_later_run_publishes_the_map_and_follow_off_hides_it(tiny_h3, root):
measure.set_enabled(True)
patched, wrap, outer = _load(tiny_h3)
_run(tiny_h3, patched, wrap, outer)
measure.set_enabled(False)
patched, _w, _o = _load(tiny_h3)
view = patched.model_options["transformer_options"]["funui_block_map"]
assert view is not None and view.n == len(tiny_h3.patcher.model.diffusion_model.blocks)
patched, _w, _o = _load(tiny_h3, {"block_map": {"follow": False}})
assert patched.model_options["transformer_options"].get("funui_block_map") is None
def test_a_failing_probe_pass_costs_nothing(tiny_h3, root):
from conftest import packed_av
measure.set_enabled(True)
patched, wrap, outer = _load(tiny_h3)
x0, shapes = packed_av(tiny_h3.video, tiny_h3.audio)
def executor(x, t, *a, **kw):
to = kw.get("transformer_options") or (a[3] if len(a) > 3 else {})
if (to or {}).get("funui_probe"):
raise RuntimeError("probe broke")
return x0
s = torch.linspace(1.0, 0.0, 5)
out = wrap(executor, x0, torch.tensor([[1.0]]), None, None, None,
{"sample_sigmas": s, "sigmas": s[0:1], "cond_or_uncond": [0]}, latent_shapes=shapes)
assert out.shape == x0.shape
def test_the_helper_every_feature_asks_is_the_value_itself_without_a_map_and_follows_the_step_with_one():
from modules._core import dit_hooks
assert dit_hooks.block_map_value({}, "residual", 3, 0.4, [3, 4]) == 0.4
assert dit_hooks.block_map_value(None, "residual", 3, 0.4, [3, 4]) == 0.4
data = _data(lev=[[1.0] * 3, [4.0] * 3])
data["residual"] = data["attention"]
options = {"funui_block_map": measure.MapView(data), "sample_sigmas": torch.linspace(1, 0, 5), "sigmas": torch.tensor([0.5])}
assert dit_hooks.block_map_value(options, "residual", 1, 0.4, [1, 8]) > dit_hooks.block_map_value(options, "residual", 8, 0.4, [1, 8])
assert dit_hooks.block_map_value(options, "attention", 1, 0.4, [1]) == pytest.approx(0.4, rel=0.05) # one block: nothing to spread, only the soft limit
broken = {"funui_block_map": object(), "sample_sigmas": None}
assert dit_hooks.block_map_value(broken, "residual", 1, 0.4, [1, 2]) == 0.4
def _view(lev, frg=None):
data = _data(lev=lev, frg=frg)
data["residual"] = data["attention"]
return measure.MapView(data)
def test_a_huge_setting_is_kept_but_cannot_push_a_block_past_what_it_takes():
view = _view([[1.0] * 3, [4.0] * 3])
blocks = [1, 2, 7, 8]
small = view.adjust("attention", 7, 0.5, blocks, 0.05)
assert 0.0 < small <= 0.05 * measure.HIGH + 1e-9 # an ordinary setting passes (spread, not limited)
big_low, big_high = view.adjust("attention", 1, 0.5, blocks, 10.0), view.adjust("attention", 7, 0.5, blocks, 10.0)
assert 1.0 < big_low < 10.0 and 0.0 < big_high < big_low # 10 is respected as far as the block allows
assert view.adjust("attention", 7, 0.5, blocks, -10.0) == pytest.approx(-big_high) # the sign is kept
def test_adjust_leaves_neutral_and_flat_settings_alone_and_follows_the_run():
assert measure.MapView(_data()).adjust("attention", 3, 0.5, [2, 3], 0.3) == 0.3
view = _view([[1.0] * 3, [4.0] * 3])
assert view.adjust("attention", 3, 0.5, [2, 3], 0.0) == 0.0
assert view.adjust("residual", 3, 0.1, [2, 3], 0.3) != view.adjust("attention", 8, 0.1, [2, 3], 0.3)
+2 -1
View File
@@ -120,7 +120,8 @@ def install(patcher, values, key):
return func(q, k, v, *args, **kwargs)
# Rotation keeps length, so the rotated rows' size is the natural one's.
norm = torch.linalg.vector_norm(rows, dim=-1, dtype=torch.float32).mean()
scale = torch.where(torch.isfinite(norm) & (norm > 0), norm * strength, torch.zeros_like(norm))
push_strength = dit_hooks.block_map_value(to, "attention", block, strength, steer) # nudged by the Block map
scale = torch.where(torch.isfinite(norm) & (norm > 0), norm * push_strength, torch.zeros_like(norm))
push = (direction.to(device=q.device, dtype=q.dtype).view(heads, 1, dim)
* scale.to(q.dtype)).expand(heads, count, dim)
q = q.clone()
+3 -1
View File
@@ -107,8 +107,10 @@ def install(patcher, values, key):
if d is None:
d = cast[(block, rows.dtype)] = direction.to(device=rows.device, dtype=rows.dtype)
norm = torch.linalg.vector_norm(rows, dim=-1, dtype=torch.float32).mean()
# The Block map (when this model has a profile) nudges the strength per block; a big setting is kept, up to what the block takes.
push = dit_hooks.block_map_value(args.get("transformer_options"), "residual", block, strength, steer)
# No host sync: a bad norm makes the push zero instead of a branch.
scale = torch.where(torch.isfinite(norm) & (norm > 0), norm * strength,
scale = torch.where(torch.isfinite(norm) & (norm > 0), norm * push,
torch.zeros_like(norm))
rows += d * scale.to(rows.dtype)
if isinstance(out, (tuple, list)) and rows.data_ptr() != out[0].data_ptr():