From 839fa6e246d0472cc91eb6675e08b615d0b5313d Mon Sep 17 00:00:00 2001 From: Kevin Gilper Date: Fri, 3 Jul 2026 07:04:42 -0400 Subject: [PATCH] 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). --- CHANGELOG.md | 1 + docs/deepstack-layers/.gitignore | 1 + docs/deepstack-layers/README.md | 110 +++++++--- .../compare_to_model_prior.py | 114 +++++++++++ docs/deepstack-layers/extract_model_prior.py | 73 +++++++ .../deepstack-layers/generate_probe_graphs.py | 146 +++++++++++++ docs/deepstack-layers/model_prior.json | 11 + docs/deepstack-layers/probe_node/__init__.py | 3 + .../probe_node/kg_conditioning_probe.py | 80 ++++++++ docs/deepstack-layers/probe_selectivity.py | 193 ++++++++++++++++++ 10 files changed, 701 insertions(+), 31 deletions(-) create mode 100644 docs/deepstack-layers/compare_to_model_prior.py create mode 100644 docs/deepstack-layers/extract_model_prior.py create mode 100644 docs/deepstack-layers/generate_probe_graphs.py create mode 100644 docs/deepstack-layers/model_prior.json create mode 100644 docs/deepstack-layers/probe_node/__init__.py create mode 100644 docs/deepstack-layers/probe_node/kg_conditioning_probe.py create mode 100644 docs/deepstack-layers/probe_selectivity.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 4856a55..db12dca 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ - Card: user-defined custom recipes - schema-validated YAML/JSON files in `custom_recipes/` (or `/krea_reference/recipes/`) become first-class `Use image for` choices, picked up on node-definition refresh. Only `label` and `role` are required; unknown keys are rejected (typo protection); invalid files are skipped with logged reasons and the node always loads; removed-recipe workflows fall back to `balanced` with a warning. Ships with `custom_recipes/README.md` and a disabled template. - Recipe authoring docs: `custom_recipes/README.md` and both V10 guides now explain how the `layers` array values work - the 12 text-layer taps Krea 2 conditions on, the built-in family tables, the `clamp(strength x shape x gain, +/-6)` landing math, a step-by-step derivation procedure with a copy-paste helper (the same scaling as the card's Structure/Finish dials), and a worked example. - `docs/deepstack-layers/`: an authoritative determination of what the `layers` values scale (verified from the Krea 2 model - 12 text-encoder layer taps, `hidden_states[2,5,8,...,35]`, flattened to a 30720-wide conditioning; the "deepstack" name is a documented misnomer for the separate 3 vision taps), where the specific numbers came from (adopted from the ComfyUI-ConditioningKrea2Rebalance node's defaults plus shallow=structure/deep=appearance design reasoning, with only the scalar shape/cap knobs empirically tuned - not a per-tap sweep), a reproducible table-shape analysis, and a turnkey single-tap measurement sweep (still to be run). +- `docs/deepstack-layers/` derivation harness (a reliable, targeted methodology for setting the layer values): Stage 0 extracts Krea 2's own learned per-tap weighting (`txtfusion.projector`) and scores the shipped tables against it (they only weakly track the model - motivation to re-derive); Stage 1 is a no-render selectivity harness (a `KG Conditioning Probe` node + variance-decomposition analysis, with a passing synthetic self-test) that measures what each tap encodes from controlled reference sets; Stages 2-3 (metric-scored render validation and black-box optimization of the 12-gain vector against a stated objective) are documented for a V10 render box. - Stack: `Balance strong cards` - an optional per-phase budget on summed departure from neutral, so several hot cards degrade gracefully instead of fighting. - Stack: `Reuse image studies` - a content-keyed cache of the base encode and ingredient deltas; re-runs that change only strengths, direction, timing, or balance skip every encoder pass. - Stack: two new outputs - `stack_report` (plain-language account of caps, curves, guard actions, balance, timing, and encoder pass counts) and `prepared_references` (contact sheet of exactly what the vision encoder studied). diff --git a/docs/deepstack-layers/.gitignore b/docs/deepstack-layers/.gitignore index 84236fe..9cb5aae 100644 --- a/docs/deepstack-layers/.gitignore +++ b/docs/deepstack-layers/.gitignore @@ -1 +1,2 @@ sweep_out/ +probe_out/ diff --git a/docs/deepstack-layers/README.md b/docs/deepstack-layers/README.md index 5c875c7..2a01525 100644 --- a/docs/deepstack-layers/README.md +++ b/docs/deepstack-layers/README.md @@ -89,49 +89,97 @@ template (section 2), so their consensus reflects a single design, not four independent measurements. It is a self-consistent, principled *design*; treat it as such. -## 4. What is still unmeasured, and how to measure it (turnkey) +## 4. How to derive the values reliably (the methodology + harness) -Nobody has yet measured, on Krea 2, what each individual tap actually does to -the image. The direct experiment is the single-chunk sweep, built and ready -here: +Nobody has yet *measured*, on Krea 2, what each tap does. The reliable way to +do it - and to re-derive the tables for any future checkpoint - is a four-stage +pipeline, from cheapest/most-targeted to definitive. Stages 0-1 are built and +run/validated here today; Stages 2-3 need renders on a V10 box. + +### Stage 0 - the model's own prior (free, no rendering) - DONE + +Krea 2's diffusion model already learned a per-tap weighting +(`txtfusion.projector [1,12]`). Extract it and compare the shipped tables to +it: ```bash -python docs/deepstack-layers/generate_sweep.py --dry-run # build + validate (no server) -python docs/deepstack-layers/generate_sweep.py --server http://HOST:8188 # render (needs the V10 nodes) +python docs/deepstack-layers/extract_model_prior.py --model # re-extract +python docs/deepstack-layers/compare_to_model_prior.py # tables vs. prior ``` -Method: hold one reference at fixed strength; for each tap `L`, render a gain -table that is 1.0 everywhere except a spike at `L`; compare to the all-ones -control. Everything else is held identical, so any visible difference isolates -tap `L`. Scoring: [SCORING.md](SCORING.md). It emits each spike as a V10 -custom recipe (validated against the real node code) and writes renders to the -dedicated `output/claude-generations/` folder. Single-chunk tables are not -expressible through the V9 widgets, so rendering needs the V10 nodes on the -target ComfyUI. +**Result (2026-07-03):** the model leans hardest on taps **7 > 9 > 4 > 10 > 8**; +the shipped tables spike **8 > 10 > 7**. Rank-correlation of each table with the +model's emphasis is only +0.58 to +0.64, with consistent gaps at taps 4 (model +#3, tables suppress it), 8 (tables' #1 spike, model #5), and 11. So the +borrowed template does not track how Krea 2 actually uses the taps - concrete +motivation to re-derive. (Caveat: the projector weights *processed* taps while +the node's gains hit the *raw* delta, so this is a prior/smell-test, not ground +truth.) Prior values: [model_prior.json](model_prior.json). -### Measured verdicts (to be filled by the sweep) +### Stage 1 - representational selectivity (cheap, no rendering) - HARNESS READY -| Tap | subject ref | palette ref | consensus | note | -| --- | --- | --- | --- | --- | -| 0-4 | _pending_ | _pending_ | | design predicts structure (shallow) | -| 5-6 | _pending_ | _pending_ | | design predicts transition | -| 7 / 8 / 10 | _pending_ | _pending_ | | design predicts appearance (deep); 8 strongest | -| 9, 11 | _pending_ | _pending_ | | design predicts gentle | +Measure, at the conditioning level, what each tap encodes. Build a *controlled* +reference set (vary one attribute at a time - a palette series with fixed +subject, a structure series with fixed palette, etc.), encode each through a +neutral V10 card, capture the per-tap signature with the probe node, and +decompose variance: `eta2(tap, attribute)` = how much of a tap's contribution +is explained by each attribute. High palette-eta2 + low structure-eta2 = a +palette tap. -If the sweep contradicts the design prediction, that is a real finding - the -borrowed template may not match Krea 2's actual per-tap response, and the -tables would be worth re-deriving from the measurement. +```bash +python docs/deepstack-layers/probe_selectivity.py --selftest # validate the math (passes) +python docs/deepstack-layers/generate_probe_graphs.py --dry-run # build encode graphs +# then on a V10+probe box: --server URL, copy signatures back, then: +python docs/deepstack-layers/probe_selectivity.py --probes --manifest probe_out/probe-manifest.json +``` + +The selectivity math is validated (the self-test recovers an injected +tap->attribute map exactly). Running it for real needs: the V10 nodes + the +[probe node](probe_node/) installed on a ComfyUI box, and a controlled +reference set (see below). It renders **nothing** - text-encoder passes only. + +### Stage 2 - metric-scored render validation (moderate) - needs a V10 box + +Confirm the Stage 1 map translates to pixels: render single-tap and +small-combination spikes on a fixed seed/prompt grid +([generate_sweep.py](generate_sweep.py)) but score with objective, +attribute-specific metrics (color-distribution distance for palette, LPIPS/SSIM +for structure, high-frequency energy for texture, style/identity distances), +not by eye. [SCORING.md](SCORING.md) is the interim manual rubric. + +### Stage 3 - optimize the table for a stated objective (definitive) - needs a V10 box + +Turn each role's intent into a measurable objective (e.g. style: maximize +style-similarity to the reference minus content/identity leakage) and optimize +the 12-gain vector with a black-box optimizer (CMA-ES / Bayesian optimization, +~100-300 evals for 12 dims), initialized from the Stage 0 prior + Stage 1 map, +trained and reported on held-out references. This *derives* the table from data +for the exact effect wanted. + +### Building the controlled reference sets + +Stage 1's reliability depends on the sets being controlled. Make them by +generating variants that hold everything fixed but one attribute (e.g. the same +scene re-rendered in N palettes; the same palette over N subjects). These can be +produced with Krea/Ideogram to a dedicated folder. The +[probe manifest](generate_probe_graphs.py) records each reference's attribute +levels for the variance decomposition. ## Files -| File | What | -| --- | --- | -| [analyze_tables.py](analyze_tables.py) | Prints the five tables and their designed shape from the live code. No rendering. | -| [generate_sweep.py](generate_sweep.py) | The single-tap sweep generator - the honest per-tap measurement, still to be run. | -| [SCORING.md](SCORING.md) | How to score the rendered grid into per-tap verdicts. | +| File | What | Runs | +| --- | --- | --- | +| [analyze_tables.py](analyze_tables.py) | Prints the five tables' designed shape from live code. | now, local | +| [extract_model_prior.py](extract_model_prior.py) | Extracts the model's learned per-tap weighting from a checkpoint. | now (reads model file) | +| [compare_to_model_prior.py](compare_to_model_prior.py) | Stage 0: tables vs. the model prior. | now, local | +| [probe_node/](probe_node/) | ComfyUI node that saves per-tap conditioning signatures. | install on box | +| [probe_selectivity.py](probe_selectivity.py) | Stage 1: tap x attribute selectivity + self-test. | now (selftest), needs data (real) | +| [generate_probe_graphs.py](generate_probe_graphs.py) | Builds the encode-only probe graphs. | now (dry-run), needs box (encode) | +| [generate_sweep.py](generate_sweep.py) | Stage 2: single-tap render sweep. | needs V10 box | +| [SCORING.md](SCORING.md) | Manual scoring rubric for the render sweep. | reference | ## Cross-references -- [V9 technical paper 5.2](../krea-v9-technical-paper.md#52-per-layer-gains-steering-inside-the-token-channel) - the channel math (note: it inherits the "deepstack" naming corrected here). -- [V9 technical paper 16.4](../krea-v9-technical-paper.md#164-re-tune-layer-gains-for-a-new-checkpoint) - the sweep methodology this kit implements. +- [V9 technical paper 5.2](../krea-v9-technical-paper.md#52-per-layer-gains-steering-inside-the-token-channel) - the channel math (inherits the "deepstack" naming corrected here). +- [V9 technical paper 16.4](../krea-v9-technical-paper.md#164-re-tune-layer-gains-for-a-new-checkpoint) - the render-sweep methodology. - [custom_recipes/README.md](../../custom_recipes/README.md#deriving-the-layers-array) - authoring a `layers` array from this structure. diff --git a/docs/deepstack-layers/compare_to_model_prior.py b/docs/deepstack-layers/compare_to_model_prior.py new file mode 100644 index 0000000..d1ac3b0 --- /dev/null +++ b/docs/deepstack-layers/compare_to_model_prior.py @@ -0,0 +1,114 @@ +"""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() diff --git a/docs/deepstack-layers/extract_model_prior.py b/docs/deepstack-layers/extract_model_prior.py new file mode 100644 index 0000000..db85a25 --- /dev/null +++ b/docs/deepstack-layers/extract_model_prior.py @@ -0,0 +1,73 @@ +"""Extract Krea 2's learned per-tap weighting (txtfusion.projector) from a checkpoint. + +Reads only the safetensors header + the one small tensor - it does NOT load +the model. Reproduces model_prior.json so the 12 prior values are never a +magic number: rerun against any Krea 2 checkpoint (e.g. a new version, or the +8B) to re-derive the prior for that model. + +Usage: + python extract_model_prior.py --model /path/to/krea2_turbo_bf16.safetensors + python extract_model_prior.py --model ... --write # update model_prior.json +""" + +import argparse +import array +import json +import struct +from pathlib import Path + +HERE = Path(__file__).resolve().parent +TENSOR = "txtfusion.projector.weight" + + +def read_tensor(path, name): + with open(path, "rb") as f: + n = struct.unpack(" probe. image=None for the baseline.""" + g = { + "clip": {"class_type": "CLIPLoader", "inputs": {"clip_name": CLIP, "type": "krea2", "device": "default"}}, + "stack": {"class_type": "KGTextEncodeKreaImageReferencesV10", "inputs": stack_inputs(image is not None)}, + "probe": {"class_type": "KGConditioningProbe", "inputs": {"conditioning": ["stack", 0], "label": label}}, + } + if image is not None: + g["load"] = {"class_type": "LoadImage", "inputs": {"image": image, "upload": "image"}} + g["card"] = {"class_type": "KGKrea2ImageGuideCardV10", "inputs": { + "Reference image": ["load", 0], + "How strongly this image guides": 1.0, + "Use image for": "balanced", + }} + return g + + +def post_and_wait(server, graph, timeout=180): + payload = json.dumps({"prompt": graph}).encode("utf-8") + req = urllib.request.Request(server + "/prompt", data=payload, headers={"Content-Type": "application/json"}) + with urllib.request.urlopen(req, timeout=30) as r: + pid = json.load(r)["prompt_id"] + start = time.time() + while time.time() - start < timeout: + with urllib.request.urlopen(server + "/history/" + pid, timeout=30) as r: + entry = json.load(r).get(pid) + if entry and (entry.get("outputs") or entry.get("status", {}).get("completed")): + return + if entry and entry.get("status", {}).get("status_str") == "error": + raise RuntimeError(json.dumps(entry["status"])[:800]) + time.sleep(1.5) + raise TimeoutError(pid) + + +def main(): + ap = argparse.ArgumentParser(description=__doc__) + ap.add_argument("--refs", help="controlled-reference manifest json (see SAMPLE_REFS)") + ap.add_argument("--server", help="ComfyUI base URL (V10 + probe node installed)") + ap.add_argument("--dry-run", action="store_true") + args = ap.parse_args() + + OUT_DIR.mkdir(parents=True, exist_ok=True) + refs = json.loads(Path(args.refs).read_text(encoding="utf-8")) if args.refs else SAMPLE_REFS + + plan = [("baseline", None)] + [(lbl, r["image"]) for lbl, r in refs["references"].items()] + graphs = {lbl: build_graph(lbl, img) for lbl, img in plan} + # structural validation + for lbl, g in graphs.items(): + assert g["probe"]["inputs"]["conditioning"] == ["stack", 0] + assert g["probe"]["inputs"]["label"] == lbl + + if args.dry_run or not args.server: + (OUT_DIR / "sample-refs.json").write_text(json.dumps(SAMPLE_REFS, indent=2) + "\n", encoding="utf-8") + (OUT_DIR / "probe-manifest.json").write_text(json.dumps({ + "attributes": refs["attributes"], "baseline": "baseline", + "references": {k: {a: v[a] for a in refs["attributes"]} for k, v in refs["references"].items()}, + }, indent=2) + "\n", encoding="utf-8") + print("dry run OK: {} encode graphs (1 baseline + {} references) validated".format( + len(graphs), len(graphs) - 1)) + print("wrote sample-refs.json + probe-manifest.json to", OUT_DIR) + print("to run: install V10 + probe_node on the render box, put controlled images in") + print("ComfyUI/input/layerprobe/, then rerun with --refs --server http://HOST:8188") + return 0 + + server = args.server.rstrip("/") + for lbl, img in plan: + print("encoding", lbl, flush=True) + post_and_wait(server, graphs[lbl]) + print("done - probe signatures saved under output/claude-generations/kg-selectivity-probe/ on the box") + print("copy them back, then: python probe_selectivity.py --probes --manifest probe_out/probe-manifest.json") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/docs/deepstack-layers/model_prior.json b/docs/deepstack-layers/model_prior.json new file mode 100644 index 0000000..2303d21 --- /dev/null +++ b/docs/deepstack-layers/model_prior.json @@ -0,0 +1,11 @@ +{ + "description": "Krea 2's own learned per-tap weighting over the 12 text-layer taps, extracted from the diffusion model. A model-intrinsic prior for the layers gain tables (Stage 0 of the derivation methodology in README section 4).", + "source_tensor": "txtfusion.projector.weight", + "source_file": "models/diffusion_models/krea2/krea2_turbo_bf16.safetensors (on the render box \\\\olga\\d\\ComfyUI)", + "shape": [1, 12], + "dtype": "F32", + "extracted": "2026-07-03", + "reextract": "python docs/deepstack-layers/extract_model_prior.py --model ", + "caveat": "The projector combines taps AFTER per-tap layerwise/refiner blocks; the node's gains act on the raw tap delta BEFORE that processing. So this is a prior and sanity check on relative tap importance, not a literal gain table. The node->pixel path is nonlinear.", + "weights": [-0.0544, -0.1611, 0.3711, 0.5039, 0.707, 0.3945, 0.3984, -1.4375, -0.5117, -0.8906, -0.6094, 0.1128] +} diff --git a/docs/deepstack-layers/probe_node/__init__.py b/docs/deepstack-layers/probe_node/__init__.py new file mode 100644 index 0000000..f084aaf --- /dev/null +++ b/docs/deepstack-layers/probe_node/__init__.py @@ -0,0 +1,3 @@ +from .kg_conditioning_probe import NODE_CLASS_MAPPINGS, NODE_DISPLAY_NAME_MAPPINGS + +__all__ = ["NODE_CLASS_MAPPINGS", "NODE_DISPLAY_NAME_MAPPINGS"] diff --git a/docs/deepstack-layers/probe_node/kg_conditioning_probe.py b/docs/deepstack-layers/probe_node/kg_conditioning_probe.py new file mode 100644 index 0000000..73dbea0 --- /dev/null +++ b/docs/deepstack-layers/probe_node/kg_conditioning_probe.py @@ -0,0 +1,80 @@ +"""KG Conditioning Probe - a tiny ComfyUI node for the layer-derivation harness. + +Captures the conditioning tensor flowing through it and saves a compact +per-tap signature to disk, so the layer-selectivity analysis (Stage 1 of the +methodology) can measure what each of Krea 2's 12 text-layer taps encodes - +WITHOUT running diffusion (no sampler, no VAE, no image generated). + +It saves the sequence-mean of the conditioning, reshaped to (12, tap_dim): +one mean vector per tap. That is enough to compute, offline, how each tap's +contribution varies across a controlled set of reference images. + +Install: copy this folder into ComfyUI/custom_nodes/ on the render box and +restart. Output goes under the dedicated Claude folder +(output/claude-generations/kg-selectivity-probe/