Nodes/ComfyUI QwenScope/Train Lens (Contrastive)
ComfyUI Node

Train Lens (Contrastive)

Train Lens (Contrastive) — teach a direction a concept, no LoRA required

By Nynxz·Created 4 months ago·Updated 4 months ago· 0
Train Lens (Contrastive)
  • clip
  • steer
  • report
positive_texts
negative_texts
layer-1
steps500
lr0.0100
beta50
strength1.00
save_name

Train Lens (Contrastive) is the node that gets you concepts SAE features can't. Single SAE features are narrow - one feature might reliably fire on rim lighting, but "cinematic" or "vintage film" or "ethereal" isn't one feature, it's a whole cluster. This node trains a direction for exactly those concepts: you give it positive prompts that embody the concept, negative prompts that don't, and it optimizes a vector that maximally separates the two. Roughly 30 seconds later you have a steer plan.

That's the same recipe as ConceptSteer's contrastive lens training, but routed through ComfyUI's CLIP bridge - so it works on whatever encoder your image model actually uses (FLUX.2 [klein]'s Qwen3-8B, Qwen-Image, quantised encoders included) without loading a separate Qwen LLM. It's the most "train-y" node in the pack and the one that produces the most reusable results, which is why its output feeds straight into Save Lens.

How it works

It captures the mean-pooled residual at one layer for each of your positive and negative prompts, then runs a small optimization loop (default 500 steps, Adam, lr 0.01) that maximizes a Bradley-Terry margin loss on (pos − neg) · direction. The result is normalized to the unit sphere so strength stays calibrated. The beta input controls margin sharpness - higher = a sharper boundary, easier to overfit; the default 50 is a solid middle. It even escapes ComfyUI's inference-mode tensor restrictions to run real autograd, which is a nice bit of engineering under the hood.

The inputs that matter

  • clip - the CLIP whose encoder you're training on. Use the one you'll actually generate with.
  • positive_texts / negative_texts - one prompt per line, equal counts required. The pairs should differ only on the concept: "a cinematic dramatic portrait" vs "a casual snapshot portrait" beats "a cinematic dramatic portrait" vs "a photo of a dog." The pack ships starter prompt packs in examples/lens_prompts.md (cinematic, vintage_film, ethereal…).
  • layer - -1 (last layer) is the default and the right choice if you'll apply via Steer Conditioning. Pick a middle layer if you'll apply via Steer CLIP at the same layer.
  • steps / lr / beta - leave at defaults (500 / 0.01 / 50) until you have a reason not to.
  • strength - the default strength baked into the plan (start 1.0; edit models often want 0.5–1.5).
  • save_name - set it to auto-save to lenses/qwenscope/<name>.safetensors after training.

Outputs: steer (the trained direction, ready for Steer Conditioning / Steer CLIP / Save Lens) and report - a STRING with training time, layer, pairs, steps and the resulting direction norm.

Installing it

Same pack, one install: ComfyUI Manager → search "ComfyUI QwenScope", or

cd ComfyUI/custom_nodes
git clone https://github.com/Nynxz/ComfyUI-QwenScope

restart. Standard deps. No model downloads beyond what your CLIP already loaded - training is on the encoder you have.

Common issues

Three real ones. (1) Mismatched counts - positives ≠ negatives throws immediately; count your lines. (2) NaN during training - the error message is blunt about the usual cause: leftover steering hooks from a Steer CLIP contaminating your captures. Run Clear CLIP Steering first. (3) Positives and negatives too similar - if the means are nearly identical the node warns and falls back to a random init; you'll get a direction that means nothing. Fix it with more diverse pairs. And 8–12 good pairs is usually enough; 20+ is diminishing returns, so don't grind out fifty.

CategoryQwenScope/Train

Inputs (9)

NameTypeDefaultDescription
clipCLIP
positive_textsSTRINGTexts that EMBODY the concept (one per line).
negative_textsSTRINGNeutral / opposite texts (same count as positives).
layerINT-1-128–127Layer to capture residuals at. -1 = last layer (use this if you'll apply via Steer Conditioning). Middle layers (e.g. half of total) work well for Steer CLIP at the same layer.
stepsINT50050–10000
lrFLOAT0.01000.0001–0.1
betaFLOAT501–200Margin sharpness in the BT loss. Higher = sharper boundary.
strengthFLOAT1.00-8–8
save_nameoptSTRINGIf set, also save to lenses/qwenscope/<save_name>.safetensors.

Outputs (2)

NameTypeDescription
steerQSCOPE_STEER
reportSTRING