Replaces "borrow a template + eyeball renders" with a staged pipeline for setting the 12 layer-gain values, cheapest/most-targeted first: - Stage 0 (done, local): extract_model_prior.py reads Krea 2's own learned per-tap weighting (txtfusion.projector [1,12]) from the checkpoint; compare_to_model_prior.py scores each shipped table against it. Finding: the model leans hardest on taps 7>9>4>10>8 but the tables spike 8>10>7 (rank-corr only +0.58..+0.64) - the borrowed template does not track how the model uses the taps. model_prior.json records the values. - Stage 1 (harness ready, no render): probe_node/ is a tiny ComfyUI node that saves per-tap conditioning signatures; probe_selectivity.py does a variance decomposition (eta^2) of each tap against palette/structure/ texture/lighting over a controlled reference set; generate_probe_graphs.py builds the encode-only graphs. The selectivity math passes a synthetic self-test (recovers an injected tap->attribute map exactly). Running it for real needs V10 + the probe node installed on a box, plus controlled image sets - it renders nothing (text-encoder passes only). - Stages 2-3 (documented, need a V10 box): metric-scored render validation, then black-box optimization of the 12-gain vector against a stated objective, held-out validated. README section 4 rewritten as the methodology. All scripts run/validate locally; tooling stays under docs/ (excluded from registry packs).
115 lines
4.3 KiB
Python
115 lines
4.3 KiB
Python
"""Stage 0: how far are the shipped tables from the model's own tap emphasis?
|
|
|
|
Compares each built-in layer table against Krea 2's learned per-tap weighting
|
|
(txtfusion.projector, model_prior.json). This is the cheapest, no-render check
|
|
in the derivation methodology (README section 4): if a table's emphasis does
|
|
not track the taps the model actually relies on, that is a flag the table may
|
|
be suboptimal.
|
|
|
|
Honest scope: the projector weights the PROCESSED taps (after per-tap blocks)
|
|
while the node's gains act on the RAW tap delta (before them), so perfect
|
|
alignment is not expected and misalignment is a smell test, not proof. It
|
|
tells you where to look, and gives a model-informed starting point for the
|
|
real derivation (Stages 1-3).
|
|
|
|
Run: python docs/deepstack-layers/compare_to_model_prior.py
|
|
"""
|
|
|
|
import json
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
HERE = Path(__file__).resolve().parent
|
|
REPO_ROOT = HERE.parents[1]
|
|
sys.path.insert(0, str(REPO_ROOT / "tests"))
|
|
|
|
from _kg_stub_env import load_module # noqa: E402
|
|
|
|
APPEARANCE = ("style", "palette", "material", "lighting")
|
|
|
|
|
|
def load_tables():
|
|
v9, _ = load_module("kg_krea_v9", "kg_prior_compare")
|
|
r = v9.recipes
|
|
return {
|
|
"even": list(r.EVEN_LAYER_PULL), "style": list(r.STYLE_LAYER_PULL),
|
|
"palette": list(r.PALETTE_LAYER_PULL), "material": list(r.MATERIAL_LAYER_PULL),
|
|
"lighting": list(r.LIGHTING_LAYER_PULL),
|
|
}
|
|
|
|
|
|
def ranks(xs):
|
|
order = sorted(range(len(xs)), key=lambda i: xs[i])
|
|
rk = [0.0] * len(xs)
|
|
i = 0
|
|
while i < len(xs):
|
|
j = i
|
|
while j + 1 < len(xs) and xs[order[j + 1]] == xs[order[i]]:
|
|
j += 1
|
|
avg = sum(range(i, j + 1)) / (j - i + 1)
|
|
for k in range(i, j + 1):
|
|
rk[order[k]] = avg
|
|
i = j + 1
|
|
return rk
|
|
|
|
|
|
def pearson(a, b):
|
|
n = len(a)
|
|
ma, mb = sum(a) / n, sum(b) / n
|
|
cov = sum((a[i] - ma) * (b[i] - mb) for i in range(n))
|
|
va = sum((x - ma) ** 2 for x in a) ** 0.5
|
|
vb = sum((x - mb) ** 2 for x in b) ** 0.5
|
|
return cov / (va * vb) if va and vb else 0.0
|
|
|
|
|
|
def spearman(a, b):
|
|
return pearson(ranks(a), ranks(b))
|
|
|
|
|
|
def main():
|
|
prior = json.loads((HERE / "model_prior.json").read_text(encoding="utf-8"))
|
|
w = prior["weights"]
|
|
importance = [abs(x) for x in w]
|
|
tot = sum(importance) or 1.0
|
|
share = [x / tot for x in importance]
|
|
tables = load_tables()
|
|
|
|
print("=" * 74)
|
|
print("Model's own per-tap emphasis (|txtfusion.projector|, normalized)")
|
|
print("=" * 74)
|
|
print("tap: " + " ".join("{:>5}".format(i) for i in range(12)))
|
|
print("share: " + " ".join("{:>5.3f}".format(s) for s in share))
|
|
print("model ranking (most-used first):", sorted(range(12), key=lambda i: -importance[i]))
|
|
|
|
print("\n" + "=" * 74)
|
|
print("Table emphasis vs. the model prior (rank correlation; 1.0 = aligned)")
|
|
print("=" * 74)
|
|
for name in APPEARANCE:
|
|
table = tables[name]
|
|
rho = spearman(table, importance)
|
|
# Where the table and the model most disagree.
|
|
table_rank = {t: r for t, r in zip(range(12), ranks([-x for x in table]))}
|
|
model_rank = {t: r for t, r in zip(range(12), ranks([-x for x in importance]))}
|
|
gaps = sorted(range(12), key=lambda i: -abs(table_rank[i] - model_rank[i]))
|
|
worst = ["tap {} (table#{}/model#{})".format(i, int(table_rank[i]) + 1, int(model_rank[i]) + 1) for i in gaps[:3]]
|
|
print(" {:>8}: rank-corr {:+.2f} biggest gaps: {}".format(name, rho, "; ".join(worst)))
|
|
|
|
print("\n" + "=" * 74)
|
|
print("Model-informed starting shape (for Stages 1-3, NOT a drop-in table)")
|
|
print("=" * 74)
|
|
# A neutral illustration: gains that track the model's emphasis, scaled so
|
|
# the mean is ~1 (so it is comparable to the even baseline before role tuning).
|
|
mean_share = sum(share) / 12
|
|
prior_shape = [round(s / mean_share, 2) for s in share]
|
|
print(" gains proportional to model tap-use:", prior_shape)
|
|
style = tables["style"]
|
|
print(" STYLE table spikes taps 7/8/10 = {}/{}/{} (biggest at 8);".format(style[7], style[8], style[10]))
|
|
print(" the model leans hardest on taps {} (biggest at 7) - different taps.".format(
|
|
sorted(range(12), key=lambda i: -importance[i])[:3]))
|
|
print(" reminder: this is a prior/sanity-check, not a drop-in table. Derive real")
|
|
print(" gains with the selectivity harness + optimization (README section 4).")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|