Files
Kevin Gilper 839fa6e246 Add the layer-derivation harness (reliable, targeted methodology)
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).
2026-07-03 07:04:42 -04:00

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()