CPO
steerability.algorithms.input_control.cpo
args
Arguments for the CPO input control.
control
CPO, causal prompt optimization.
Reference:
- "Causal Prompt Optimization" Chen et al., 2026 https://arxiv.org/abs/2602.01711
logger = logging.getLogger(__name__)
module-attribute
CPO
Bases: InputControl
Causal prompt optimization.
Stage 1 (steer): build a ⟨query, prompt, score⟩ dataset (either supplied or generated from
train_dataset), then train a CausalRewardScorer over PCA-reduced embeddings.
Stage 2 (adapt_messages): per-query, run a B-wide K-retained R-round tree search using the prompt
proposer to expand parents and the causal scorer to retain survivors. Returns the best survivor as
the system prompt.
The query cache keys on a sha256 hash of the query text. By default the raw query is hashed, so
whitespace, casing, and punctuation differences yield distinct cache entries. Supply
cache_key_normalizer (a str -> str callable) to normalize the query before hashing so
near-duplicate queries share a cached prompt.
Reference:
- "Causal Prompt Optimization" Chen et al., 2026 https://arxiv.org/abs/2602.01711
Source code in steerability/algorithms/input_control/cpo/control.py
58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 | |
RUNTIME_KWARGS_SCHEMA = []
class-attribute
instance-attribute
args = self.Args.validate(*args, **kwargs)
instance-attribute
cache_key_normalizer = None
class-attribute
instance-attribute
cache_queries = True
class-attribute
instance-attribute
candidates_per_parent = 5
class-attribute
instance-attribute
embedding_model = 'nomic-ai/nomic-embed-text-v1.5'
class-attribute
instance-attribute
enabled = True
class-attribute
instance-attribute
eval_gen_kwargs = None
class-attribute
instance-attribute
memory = None
class-attribute
instance-attribute
n_prompts_per_query = 200
class-attribute
instance-attribute
offline_data = None
class-attribute
instance-attribute
pca_prompt_dim = 15
class-attribute
instance-attribute
pca_query_dim = 40
class-attribute
instance-attribute
prompt_lm = None
class-attribute
instance-attribute
proposer_gen_kwargs = None
class-attribute
instance-attribute
refinement_meta_prompt = None
class-attribute
instance-attribute
retained_per_round = 3
class-attribute
instance-attribute
rounds = 3
class-attribute
instance-attribute
row_scorer = None
class-attribute
instance-attribute
seed_prompt = ''
class-attribute
instance-attribute
supports_batching = True
class-attribute
instance-attribute
tokenizer = None
class-attribute
instance-attribute
train_dataset = []
class-attribute
instance-attribute
trust_remote_code = True
class-attribute
instance-attribute
use_dml = None
class-attribute
instance-attribute
adapt(input_ids, runtime_kwargs=None)
Transform input_ids into a steered prompt.
May read instance state (e.g. self.memory) that was populated by steer().
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
input_ids
|
list[int] | Tensor
|
The user's prompt token IDs. |
required |
runtime_kwargs
|
dict | None
|
Per-call parameters. |
None
|
Returns:
| Type | Description |
|---|---|
list[int] | Tensor
|
The transformed token IDs. |
Source code in steerability/algorithms/input_control/cpo/control.py
268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 | |
adapt_messages(messages, runtime_kwargs=None)
Optional message-level adaptation, called BEFORE chat-template tokenization.
Default returns None (no message-level changes). Subclasses that modify chat structure (set/replace system
prompt, insert example turns) override this. When this method returns a non-None result for a chat-input
generation, the pipeline does NOT additionally call this control's adapt() for that call; a control may
therefore implement both entry points (message-level for chat input, token-level for raw text/tensor input)
without being applied twice. Other input controls in the pipeline still run per their own phase.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
messages
|
list[list[dict]]
|
A batch of chats; outer list is the batch, inner list is the message sequence for one chat. |
required |
runtime_kwargs
|
dict | None
|
Per-call parameters. |
None
|
Returns:
| Type | Description |
|---|---|
list[list[dict]] | None
|
The transformed messages, or None to indicate no change. |
Source code in steerability/algorithms/input_control/cpo/control.py
252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 | |
cleanup()
Release resources allocated during steer().
Override in subclasses that allocate GPU memory or other resources during steering. Default no-op.
Source code in steerability/algorithms/core/base_control.py
195 196 197 198 199 200 201 | |
clone_for_call(seed=None)
A configuration-preserving shallow clone for one generation call.
The clone shares steer-time artifacts (memories, steering vectors, attached tokenizers)
with the original but has its own attribute namespace, so per-call attribute mutation on
the clone never races another call using the original. When seed is given and the
control defines reseed(seed), the clone's client-side RNG is re-seeded.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
seed
|
int | None
|
Optional seed forwarded to the clone's |
None
|
Returns:
| Type | Description |
|---|---|
BaseControl
|
The clone. |
Source code in steerability/algorithms/core/base_control.py
174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 | |
export_state()
The trained scorer memory under the "memory" key (after steer()).
Source code in steerability/algorithms/input_control/cpo/control.py
135 136 137 | |
fit_identity()
The optimizer-relevant args (everything except memory and the per-query search
knobs), or None when the recipe already carries a memory.
Source code in steerability/algorithms/input_control/cpo/control.py
148 149 150 151 152 153 154 155 156 157 158 | |
frozen_form(state)
A same-class frozen form: the recipe args with memory= set to the trained memory
(the training-only args stay inert).
Source code in steerability/algorithms/input_control/cpo/control.py
139 140 141 142 143 144 145 146 | |
requirements()
Backend requirements computed from this instance's configuration, per phase.
With prompt_lm supplied the proposer is a control-owned auxiliary and every phase is
prompt-only. With prompt_lm unset the live pipeline model is bound as the proposer at
steer and consulted per query at adapt, so the generate phase requires
Capability.IN_PROCESS_TORCH.
Source code in steerability/algorithms/input_control/cpo/control.py
111 112 113 114 115 116 117 118 119 120 121 122 123 | |
steer(model=None, tokenizer=None, session=None, **kwargs)
Optional offline preparation. Default is no-op.
session is a SteeringSession on the steering backend, provided by the pipeline.
Source code in steerability/algorithms/input_control/cpo/control.py
160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 | |
steer_access()
ModelAccess.ROLLOUTS with prompt_lm supplied (offline data generation rides the
session); ModelAccess.MODULE with prompt_lm unset (the live model is bound as the
proposer). A precomputed memory with prompt_lm supplied needs only structural
facts (ModelAccess.FACTS).
Source code in steerability/algorithms/input_control/cpo/control.py
125 126 127 128 129 130 131 132 133 | |
steer_fits()
The fit artifacts this instance's steer step will produce, for the steer plan.
Each entry is (artifact, artifact_class), where artifact is the fit source or
recipe class name and artifact_class is "direction" or "calibrated". The default
is an empty tuple (no fits).
Returns:
| Type | Description |
|---|---|
tuple[tuple[str, str], ...]
|
The declared fit artifacts, in declaration order. |
Source code in steerability/algorithms/core/base_control.py
105 106 107 108 109 110 111 112 113 114 115 | |
CPOMemory
dataclass
Custom Memory for CPO; holds the trained scorer and a query→best-prompt cache.
Source code in steerability/algorithms/input_control/cpo/control.py
38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 | |
causal_scorer
instance-attribute
query_cache = field(default_factory=dict)
class-attribute
instance-attribute
load(path, encoder)
classmethod
Source code in steerability/algorithms/input_control/cpo/control.py
50 51 52 53 54 55 | |
save(path)
Source code in steerability/algorithms/input_control/cpo/control.py
44 45 46 47 48 | |
utils
causal_reward
Causal reward model training (CPO Stage 1).
The CPO paper (Chen et al. 2026, arXiv:2602.01711) trains the reward model with Double Machine Learning
(DML) on PCA-reduced query and prompt embeddings, treating the prompt as a continuous treatment relative
to a fixed seed prompt. We expose a single CausalRewardScorer that conforms to BaseScorer and a
train(...) factory.
When econml is installed we use CausalForestDML. Otherwise we fall back to a plain
GradientBoostingRegressor over the concatenated (query, prompt) features, regressing on score
directly. The fallback drops the causal interpretation. econml is not a toolkit dependency; users who
want the paper's estimator install it separately.
logger = logging.getLogger(__name__)
module-attribute
CausalRewardScorer
dataclass
Bases: BaseScorer
Trained CPO reward model. Implements BaseScorer.score(prompts, queries).
Internally:
- embeds prompts and queries with the supplied encoder
- applies separately-fitted PCAs to reduce dimensionality
- holds either an econml
CausalForestDML(mode="dml") or aGradientBoostingRegressor(mode="gbr") over the concatenated reduced features
Source code in steerability/algorithms/input_control/cpo/utils/causal_reward.py
40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 | |
encoder
instance-attribute
estimator
instance-attribute
mode
instance-attribute
prompt_pca
instance-attribute
query_pca
instance-attribute
seed_prompt_emb
instance-attribute
load(path, encoder)
classmethod
Source code in steerability/algorithms/input_control/cpo/utils/causal_reward.py
113 114 115 116 117 118 119 120 121 122 123 124 | |
save(path)
Source code in steerability/algorithms/input_control/cpo/utils/causal_reward.py
98 99 100 101 102 103 104 105 106 107 108 109 110 111 | |
score(prompts, queries=None)
Source code in steerability/algorithms/input_control/cpo/utils/causal_reward.py
71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 | |
train(offline_data, embedding_model, pca_query_dim, pca_prompt_dim, seed_prompt, use_dml=None, encoder=None, device=None, trust_remote_code=False)
Fit the CPO reward model on a list of {query, prompt, score} rows.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
offline_data
|
Sequence[dict]
|
list of dicts with keys |
required |
embedding_model
|
str
|
HF model id for the encoder. Ignored if |
required |
pca_query_dim
|
int
|
PCA target dimension for queries. |
required |
pca_prompt_dim
|
int
|
PCA target dimension for prompts. |
required |
seed_prompt
|
str
|
The base prompt used as the treatment baseline. |
required |
use_dml
|
bool | None
|
If True, require econml; if False, force the GBR fallback. If None (default), use DML when econml is importable, otherwise fall back. |
None
|
encoder
|
TextEncoder | None
|
Optional pre-built |
None
|
device
|
str | None
|
Optional device for the encoder. |
None
|
trust_remote_code
|
bool
|
Trust remote code when loading |
False
|
Source code in steerability/algorithms/input_control/cpo/utils/causal_reward.py
127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 | |
embeddings
Sentence embeddings + PCA reduction for CPO.
Uses a single-tower transformer with mean-pooled last_hidden_state. The CPO paper specifies nomic-embed-text-v1.5 as the default, but any HF text encoder works.
logger = logging.getLogger(__name__)
module-attribute
TextEncoder
Mean-pooled HF encoder. Not a BaseScorer; this is purely a featurizer for CPO's reward model.
Source code in steerability/algorithms/input_control/cpo/utils/embeddings.py
19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 | |
device
property
model = AutoModel.from_pretrained(model_name_or_path, trust_remote_code=trust_remote_code)
instance-attribute
tokenizer = AutoTokenizer.from_pretrained(model_name_or_path, trust_remote_code=trust_remote_code)
instance-attribute
encode(text)
Source code in steerability/algorithms/input_control/cpo/utils/embeddings.py
51 52 | |
encode_batch(texts)
Source code in steerability/algorithms/input_control/cpo/utils/embeddings.py
38 39 40 41 42 43 44 45 46 47 48 49 | |
fit_pca(matrix, n_components)
Fit a PCA whose number of components is clipped to the available rank.
Source code in steerability/algorithms/input_control/cpo/utils/embeddings.py
55 56 57 58 59 60 61 | |
refinement_meta_prompt
Self-refinement meta-prompt for CPO.
Follows Appendix EC.3 of Chen et al., 2026 (arXiv:2602.01711), with a single {seed} placeholder
so it composes with the common LLMMetaPromptProposer.