Nodes/Concept Steer/Concept Steer
ComfyUI Node

Concept Steer

One vector, no LoRA — nudge your whole workflow toward 'cinematic' (or away from it)

By Nynxz·Created 6 months ago·Updated 6 months ago· 4
Concept Steer
  • conditioning
  • CONDITIONING
lensNone
strength1.00
normalizetrue
active_tokens_onlytrue
custom_lens_path

You've got a workflow that mostly works, but every output comes out looking like it was lit by a fluorescent office ceiling. You could chase it with prompt words, or you could reach for a 10 KB file that's just a single direction vector. That's the whole trick of Concept Steer, the flagship node of the Nynxz/ComfyUI-ConceptSteer pack: it sits between your CLIP Text Encode and your KSampler, adds a learned direction to the conditioning tensor, and biases the whole generation toward a concept like cinematic, vintage film, or dark moody - without touching your prompt or loading any extra model.

This is not a LoRA, and the README is upfront about that. It's a broad aesthetic nudge, not a subject-fidelity tool. A single vector added to every active token can't encode "this specific character" or a compositionally complex style. What it can do is give you a consistent stylistic push you can crank from barely-there to dominant, and stack across a whole graph for free.

How it works

The pack is built on the Linear Representation Hypothesis - the observation (Nanda et al. 2023, Park et al. 2024) that concepts live in LLM/vision-language hidden states as roughly linear directions. A "concept lens" is that direction, trained by either contrastive paired-margin optimization or a sparse autoencoder decomposition of the text encoder's residual stream. At inference the node loads the lens and adds strength × avg_token_norm × direction to every active token position in the conditioning.

Why does addition work? Text-to-image conditioning is injected via cross-attention, so adding a direction to the embedding before the projection is equivalent to adding a bias across every cross-attention layer. And because strength=1.0 is scaled to about 30% of a typical token norm, the default is clearly visible but non-destructive - you're meant to treat it like a dial, not a sledgehammer.

The pack currently targets Z Image Turbo (Qwen 3.4B encoder, 2560d) and SD 1.5 (SigLIP, 768d). Those are the tested paths; the author warns other architectures may use non-linear conditioning injection where a bare direction doesn't transfer cleanly.

The inputs that matter

  • conditioning - from your text encoder. Straight into the node.
  • lens - dropdown of everything in your lenses/ folder. It'll be empty until you generate one.
  • strength - 1.0 is clearly visible; 0.3–0.5 is a subtle nudge, 2.0–4.0 is a strong push, 5.0+ starts to distort. Negative values steer away from the concept, which is a surprisingly fun way to de-vintage something.
  • normalize - leave true; it's what makes strength values behave consistently across prompts.
  • active_tokens_only - leave true so padding tokens don't get steered.
  • custom_lens_path - a free-text override for a specific lens file, handy when you want to test one you trained without re-sorting the dropdown.

The single output is a CONDITIONING, so it wires straight into your KSampler in place of the text encoder output.

Installing

ComfyUI Manager, search "Concept Steer", install - or:

cd ComfyUI/custom_nodes
git clone https://github.com/Nynxz/ComfyUI-ConceptSteer
# restart ComfyUI

There's nothing heavy to install - the pack ships no pip dependencies. The catch is the opposite direction: no pre-trained lenses ship with it, so the dropdown is empty until you make one. The fast path is the included CLI (~30s per lens):

cd ComfyUI/custom_nodes/ComfyUI-ConceptSteer
python tools/lens_factory.py auto cinematic --target zimage

or batch-all --target zimage to generate all six presets (cinematic, ethereal, dark_moody, vintage_film, minimalist, vibrant_pop). Lenses land in lenses/ and appear in the dropdown automatically. You can also train them right in ComfyUI with the Train Lens nodes - no terminal needed. For training you'll want transformers and safetensors (pip install transformers safetensors) and roughly 10 GB VRAM for the Qwen encoder.

Where people get burned

The empty dropdown is the #1 "is this broken?" moment - it isn't; you just haven't generated a lens. Second: cranking strength to 10 and getting mush. The norm trick means 1.0 is already a visible effect; move in small steps. Third, and this one's subtle: if your prompt already says "cinematic," stacking a cinematic lens on top can overshoot and artifact. The direction is added regardless of prompt content. And contrastive lenses trained from sloppy text pairs learn scene differences instead of your concept - the lens you get back is only as good as the pairs you fed it.

It's a young pack and you won't find much community lore on it yet - the ideas (steering vectors, SAEs, TCAVs) are well-trodden research, but this implementation is fresh. That's fine: the README and docs/HOW_IT_WORKS.md in the repo are genuinely good reading if you want the full mechanism.

CategoryConcept Steer

Inputs (6)

NameTypeDefaultDescription
conditioningCONDITIONING
lensCOMBONoneSelect a concept lens to apply
strengthFLOAT1.00-10–10Steering strength. 1.0 = moderate, clearly visible effect. Negative values steer AWAY from the concept.
normalizeBOOLEANtrueScale direction relative to conditioning norm. When enabled, strength=1.0 means the perturbation magnitude equals the average active token norm.
active_tokens_onlyBOOLEANtrueOnly modify active (non-padding) token positions. Disable to steer ALL token positions including padding.
custom_lens_pathSTRINGOptional: absolute path to a .pt lens file. Overrides the dropdown selection if provided.

Outputs (1)

NameTypeDescription
CONDITIONINGCONDITIONING