SASA
steerability.algorithms.output_control.sasa
args
control
logger = logging.getLogger(__name__)
module-attribute
SASA
Bases: OutputControl
Implementation of SASA (Self-disciplined autoregressive sampling) from Ko et al., 2024.
SASA steers generation toward a target attribute defined by labeled examples. It works in two phases:
-
Subspace learning: From labeled positive (desired) and negative (undesired) examples, it fits a linear classifier in the model's own final-layer space. The classifier's weight vector defines a subspace separating the two attribute classes. Data is passed via
gen_wv_dataas unpaired classes (a{'pos', 'neg'}dict or aLabeledExamples) or as paired prompt/response data (a dict carrying a'prompts'key or aContrastivePairs), andprompt_formatselects how each example is rendered before capture.prompt_format='chat_completion'renders each pair as a user turn plus the response and requires the paired form (the paper's{prompt, response, annotation}format);'raw'and'chat_prompt'accept unpaired classes. -
Controlled decoding: At every decoding step the candidate-token logits are shifted by
beta * margin, wheremarginis the classifier distance of the updated context from the undesired side of the subspace. Sampling from the softmax of the adjusted logits nudges generation toward the desired attribute while staying close to the original distribution.
Any binary-labeled attribute works, since the attribute is defined by the labels on gen_wv_data. A previously
fitted probe is loaded via wv_path.
SASA is a step-level control. steer() fits (or loads) a Probe, and get_logits_processors() returns a
ValueGuidedProcessor whose per-candidate value is the subspace margin, obtained via a single same-model
forward per step. candidate_policy selects which tokens are scored: 'surviving' (every token earlier
processors left finite), 'top_p' (the nucleus of the raw logits, the paper's setting), or 'top_k'. The
margins are softmax-normalized over the candidate set and added (scaled by beta) to the candidate logits with
no non-candidate masking. max_candidates clamps the set on top of any policy to bound the per-step forward. As
a step-level control, SASA composes with other output controls and with a decoding driver.
A value_trace list passed via runtime_kwargs receives one ValueStepRecord per scored step (the candidate
ids, their pre-shift scores, the raw margins, and the softmax-normalized shift), for inspecting the per-step
redistribution.
include_in_scoring defaults to False, since scoring under SASA costs a K-candidate model forward per
reference position; opt in explicitly if needed.
Reference:
- "Large Language Models can Become Strong Self-Detoxifiers" Ching-Yun Ko, Pin-Yu Chen, Payel Das, Youssef Mroueh, Soham Dan, Georgios Kollias, Subhajit Chaudhury, Tejaswini Pedapati, Luca Daniel https://arxiv.org/abs/2410.03818
Source code in steerability/algorithms/output_control/sasa/control.py
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 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 | |
RUNTIME_KWARGS_SCHEMA = [{'name': 'value_trace', 'type': 'list', 'scope': 'call', 'help': 'A caller-owned list that receives one ValueStepRecord per scored step of this call.'}]
class-attribute
instance-attribute
args = self.Args.validate(*args, **kwargs)
instance-attribute
beta
instance-attribute
enabled = True
class-attribute
instance-attribute
include_in_scoring = False
class-attribute
instance-attribute
model = None
class-attribute
instance-attribute
probe = None
class-attribute
instance-attribute
same_model_forwards = True
class-attribute
instance-attribute
supports_batching = False
class-attribute
instance-attribute
tokenizer = None
class-attribute
instance-attribute
cleanup()
Release the probe and model references.
Source code in steerability/algorithms/output_control/sasa/control.py
279 280 281 282 283 284 285 286 287 | |
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_constraint(runtime_kwargs=None)
The control's declarative constrained-decoding source, or None.
A control whose per-step masking compiles from a declarative source returns a
ConstraintSource; on a backend advertising Capability.GUIDED_DECODING the pipeline
renders it onto the engine's native structured-output parameters in place of the
control's live processor. The default returns None, which keeps the control on the live
processor mechanism.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
runtime_kwargs
|
dict | None
|
Per-call parameters supplied to |
None
|
Returns:
| Type | Description |
|---|---|
ConstraintSource | None
|
The constraint source, or None. |
Source code in steerability/algorithms/output_control/base.py
190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 | |
export_generation_params(runtime_kwargs=None)
The control's sampling-expressible contribution, or None.
A control whose behavior is expressible as normalized generation parameters returns a
mapping over a subset of stop_strings, stop_token_ids, max_new_tokens, and
min_new_tokens; the pipeline merges it into the call's GenerationParams (stop rules
union with the caller's; token bounds only tighten) and does not additionally collect
the control's live processors and criteria for that call, so the control executes on
every backend through the session's composed stop rules. The default returns None, which
keeps the control on the live processor/criteria mechanism.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
runtime_kwargs
|
dict | None
|
Per-call parameters supplied to |
None
|
Returns:
| Type | Description |
|---|---|
Mapping[str, Any] | None
|
The parameter contribution, or None. |
Source code in steerability/algorithms/output_control/base.py
154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 | |
export_processor_spec(runtime_kwargs=None)
The control's engine-hosted processor form, or None.
A control whose per-step logit math is expressible in an engine's served processor
vocabulary returns a ProcessorSpec; on a backend advertising
Capability.PER_STEP_LOGIT_SPECS with the spec's kind, the pipeline submits it as a
ProcessorSpecEntry in place of the control's live processor. The default returns
None, which keeps the control on the live processor mechanism.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
runtime_kwargs
|
dict | None
|
Per-call parameters supplied to |
None
|
Returns:
| Type | Description |
|---|---|
ProcessorSpec | None
|
The processor spec, or None. |
Source code in steerability/algorithms/output_control/base.py
173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 | |
export_state()
The fitted value-subspace probe under the "probe" key (after steer()).
Source code in steerability/algorithms/output_control/sasa/control.py
120 121 122 | |
fit_identity()
The probe-fitting inputs (gen_wv_* fields and prompt_format), or None when a saved
probe is loaded.
Source code in steerability/algorithms/output_control/sasa/control.py
140 141 142 143 144 145 146 147 148 149 150 | |
frozen_form(state)
A same-class frozen form: wv_path points at the exported probe directory and the
fit-only gen_wv_* fields are dropped. The decode-time settings (candidate_policy,
top_p, top_k, max_candidates) are carried forward.
Source code in steerability/algorithms/output_control/sasa/control.py
124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 | |
get_logits_processors(input_ids, runtime_kwargs, attention_mask=None, **kwargs)
Return a fresh ValueGuidedProcessor implementing SASA's margin-based shift.
The candidate set follows candidate_policy: surviving (every token left finite by earlier
processors), top_p (the nucleus of the raw logits, the paper's setting), or top_k. Margins
are softmax-normalized over the candidate set and added (scaled by beta) with no
non-candidate masking. max_candidates clamps the set on top of the policy to bound the
per-step model forward. A value_trace list in runtime_kwargs receives one ValueStepRecord
per step.
Source code in steerability/algorithms/output_control/sasa/control.py
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 | |
get_stopping_criteria(input_ids, runtime_kwargs, **kwargs)
The control's stopping criteria.
Not applied during scoring (there is no loop to stop). Same call convention as
get_logits_processors.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
input_ids
|
Tensor
|
The steered prompt token ids |
required |
runtime_kwargs
|
dict | None
|
Per-call parameters supplied to |
required |
Returns:
| Type | Description |
|---|---|
list
|
A list of HF |
Source code in steerability/algorithms/output_control/base.py
139 140 141 142 143 144 145 146 147 148 149 150 151 152 | |
requirements()
Backend requirements computed from this instance's configuration, per phase.
The default requires Capability.IN_PROCESS_TORCH at generate and, when
include_in_scoring is True, at score as well, since remote prompt-logprob computation
applies neither live processors nor engine-registered sampling processors to prefill
logits. Setting include_in_scoring=False removes the score-phase requirement.
Returns:
| Type | Description |
|---|---|
Requirements
|
The control's phase-keyed requirements. |
Source code in steerability/algorithms/output_control/base.py
214 215 216 217 218 219 220 221 222 223 224 225 226 | |
steer(model, tokenizer=None, **__)
Load or fit the linear probe defining the attribute subspace.
A wv_path naming a directory loads a saved Probe artifact; a .probe JSON file or a
legacy {'wv', 'mu_mu'} tensor checkpoint is adapted into a Probe over the final
decoder layer. Without wv_path, a probe is fitted on gen_wv_data (fisher direction over
last-token features at the raw final-layer boundary, rendered per prompt_format, midpoint
calibration). The probe's recorded space is validated against the boundary the margins are
evaluated at.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
model
|
PreTrainedModel
|
The base language model to be steered. |
required |
tokenizer
|
PreTrainedTokenizerBase | None
|
Tokenizer for the base model. |
None
|
Returns:
| Name | Type | Description |
|---|---|---|
PreTrainedModel |
PreTrainedModel
|
The input model (unchanged). |
Raises:
| Type | Description |
|---|---|
ValueError
|
If neither |
Source code in steerability/algorithms/output_control/sasa/control.py
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 | |
steer_access()
ModelAccess.MODULE; the probe fits on the live model, which is retained for the
per-step value forwards (the generate phase is in-process).
Source code in steerability/algorithms/output_control/sasa/control.py
115 116 117 118 | |
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 | |