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).
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
from .kg_conditioning_probe import NODE_CLASS_MAPPINGS, NODE_DISPLAY_NAME_MAPPINGS
|
||||
|
||||
__all__ = ["NODE_CLASS_MAPPINGS", "NODE_DISPLAY_NAME_MAPPINGS"]
|
||||
@@ -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/<label>.json).
|
||||
|
||||
This is a maintainer/analysis tool - it is NOT part of the shipped node pack
|
||||
(kept under docs/, excluded from registry packs).
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
|
||||
import torch
|
||||
|
||||
try:
|
||||
import folder_paths
|
||||
_OUT_ROOT = folder_paths.get_output_directory()
|
||||
except Exception:
|
||||
_OUT_ROOT = os.path.join(os.getcwd(), "output")
|
||||
|
||||
_PROBE_DIR = os.path.join(_OUT_ROOT, "claude-generations", "kg-selectivity-probe")
|
||||
LAYER_COUNT = 12
|
||||
|
||||
|
||||
class KGConditioningProbe:
|
||||
"""Save a per-tap signature of the conditioning; pass it through unchanged."""
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"conditioning": ("CONDITIONING",),
|
||||
"label": ("STRING", {"default": "probe"}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("CONDITIONING", "STRING")
|
||||
RETURN_NAMES = ("conditioning", "saved_path")
|
||||
FUNCTION = "probe"
|
||||
CATEGORY = "advanced/conditioning"
|
||||
OUTPUT_NODE = True
|
||||
|
||||
def probe(self, conditioning, label):
|
||||
os.makedirs(_PROBE_DIR, exist_ok=True)
|
||||
cond_tensor, _extras = conditioning[0] # first schedule entry: (cond, dict)
|
||||
# Mean over batch + sequence -> (feature_width,), then split into 12 taps.
|
||||
flat = cond_tensor.float().reshape(-1, cond_tensor.shape[-1]).mean(dim=0)
|
||||
width = int(flat.shape[0])
|
||||
record = {
|
||||
"label": str(label),
|
||||
"feature_width": width,
|
||||
"divides_by_12": (width % LAYER_COUNT == 0),
|
||||
"tap_dim": width // LAYER_COUNT if width % LAYER_COUNT == 0 else None,
|
||||
}
|
||||
if record["divides_by_12"]:
|
||||
taps = flat.reshape(LAYER_COUNT, width // LAYER_COUNT)
|
||||
# Compact signature per tap: full mean vector (rounded) + norm.
|
||||
record["tap_mean_vectors"] = [[round(float(v), 5) for v in row] for row in taps]
|
||||
record["tap_norms"] = [round(float(row.norm()), 5) for row in taps]
|
||||
else:
|
||||
record["seq_mean_vector"] = [round(float(v), 5) for v in flat]
|
||||
safe = "".join(c if c.isalnum() or c in "-_." else "_" for c in str(label))
|
||||
path = os.path.join(_PROBE_DIR, safe + ".json")
|
||||
with open(path, "w", encoding="utf-8") as f:
|
||||
json.dump(record, f)
|
||||
return (conditioning, path)
|
||||
|
||||
|
||||
NODE_CLASS_MAPPINGS = {"KGConditioningProbe": KGConditioningProbe}
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {"KGConditioningProbe": "KG Conditioning Probe (analysis)"}
|
||||
Reference in New Issue
Block a user