Nodes/Helm/Helm Lab · Probe report
ComfyUI Node

Helm Lab · Probe report

A measurement, not a block picker

By U-N-B-R-A-N-D-E-D·Created 25 days ago·Updated 10 days ago· 7
Helm Lab · Probe report
    • peak_block
    ◄pos_tagprobe_pos►
    ◄neg_tagprobe_neg►
    ◄top5►

    Most ComfyUI nodes take something in and push something out. Helm Probe Report takes two filenames, does arithmetic, prints a table and returns one integer. No MODEL socket at all - that's the tell: it's a diagnostic, not a step in the pipeline.

    Use it after two Helm Probe runs to answer one question - where in this model do my two prompts actually differ? - and it gives you a ranked list of blocks plus a starting point for Helm Apply. What it won't do is tell you which block will look good, and the author is unusually direct about that gap.

    The arithmetic

    It loads both [n_blocks, dim] probe tensors and compares them row by row. The headline number is a scale-free separation:

    sep(b) = ‖μ_pos(b) − μ_neg(b)‖ / mean(‖μ_pos(b)‖, ‖μ_neg(b)‖)
    

    Dividing by the average magnitude makes blocks comparable; raw distance would just rank whichever blocks have big activations. The report also carries a cosine between the two means and both norms, so you can see why a block ranked where it did.

    Everything goes into ComfyUI/user/steer/probe_report.json - n_blocks, dim, a peak entry and the full ranking array with separation, cosine, mu_norm and delta_norm per block. If the write fails it says so and still prints the table, which is the right priority order.

    The inputs, and the one that's a trap

    • pos_tag - the tag you gave the first Helm Probe run (probe_pos by default).
    • neg_tag - the second (probe_neg).
    • top - how many ranked blocks to print. It changes the console output only. It does not change the number that comes out of the node.

    Output: one peak_block INT, and it isn't simply "the highest-separation block". The node restricts its search to the middle 50% of the stack - 25% to 75% of the depth - and returns the strongest separation inside that window. Grounded reason: on Z-Image the global peak is block 0, which is both the conditioning boundary and a block that destroys the image if you inject there. Ruling out the ends isn't rigour, it's damage control.

    The other deliberate omission is a strength output. The author tried estimating one from the activation norms, got a figure roughly 10× off on Z-Image, and dropped it. Sweep strength by hand.

    Wiring it without confusing yourself

    peak_block is an INT, so the honest use is to hand it to Helm Apply's block field. Right-click that widget → Convert widget to input → wire peak_block in. Now the whole chain is self-configuring: probe, report, apply.

    HelmProbe(pos) ─┐
    HelmProbe(neg) ─┴→ HelmProbeReport(top=8) ──peak_block──→ HelmApply(block=◄, axis=…)
    

    Leave the report node dangling and don't be surprised when it never runs: ComfyUI executes backwards from output nodes, so a node nothing reads from often doesn't execute at all, or runs once and sits in the cache. That's general engine behaviour rather than a Helm quirk, but it bites harder here because this node's whole value is its printed table. Wire it to something, or at least watch the console on a queue where it's genuinely upstream.

    Install

    ComfyUI Manager → search Helm → install → restart. Or:

    cd ComfyUI/custom_nodes
    git clone https://github.com/U-N-B-R-A-N-D-E-D/comfyui-helm.git comfyui-helm
    

    No models, no dependencies beyond safetensors and numpy (Python 3.10+). It's pure post-processing on files you've already written, so re-running with a different top costs nothing but a reprint. Local or self-hosted only; custom nodes don't install on Comfy Cloud.

    How to read the thing without fooling yourself

    A high separation is not a steering site. The line the node prints next to its answer is that the peak is "a MEASUREMENT, not a validated block picker". On Z-Image the middle-band peak landed at block 7 while the rendering-verified band is 9–21, and the following sweep found only 10 of 53 labelled directions held up. Treat peak_block as a first guess and render 2–3 neighbours before committing.

    Cosine tells you which kind of difference you found. Prompts differing in one property sit close in direction - high positive cosine, modest separation. A near-zero or negative cosine at some block means the two prompts are doing genuinely different things there, often a sign your contrast pair wasn't a one-property change.

    Shape mismatch means the passes weren't comparable. Both files must be the same [n_blocks, dim]. Different model, different resolution, different block list → ValueError naming both shapes. Fix the runs, don't try to work around it.

    Missing file. The same FileNotFoundError with a directory list you get from the rest of the pack: the tag doesn't resolve. Check ComfyUI/user/steer/. Note the report file has exactly one home, unlike axes, which are also read from models/steer/ and example_axes/.

    You're expecting a picture. There isn't one. The output is a JSON file and five lines of console. Screenshot the table or open the JSON - that's the deliverable.

    CategoryHelm/Lab

    Inputs (3)

    NameTypeDefaultDescription
    pos_tagSTRINGprobe_posName of the probe made WITH the idea.
    neg_tagSTRINGprobe_negName of the probe made WITHOUT the idea.
    topINT51–64how many ranked blocks to print

    Outputs (1)

    NameTypeDescription
    peak_blockINT—